Compare commits

..
222 Commits
Author SHA1 Message Date
Nick Sweeting e06204d6e0 Document the independent live picker asset URL 2026-09-18 02:17:06 -07:00
Nick Sweeting 8209d4844d Serve the shared ArchiveBox documentation version picker 2026-09-18 02:09:40 -07:00
Nick Sweeting dc84a2b053 Preserve historical code examples and lock the documentation build 2026-09-17 23:59:11 -07:00
Nick Sweeting 3944f688f4 Validate historical docs when version and configuration inputs change 2026-09-17 23:38:42 -07:00
Nick Sweeting 96e2de60ae Keep documentation repair branch Docker builds unpublished 2026-09-17 23:33:50 -07:00
Nick Sweeting b9de2eb346 Restore Read the Docs builds for the main 0.7.4 documentation 2026-09-17 23:29:37 -07:00
Nick Sweeting 3830544e13 Update Docker runtime dependencies 2026-05-18 02:24:38 -07:00
Nick Sweeting 913542c19f Release v0.7.4 2026-05-18 02:00:33 -07:00
Nick Sweeting f603a0dfff fix: skip URL entries in save_text_as_source to prevent Path() errors (#1787)
## Fix for Issue #1000

When using `archivebox add --parser=wallabag_atom <feed_url>`, the raw
feed URL was being processed by `save_text_as_source()` which splits on
whitespace and passes each token to `Path()`. URLs get broken and
treated as local file paths, causing spurious "No such file" errors and
random file paths to be accessed.

### Root Cause
Commit a676767 added file-referencing logic to `save_text_as_source()`.
URLs in the raw text (like wallabag feed URLs) get split into fragments
that `Path()` then tries to read as local files.

### Fix
Skip entries that look like URLs (`http://`, `https://`, `ftp://`)
before calling `Path().exists()`.

### Testing
Run: `curl <wallabag_feed_url> | archivebox add --parser=wallabag_atom`

Fixes #1000


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Skip URL tokens (`http://`, `https://`, `ftp://`) in
`save_text_as_source()` to avoid passing them to `Path()`, preventing
"No such file" errors when adding Wallabag Atom feeds. Fixes #1000.

<sup>Written for commit 5e47e055a2.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-04-17 10:11:22 -07:00
zkksdk 5e47e055a2 fix: skip URL entries in save_text_as_source to prevent Path() errors
When raw_text contains URLs (e.g. from wallabag_atom parser), split()
breaks them into fragments that Path() tries to read as local files.
This causes spurious "No such file" errors and random paths to be
accessed. Skip entries that look like URLs before checking Path.exists().

Fixes #1000
2026-04-18 00:25:10 +08:00
Nick Sweeting d1136d74cb fix docker release script 2024-12-15 02:06:49 -08:00
Nick Sweeting 54f598789e bump more js and py dependency lockfiles 2024-12-15 00:33:03 -08:00
Nick Sweeting 5171eaf31e bump dependencies and metadata for 0.7.3 2024-12-15 00:25:35 -08:00
Nick Sweeting 315c9f3844 0.7.2 Release Candidate (#1297) 2024-01-04 19:27:19 -08:00
Nick Sweeting 2ef3655d21 fix python version in build tests 2024-01-04 19:26:54 -08:00
Nick Sweeting fc2bd4a2d5 Update pip.yml 2024-01-04 19:23:26 -08:00
Nick Sweeting 79979d4d05 Update pip.yml 2024-01-04 19:20:03 -08:00
Nick Sweeting 310dede977 limit python version upper bound to 3.11 2024-01-04 19:18:49 -08:00
Nick Sweeting 0353d6b316 Update pip.yml 2024-01-04 19:02:46 -08:00
Nick Sweeting f695b30d36 Update pip.yml 2024-01-04 18:59:34 -08:00
Nick Sweeting df56e993f2 Update pip.yml 2024-01-04 18:51:59 -08:00
Nick Sweeting 41e6a0f215 Update debian.yml 2024-01-04 18:50:41 -08:00
Nick Sweeting e4f1503965 Update test.yml 2024-01-04 18:34:00 -08:00
Nick Sweeting d23fe6a594 Update test.yml 2024-01-04 18:28:03 -08:00
Nick Sweeting 3840265cef Update test.yml 2024-01-04 18:21:27 -08:00
Nick Sweeting 7842ca49d1 Update test.yml 2024-01-04 17:59:18 -08:00
Nick Sweeting c43f861f6f Update test.yml 2024-01-04 17:55:52 -08:00
Nick Sweeting 10dc9922de Update test.yml 2024-01-04 17:39:45 -08:00
Nick Sweeting ffcd1640da Update test.yml 2024-01-04 17:34:37 -08:00
Nick Sweeting 3247866d3a Update test.yml to install packages needed by ldap 2024-01-04 17:30:59 -08:00
Nick Sweeting 490b765d5b add dev deps to lockfile 2024-01-04 17:24:31 -08:00
Nick Sweeting 3ddc0c7f7a fix free space calculation and make it based on absolute values instead of pcts 2024-01-04 17:24:31 -08:00
Nick Sweeting 3ee3916b29 Update test.yml to fix python builds 2024-01-04 17:24:10 -08:00
Nick Sweeting 2bb6dca294 check for available disk space on docker root fs and data volume and improve shell requoting 2024-01-04 15:58:45 -08:00
Nick Sweeting 48c9a49d56 fix shell re-quoting in docker_entrypoint.sh so that quoted subcommands can be passed in 2024-01-04 15:10:10 -08:00
Nick Sweeting 83eede54da fix docker image in compose file 2024-01-04 14:59:08 -08:00
Nick Sweeting a0f01a543d minor readme tweaks 2024-01-04 14:58:41 -08:00
Nick Sweeting 30fd005605 more minor README tweaks 2024-01-04 13:58:33 -08:00
Nick Sweeting 71ca0b27a8 minor formatting and wording improvements to README 2024-01-04 13:47:21 -08:00
Nick Sweeting 4479d3fb17 add Learn More sections to README 2024-01-04 13:32:08 -08:00
Nick Sweeting 64ddab3750 add more data to archive layout section 2024-01-04 12:44:27 -08:00
Nick Sweeting 1532186283 Merge branch 'main' into dev 2024-01-04 12:30:46 -08:00
Nick Sweeting 64bfd7667e collapse README sections to reduce length and link to PUID PGID and root_squash info 2024-01-04 12:30:21 -08:00
Nick Sweeting 6f1d4e477b move README images from imgur to Github assets hosting 2024-01-04 12:29:38 -08:00
Nick Sweeting a215b38681 update dompurify version 2024-01-04 11:43:50 -08:00
Nick Sweeting 94b132818d fix lint errors 2024-01-04 11:41:12 -08:00
Nick Sweeting 5df7b258bd bump version of dev build to 0.7.2 2024-01-03 20:37:00 -08:00
Nick Sweeting c1fd2cfa42 tag URLs immediately once added instead of waiting until archival completes 2024-01-03 20:31:46 -08:00
Nick Sweeting 8ab17f0561 add tag support to schedule command 2024-01-03 20:31:14 -08:00
Nick Sweeting f775da299a show command to enter docker container in failed command output hint when IN_DOCKER=True 2024-01-03 20:12:03 -08:00
Nick Sweeting b9e05b2abe quote URLs in failure message hint output to make copy-pasting easier 2024-01-03 20:11:30 -08:00
Nick Sweeting db2984e47b prefer dom dump to singlefile for generating readability output 2024-01-03 20:11:06 -08:00
Nick Sweeting 78d942ac22 show more detail in readabiliity error messages 2024-01-03 20:09:31 -08:00
Nick Sweeting c26d9dc1bb link to exact build commit hash in public index footer 2024-01-03 20:09:04 -08:00
Nick Sweeting ceabc4e12e fix CHROME_USER_AGENT not interpolating inline config variables 2024-01-03 20:08:39 -08:00
Nick Sweeting 5b07a1126c add comment about why DOM is preferred over singlefile for readability parsing 2024-01-03 19:09:24 -08:00
Nick Sweeting fcdc41a1ab re-enable autocomplete tag field for now as its breaking action buttons display 2024-01-03 19:09:14 -08:00
Nick Sweeting b0da386793 include git refs and HEAD file in docker images to allow GIT_SHA of builds to show in version output 2024-01-03 18:59:45 -08:00
Nick Sweeting 4af9beedd1 disable GLOBAL_CONTEXT used by version upgrade banner as not all admin views accept extra_context kwarg 2024-01-03 16:11:27 -08:00
Nick Sweeting d2281cabd6 remove moment.js 2024-01-03 16:04:29 -08:00
Nick Sweeting 2c54e55697 prefer dom dump to singlefile for generating readability output 2024-01-02 19:50:56 -08:00
Nick Sweeting 1f71fedff7 dont check for PGID=0 anymore 2024-01-02 17:22:54 -08:00
Nick Sweeting 1316607262 fix dependency versions 2024-01-02 17:19:09 -08:00
Nick Sweeting 8ee2981957 fix check-for-update fetching code 2024-01-02 17:17:35 -08:00
Nick Sweeting 73993d26c0 move SYSTEM_USER call to get_system_user func 2024-01-02 17:11:29 -08:00
Nick Sweeting a0c16ecae0 bump dependency versions 2024-01-02 17:10:29 -08:00
Nick Sweeting 6584f300d6 add better comments 2024-01-02 17:10:22 -08:00
Nick Sweeting b03ece41e9 Show banner and hints to upgrade when ArchiveBox is out of date (#1274) 2023-12-19 10:03:42 -08:00
Nick Sweeting bfcff2f78f make sure user is a superuser before showing upgrade notification 2023-12-19 10:03:04 -08:00
Nick Sweeting bfebc23f42 Rename VERSION_RELEASES to VERSIONS_AVAILABLE 2023-12-19 10:02:28 -08:00
Nick Sweeting 42dad5e0b3 rename VERSION_RELEASES to VERSIONS_AVAILABLE 2023-12-19 10:01:08 -08:00
Nick Sweeting 168cdac2c4 rename VERSION_RELEASES to VERSIONS_AVAILABLE 2023-12-19 09:58:42 -08:00
Nick Sweeting 480eff52db Update urls.py 2023-12-19 09:58:15 -08:00
Nick Sweeting 14f10a0461 Merge branch 'dev' into version-banner 2023-12-19 09:57:08 -08:00
Nick Sweeting 1f33fc73c9 Update README.md 2023-12-19 08:30:37 -08:00
Nick Sweeting 43128122d6 Update README.md minor formatting changes 2023-12-19 08:25:51 -08:00
Nick Sweeting 764a483903 make get_commit_hash more precisce by using exact ref (#1270) 2023-12-18 19:28:10 -08:00
Nick Sweeting 487e560e32 Update config.py to use fallback git head method 2023-12-18 19:27:52 -08:00
Nick Sweeting 5805e18380 Update settings.py 2023-12-18 19:25:08 -08:00
Nick Sweeting 7d5a7f80ca Update admin.py 2023-12-18 19:24:48 -08:00
Nick Sweeting 7d06836443 add WIP comment 2023-12-18 19:24:18 -08:00
Nick Sweeting 89bdda85e9 Merge branch 'dev' into side-fixes 2023-12-18 19:17:31 -08:00
Nick Sweeting 93b5e0d9e5 Update docker.yml 2023-12-17 22:56:09 -08:00
Nick Sweeting 5881a64353 fallback to alternative way of getting chrome version if needed 2023-12-17 22:11:13 -08:00
Nick Sweeting 25a505f668 fix GIT_SHA tagging during build 2023-12-17 21:51:04 -08:00
Nick Sweeting 6ec7114033 also build GIT_SHA version of images 2023-12-17 21:48:38 -08:00
Nick Sweeting 78ad83a510 Update docker.yml 2023-12-17 21:18:16 -08:00
Nick Sweeting 7410fddacf improve entrypoint error handling and color coding 2023-12-17 21:03:01 -08:00
Nick Sweeting b021a23b7b dont install C compiler for final pip install archivebox in Dockerfile 2023-12-17 21:02:42 -08:00
Nick Sweeting 6d2c4f9e39 dont include docs in docker builds 2023-12-17 21:02:16 -08:00
Nick Sweeting c259b3f2fd fix browsers chown on startup again 2023-12-17 17:59:10 -08:00
Nick Sweeting b82297a61a bump python versions supported in pyproject.toml 2023-12-17 17:55:35 -08:00
Nick Sweeting 1ce228e9bb fix /browsers/* does not exist chown error 2023-12-17 17:51:36 -08:00
Nick Sweeting f250762c98 bump python requirement versions 2023-12-17 17:44:42 -08:00
Nick Sweeting 4a4ed2cad6 fix version string parsing 2023-12-17 17:44:26 -08:00
Nick Sweeting d5f1411ed1 exit with running as root error when PUID is 0 2023-12-17 16:57:40 -08:00
Nick Sweeting 8a306dbf6f add BUILD_TIME to archivebox version output 2023-12-17 16:57:02 -08:00
Nick Sweeting 951bba52a0 hide +editable from version string in output because all docker installs are editable 2023-12-17 16:14:39 -08:00
Nick Sweeting 35685cf6ea mkdir and chown browsers directory automatically in entrypoint script 2023-12-17 16:06:02 -08:00
Nick Sweeting b9f25d84f3 catch edge case where user sets PUID=0 or autodetected PUID=0 and add hints about network filesystem permissions being messed with 2023-12-17 16:05:45 -08:00
Nick Sweeting 169b427fde bump min required python version to 3.7.0 2023-12-17 16:05:00 -08:00
Nick Sweeting c183de2903 split archivebox version into separate RUN line so that version can depend on VERSION.txt to get build time 2023-12-17 16:04:48 -08:00
Nick Sweeting d4b0660b8d add hint to use docker run instead of exec when catching root error fixes #1287 2023-12-17 16:04:02 -08:00
Nick Sweeting 4749a3ac6d Merge pull request #1281 from vladimirdulov/bugfix/ldap_and_local_auth 2023-12-06 06:08:06 -08:00
Vladimir D 37bdcbf950 make local auth working along with ldap auth so that local superuser could manage ldap users 2023-12-06 16:16:51 +04:00
Ben Muthalaly a2b11b696d Remove version comparison function 2023-12-01 16:55:07 -06:00
Ben Muthalaly 25d7f3ed1c Parse without regex and change 'let' to 'const' 2023-11-30 19:57:46 -06:00
Ben MuthalalyandNick Sweeting 4ae35c86f2 Update archivebox/config.py
Co-authored-by: Nick Sweeting <git@sweeting.me>
2023-11-30 19:53:20 -06:00
Ben MuthalalyandNick Sweeting 8616cd758a Fix typos, formatting, and variable declarations
Co-authored-by: Nick Sweeting <git@sweeting.me>
2023-11-30 18:06:54 -06:00
Ben Muthalaly ba4342dadf Add CLI hints to add and schedule jobs 2023-11-27 22:58:13 -06:00
Ben Muthalaly a3fd8a8ecd Move version banner logic from JS to Python
Also adds CSS styling to banner.
2023-11-27 22:56:30 -06:00
Ben Muthalaly 7599dbb79d Add notification about upgrade to admin page 2023-11-23 03:04:31 -06:00
Nick Sweeting 0bd83076db Fix icon and add Elest.io to paid hosting options 2023-11-19 17:35:19 -08:00
Nick Sweeting ff12d34dbf warn about delayed releases for some package managers 2023-11-19 13:51:42 -08:00
Nick Sweeting 2619853e60 Update README.md 2023-11-19 13:45:58 -08:00
Nick Sweeting f81e51a1fc fix typo in readme 2023-11-19 13:45:00 -08:00
Nick Sweeting 9ced228d58 Merge pull request #1271 from ArchiveBox/dependabot/pip/yt-dlp-2023.11.14 2023-11-16 08:15:38 -08:00
dependabot[bot] b3adef6267 Bump yt-dlp from 2023.10.13 to 2023.11.14
Bumps [yt-dlp](https://github.com/yt-dlp/yt-dlp) from 2023.10.13 to 2023.11.14.
- [Release notes](https://github.com/yt-dlp/yt-dlp/releases)
- [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md)
- [Commits](https://github.com/yt-dlp/yt-dlp/compare/2023.10.13...2023.11.14)

---
updated-dependencies:
- dependency-name: yt-dlp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-15 14:49:40 +00:00
Nick Sweeting f5c2cfcfe2 dont alert on statics 2023-11-14 02:30:59 -08:00
Nick Sweeting 277364ab61 remove extra files 2023-11-14 02:29:40 -08:00
Nick Sweeting 76f6cfbfde Update pyproject.toml and merge dev groups 2023-11-14 02:24:51 -08:00
Nick Sweeting 6761ed368c wip pre-0.7.1 side fixes 2023-11-14 02:09:58 -08:00
Nick Sweeting 2b421c56bb require builx for for docker builds 2023-11-14 02:07:54 -08:00
Nick Sweeting f0033f75d0 config.py lint fixes 2023-11-14 02:07:35 -08:00
Nick Sweeting 0b950b3253 make get_commit_hash more precisce by using exact ref 2023-11-14 02:04:57 -08:00
Nick Sweeting f6ea21eabf bump version in package.json 2023-11-14 00:52:29 -08:00
Nick Sweeting 3f8ff7913a override theme css 2023-11-14 00:45:53 -08:00
Nick Sweeting 6188dcdcbd return placeholder version during docs build 2023-11-14 00:38:37 -08:00
Nick Sweeting d3d05f5f64 add fallback version detection mode 2023-11-14 00:21:09 -08:00
Nick Sweeting 8bb448c614 install both sets of requirements 2023-11-14 00:11:45 -08:00
Nick Sweeting 7665c803da add requirements.txt to docs subfolder 2023-11-14 00:10:10 -08:00
Nick Sweeting 8b93641c4b clone submodules 2023-11-14 00:05:08 -08:00
Nick Sweeting 65bf98bcd2 remove python-ldap from requirements.txt 2023-11-14 00:01:44 -08:00
Nick Sweeting 282e033b3c fix nodejs sphinx requirement 2023-11-13 23:58:34 -08:00
Nick Sweeting 93d284a67b fix yaml typo 2023-11-13 23:58:12 -08:00
Nick Sweeting 79a69ca031 add .readthedocs.yaml config 2023-11-13 23:57:49 -08:00
Nick Sweeting ca8e3a5403 add .readthedocs.yaml config 2023-11-13 23:56:12 -08:00
Nick Sweeting 1e4457690e bump docs build 2023-11-13 23:51:46 -08:00
Nick Sweeting 4523d6817f readme fixes 2023-11-13 23:51:46 -08:00
Nick Sweeting 74e03fd6f9 Update jekyll-gh-pages.yml 2023-11-13 22:50:35 -08:00
Nick Sweeting ab64a86716 Update jekyll-gh-pages.yml 2023-11-13 22:50:03 -08:00
Nick Sweeting c778ea3eea remove old stdeb.cfg config 2023-11-13 22:39:23 -08:00
Nick Sweeting 823138887c move gh pages content to subdirectory 2023-11-13 22:38:19 -08:00
Nick Sweeting 716f68c34a bump vendor dependency versions 2023-11-13 22:38:19 -08:00
Nick Sweeting 083d8b9965 fix Dockerfile lint issues 2023-11-13 22:38:19 -08:00
Nick Sweeting c6a4fe325f Update jekyll-gh-pages.yml 2023-11-13 22:34:48 -08:00
Nick Sweeting dc346c06d0 Update README.md 2023-11-13 22:34:25 -08:00
Nick Sweeting 6092a27330 Update jekyll-gh-pages.yml 2023-11-13 22:30:24 -08:00
Nick Sweeting 9c0b637bf4 Create jekyll-gh-pages.yml 2023-11-13 22:27:25 -08:00
Nick Sweeting eaedd828b7 fix missing stderr redirect breaking docker hub autobuild 2023-11-13 20:40:29 -08:00
Nick Sweeting dd1e01ee24 add requirements.txt bc required by docker hub auto build 2023-11-13 20:21:50 -08:00
Nick Sweeting c1fb221d58 ignore git requirements.txt 2023-11-13 20:19:15 -08:00
Nick Sweeting 8a17a74c22 bump pdm package lockfiles 2023-11-13 20:18:34 -08:00
Nick Sweeting d0cc1ff675 ignore old venv 2023-11-13 20:18:34 -08:00
Nick Sweeting 7c9babac95 fix broken markdown link 2023-11-13 20:07:28 -08:00
Nick Sweeting 4f5c81a81a Update README.md 2023-11-13 20:06:57 -08:00
Nick Sweeting 51fe525c58 Update README.md 2023-11-13 20:05:04 -08:00
Nick Sweeting 70fa20fbcc Update README.md 2023-11-13 20:03:37 -08:00
Nick Sweeting 547b78c843 Update README.md 2023-11-13 20:00:48 -08:00
Nick Sweeting aec99db7bb Update README.md 2023-11-13 19:57:06 -08:00
Nick Sweeting 7238a8288e Update README.md 2023-11-13 19:50:50 -08:00
Nick Sweeting 3d9d01ccd7 fix PLAYWRIGHT_BROWSERS_PATH not always being created 2023-11-13 19:18:47 -08:00
Nick Sweeting 3d07efb5d7 Add links to fdupes and rdfind for deduplicating archive content 2023-11-13 17:17:50 -08:00
Nick Sweeting f5739506f6 Merge pull request #1244 from overhacked/search_index_extract_html_text 2023-11-08 23:34:44 -08:00
Nick Sweeting fb698e6ecf Update README.md 2023-11-08 22:42:20 -08:00
Nick Sweeting 7c7257c446 Update README.md 2023-11-08 22:40:06 -08:00
Nick Sweeting 9df570aa1a Update README.md 2023-11-08 22:39:20 -08:00
Nick Sweeting cbaf88186c Update README.md 2023-11-08 22:35:18 -08:00
Nick Sweeting c61aabbc3c Update README.md 2023-11-08 22:33:56 -08:00
Nick Sweeting cf92025928 Update README.md 2023-11-08 22:33:22 -08:00
Nick Sweeting 58571d3756 Update README.md 2023-11-08 22:26:55 -08:00
Nick Sweeting 33bb74af6b Update README.md 2023-11-08 22:26:33 -08:00
Nick Sweeting cf8186bee3 Update README.md 2023-11-08 22:24:21 -08:00
Nick Sweeting 0464199c9e Update README.md 2023-11-08 22:11:56 -08:00
Nick Sweeting 1b55244884 Update README.md 2023-11-08 22:08:04 -08:00
Nick Sweeting 01d342f2b5 Update README.md 2023-11-08 22:07:27 -08:00
Nick Sweeting 75e3515373 Update README.md 2023-11-08 22:03:14 -08:00
Nick Sweeting 88c211c719 Update README.md 2023-11-08 22:02:49 -08:00
Nick Sweeting db51160004 Update README.md 2023-11-08 22:01:08 -08:00
Nick Sweeting 144590b0bf Update README.md 2023-11-08 21:58:16 -08:00
Nick Sweeting eb59eb9591 Clarify Windows is only supported with Docker 2023-11-08 21:40:30 -08:00
Nick Sweeting fbd3d2a260 Update README.md 2023-11-08 21:39:40 -08:00
Nick Sweeting 6fc75ddb0d Update README.md 2023-11-08 09:47:54 -08:00
Nick Sweeting ae83699fe9 Update README.md 2023-11-08 09:47:28 -08:00
Nick Sweeting 2e271af5d2 Merge pull request #1267 from malteo/user-pid-gid-fix 2023-11-08 09:42:42 -08:00
Matteo Giordano a8eade4dd0 Fix archivebox user UID & GID 2023-11-08 13:13:25 +01:00
Nick Sweeting ba395496ba Update README.md 2023-11-08 03:16:43 -08:00
Nick Sweeting 1c222ce1ce bump python and node requirements 2023-11-08 03:09:22 -08:00
Nick Sweeting 38106185e1 Update README.md 2023-11-08 03:07:45 -08:00
Nick Sweeting 0ec766e33d Update README.md 2023-11-08 03:06:29 -08:00
Nick Sweeting 1588f62345 Update README.md 2023-11-08 03:04:55 -08:00
Nick Sweeting 253537afcc Update README.md 2023-11-08 03:02:47 -08:00
Nick Sweeting d8de12463e Update README.md 2023-11-08 03:01:06 -08:00
Nick Sweeting 79d1acfb29 Update README.md 2023-11-08 03:00:28 -08:00
Nick Sweeting 22114afac1 Update README.md 2023-11-08 02:58:28 -08:00
Nick Sweeting 2f95ab3ea7 Update README.md 2023-11-08 02:57:29 -08:00
Nick Sweeting f211b0e6ea Update README.md 2023-11-08 02:56:52 -08:00
Nick Sweeting be1fa5600d Update README.md 2023-11-08 02:55:29 -08:00
Nick Sweeting 51a6a216f5 Update README.md 2023-11-08 02:54:39 -08:00
Nick Sweeting 759601fb84 Update README.md 2023-11-08 02:54:24 -08:00
Nick Sweeting ce1dce495d Update README.md 2023-11-08 02:54:03 -08:00
Nick Sweeting fbd78bf1de Update README.md 2023-11-08 02:53:38 -08:00
Nick Sweeting 49902d49d3 Update README.md 2023-11-08 02:49:51 -08:00
Nick Sweeting 6e73b0fb55 Update README.md 2023-11-08 02:48:41 -08:00
Nick Sweeting 91e2efec57 Update README.md 2023-11-08 02:45:53 -08:00
Nick Sweeting 293a1b5263 Update README.md 2023-11-08 02:38:07 -08:00
Nick Sweeting 0a2e48f16f Update README.md 2023-11-08 02:34:44 -08:00
Nick Sweeting bd6b21ec36 Update README.md 2023-11-08 02:31:13 -08:00
Nick Sweeting 89e3b96623 Update README.md 2023-11-08 02:29:16 -08:00
Nick Sweeting ee6bf58170 Update README.md 2023-11-08 02:27:40 -08:00
Nick Sweeting e65b80b084 Update README.md 2023-11-08 02:19:06 -08:00
Nick Sweeting 7eedf85577 Update README.md 2023-11-08 02:16:29 -08:00
Nick Sweeting 1ef4d76431 Update README.md 2023-11-08 02:13:36 -08:00
Nick Sweeting df22cf740a Update README.md 2023-11-08 02:11:55 -08:00
Nick Sweeting ad65a316c1 Update README.md 2023-11-08 02:03:11 -08:00
Nick Sweeting 5a6fac971a Update README.md 2023-11-08 01:53:27 -08:00
Nick Sweeting b0e42323ae Update README.md 2023-11-08 00:57:22 -08:00
Nick Sweeting a680724367 Merge branch 'dev' into search_index_extract_html_text 2023-10-27 23:09:28 -07:00
Ross Williams 310b4d1242 Add htmltotext extractor
Saves HTML text nodes and selected element attributes in
`htmltotext.txt` for each Snapshot. Primarily intended to be used
for search indexing.
2023-10-23 21:42:32 -04:00
Ross Williams 6555719489 Add space after tags when extracting text
Add space after any close tag to ensure that
tokens that would be rendered separate in HTML
get extracted as separate tokens in text.

Example:

`<p>First</p><p>Second</p>` --> `First Second`
NOT `FirstSecond`
2023-10-16 09:59:08 -04:00
Ross Williams d8aa84ac98 Make extracting text for indexing optional
Add a configuration option to enable/disable HTML text extraction
for indexing
2023-10-12 13:14:39 -04:00
Ross Williams b6a20c962a Extract text from singlefile.html when indexing
singlefile.html contains a lot of large strings in the form of `data:`
URLs, which can be unnecessarily stored in full-text indices. Also,
large chunks of JavaScript shouldn't be indexed, either, as they pollute
search results for searches about JS functions, etc.

This commit takes a blanket approach of parsing singlefile.html as it is
read and only outputting text and selected textual attributes (like
`alt`) for indexing.
2023-10-12 13:06:35 -04:00
66 changed files with 2227 additions and 2261 deletions

No files matched your search

+7 -2
View File
@@ -5,22 +5,27 @@ __pycache__/
.mypy_cache/
.pytest_cache/
.github/
.git/
.pdm-build/
.pdm-python/
.pdm-python
.eggs/
.git/
!.git/HEAD
!.git/refs/heads/*
venv/
.venv/
.venv-old/
.docker-venv/
node_modules/
docs/
build/
dist/
brew_dist/
deb_dist/
pip_dist/
assets/
docker/
data/
output/
+20
View File
@@ -0,0 +1,20 @@
version: 2
build:
os: ubuntu-24.04
tools:
python: "3.13"
jobs:
build:
html:
- uv run --no-project --with-requirements .github/docs/requirements.txt sphinx-build -W --keep-going -b html -c .github/docs docs "$READTHEDOCS_OUTPUT/html"
submodules:
include:
- docs
python:
install:
- method: uv
command: pip
requirements: .github/docs/requirements.txt
sphinx:
configuration: .github/docs/conf.py
fail_on_warning: true
+43
View File
@@ -0,0 +1,43 @@
# Building the 0.7.4 documentation
The `docs` submodule remains pinned to the wiki revision shipped with 0.7.4.
This configuration renders that historical user guide without installing
ArchiveBox, importing Django, or updating the wiki checkout.
Prerequisites: [uv](https://docs.astral.sh/uv/getting-started/installation/) 0.11.3 and Python 3.13, matching CI and Read the Docs. `uv` can install Python when needed.
```sh
git submodule update --init docs
uv run --no-project --python 3.13 --with-requirements .github/docs/requirements.txt \
sphinx-build -W --keep-going -b html -c .github/docs docs /tmp/archivebox-docs
```
`conf.py` adapts GitHub wiki links, heading levels, and obsolete local anchors
in memory. Configuration options without a prose section link to their exact
0.7.4 source definition. The original user documentation is not rewritten.
Generated Python autodoc scaffolding is outside this standalone user guide;
its legacy Django-dependent builder is not loaded.
Read the Docs uses `.github/.readthedocs.yaml` and the same strict HTML command.
Keep its project configuration-file setting pointed at that path.
`requirements.txt` pins the complete documentation dependency set. To update it,
edit `requirements.in` and run:
```sh
uv pip compile --universal --python-version 3.13 .github/docs/requirements.in \
--output-file .github/docs/requirements.txt
```
## Live version picker
Read the Docs loads `https://docs.archivebox.io/docs-live-picker/_static/archivebox-versions.js`
through Addons > Custom script. The hidden `docs-live-picker` version keeps this
asset available independently of future main or release documentation builds. This applies the release policy and puts `dev`
after `latest` across existing versions without rebuilding them. The script uses
the published version inventory and preserves existing navigation URLs.
The hosting checkout command checks out the original main/tag commit, then overlays
only the historical Sphinx build files from pinned docs repair commits. Application
source, release tags, and pinned wiki revisions remain those of the selected release.
Keep the docs repair branches available for fetching these configuration commits.
+200
View File
@@ -0,0 +1,200 @@
"""Build the pinned 0.7.4 user guide without importing ArchiveBox or Django."""
import json
import os
import re
from pathlib import Path
from urllib.parse import unquote
from markdown_it import MarkdownIt
from myst_parser.mdit_to_docutils.base import default_slugify
ROOT = Path(__file__).resolve().parents[2]
DOCS = ROOT / "docs"
project = "ArchiveBox"
author = "ArchiveBox contributors"
copyright = "ArchiveBox contributors"
html_show_sphinx = False
release = json.loads((ROOT / "package.json").read_text())["version"]
version = release
extensions = ["myst_parser", "sphinx_rtd_theme"]
source_suffix = {".rst": "restructuredtext", ".md": "markdown"}
root_doc = "index"
# These are generated autodoc scaffolding, not standalone user documentation.
exclude_patterns = [
"archivebox*.rst",
"modules.rst",
"_Sidebar.md",
"_Footer.md",
".git",
]
html_theme = "sphinx_rtd_theme"
html_theme_options = {
"navigation_depth": 3,
"collapse_navigation": False,
"version_selector": True,
"language_selector": False,
}
html_context = {"current_version": os.environ.get("READTHEDOCS_VERSION", release)}
html_logo = str(DOCS / "logo.png")
html_static_path = [str(DOCS / "_static"), str(ROOT / ".github/docs/static")]
html_title = f"ArchiveBox {release} documentation"
myst_heading_anchors = 6
def rewrite_prose(text, transform):
"""Rewrite navigation outside fenced and indented code blocks."""
lines = text.splitlines(keepends=True)
output = []
start = 0
for token in MarkdownIt().parse(text):
if token.type not in {"fence", "code_block"}:
continue
first, last = token.map
output.append(transform("".join(lines[start:first])))
output.append("".join(lines[first:last]))
start = last
output.append(transform("".join(lines[start:])))
return "".join(output)
def prepare_source(app, docname, source):
"""Adapt GitHub wiki navigation in memory, preserving the pinned sources."""
text = source[0]
if docname == "Contents":
text = re.sub(
r"API Reference\n#############.*?(?=Meta\n####)", "", text, flags=re.DOTALL
)
text += "\n.. toctree::\n :hidden:\n\n Home\n"
if docname == "index":
text = text.replace("archivebox info", "archivebox status")
text = text.replace(
"pip install archivebox", f"pip install archivebox=={release}"
)
text = text.replace(
"ArchiveBox/ArchiveBox/tree/master",
f"ArchiveBox/ArchiveBox/tree/v{release}",
)
text = text.replace(
"`Github <https://github.com/ArchiveBox/ArchiveBox/issues>`_",
"`GitHub issues <https://github.com/ArchiveBox/ArchiveBox/issues>`_",
)
text = text.replace(
"==========\nArchiveBox\n==========",
f"ArchiveBox {release}\n" + "=" * (11 + len(release)),
)
if Path(app.env.doc2path(docname)).suffix == ".md":
def wiki_link(match):
parts = match.group(1).split("|", 1)
label, target = (parts[0], parts[-1])
page, separator, anchor = unquote(target).partition("#")
page = page.replace(" ", "-")
suffix = f"#{anchor}" if separator else ""
return f"[{label}]({page}.md{suffix})"
text = rewrite_prose(
text, lambda prose: re.sub(r"\[\[([^\]\n]+)\]\]", wiki_link, prose)
)
source[0] = text
# GitHub wiki headings allow skipped levels; normalize their hierarchy before MyST
# parses them, without changing fenced code blocks or the historical source files.
def normalize_markdown(app, docname, source):
if Path(app.env.doc2path(docname)).suffix != ".md":
return
text = source[0]
if docname in {"Home", "README", "Donations", "Upgrading-or-Merging-Archives"}:
text = (
"# "
+ (
"ArchiveBox overview"
if docname == "README"
else docname.replace("-", " ")
)
+ "\n\n"
+ text
)
lines = text.splitlines()
stack = []
for token in MarkdownIt().parse(text):
if token.type != "heading_open":
continue
original = int(token.tag[1])
while stack and stack[-1] >= original:
stack.pop()
stack.append(original)
line = token.map[0]
lines[line] = re.sub(r"^#{1,6}(?=\s)", "#" * len(stack), lines[line]).replace(
"\ufe0f", ""
)
text = "\n".join(lines) + "\n"
def link(match):
target = match.group(1)
if "://" in target or target.startswith("mailto:"):
return match.group(0)
page, sep, anchor = target.partition("#")
if page and (DOCS / (page + ".md")).exists():
page += ".md"
# MyST resolves Markdown GitHub slugs (e.g. background--motivation)
# to rendered section IDs; raw HTML links below need explicit rewrites.
anchor = anchor.lower()
if (docname == "Configuration" and not page) or page == "Configuration.md":
other = (DOCS / "Configuration.md").read_text()
for heading in re.findall(r"^#+\s+(.+)$", other, re.MULTILINE):
if anchor in [
word.lower() for word in re.findall(r"`([^`]+)`", heading)
]:
anchor = default_slugify(heading.replace("`", ""))
break
aliases = {"templates_dir": "custom_templates_dir"}
anchor = aliases.get(anchor, anchor)
if anchor in {
"search_backend_timeout",
"search_backend_engine",
"use_curl",
}:
code = (ROOT / "archivebox/config.py").read_text().splitlines()
number = next(
i + 1 for i, line in enumerate(code) if anchor.upper() in line
)
return (
"](https://github.com/ArchiveBox/ArchiveBox/blob/v0.7.4/archivebox/config.py#L"
+ str(number)
+ ")"
)
if (
docname == "README"
and anchor == "saves-lots-of-useful-stuff-for-each-imported-link"
):
anchor = "output-formats"
if docname == "Web-Archiving-Community":
anchor = {
"blogs": "blogs-friends-of-archivebox",
"articles": "articles-we-like-about-internet-archiving",
}.get(anchor, anchor)
return "](" + page + ("#" + anchor if sep else "") + ")"
def rewrite_links(prose):
prose = re.sub(r"\]\(([^)\s]+)\)", link, prose)
# Raw HTML links are outside MyST's cross-reference resolver.
html_anchors = {
"background--motivation": "background-motivation",
"Caveats": "caveats",
"contents": "web-archiving-community",
"%EF%B8%8F-cli-usage": "cli-usage",
}
for old, new in html_anchors.items():
prose = prose.replace(f'href="#{old}"', f'href="#{new}"')
prose = prose.replace("#️-cli-usage", "#cli-usage")
return prose
text = rewrite_prose(text, rewrite_links)
source[0] = text
def setup(app):
app.connect("source-read", prepare_source)
app.connect("source-read", normalize_markdown)
+3
View File
@@ -0,0 +1,3 @@
Sphinx==8.2.3
myst-parser==4.0.1
sphinx-rtd-theme==3.1.0
+75
View File
@@ -0,0 +1,75 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --universal --python-version 3.13 .github/docs/requirements.in --output-file .github/docs/requirements.txt
alabaster==1.0.0
# via sphinx
babel==2.18.0
# via sphinx
certifi==2026.7.22
# via requests
charset-normalizer==3.5.1
# via requests
colorama==0.4.6 ; sys_platform == 'win32'
# via sphinx
docutils==0.21.2
# via
# myst-parser
# sphinx
# sphinx-rtd-theme
idna==3.20
# via requests
imagesize==2.0.1
# via sphinx
jinja2==3.1.6
# via
# myst-parser
# sphinx
markdown-it-py==3.0.0
# via
# mdit-py-plugins
# myst-parser
markupsafe==3.0.3
# via jinja2
mdit-py-plugins==0.6.1
# via myst-parser
mdurl==0.1.2
# via markdown-it-py
myst-parser==4.0.1
# via -r .github/docs/requirements.in
packaging==26.3
# via sphinx
pygments==2.21.0
# via sphinx
pyyaml==6.0.3
# via myst-parser
requests==2.34.2
# via sphinx
roman-numerals==4.1.0
# via roman-numerals-py
roman-numerals-py==4.1.0
# via sphinx
snowballstemmer==3.1.1
# via sphinx
sphinx==8.2.3
# via
# -r .github/docs/requirements.in
# myst-parser
# sphinx-rtd-theme
# sphinxcontrib-jquery
sphinx-rtd-theme==3.1.0
# via -r .github/docs/requirements.in
sphinxcontrib-applehelp==2.0.0
# via sphinx
sphinxcontrib-devhelp==2.0.0
# via sphinx
sphinxcontrib-htmlhelp==2.1.0
# via sphinx
sphinxcontrib-jquery==4.1
# via sphinx-rtd-theme
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==2.0.0
# via sphinx
sphinxcontrib-serializinghtml==2.0.0
# via sphinx
urllib3==2.8.0
# via requests
+106
View File
@@ -0,0 +1,106 @@
/* ArchiveBox RTD picker policy. No requests, credentials, or documentation edits.
* RTD event is read-only input: internal flyout rendering precedes the public
* event, so changing its payload is not a supported rendering hook.
* Targets: addons readthedocs-flyout shadow dl.versions, RTD theme 3.1 select,
* and the legacy theme's Versions definition list. Other navigation is untouched.
*/
(function () {
"use strict";
function parse(slug) {
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-?rc(\d*))?$/i.exec(slug);
if (!match) return null;
return { slug, major: +match[1], minor: +match[2], patch: +match[3],
rc: match[4] === undefined ? null : +(match[4] || 0) };
}
function compare(a, b) {
return a.major - b.major || a.minor - b.minor || a.patch - b.patch ||
(a.rc === null ? Infinity : a.rc) - (b.rc === null ? Infinity : b.rc) ||
a.slug.localeCompare(b.slug);
}
function select(versions) {
const published = versions.filter(v => v.active !== false && v.built !== false && !v.hidden);
const parsed = published.map(v => parse(v.slug)).filter(Boolean);
const minors = [...new Set(parsed.sort((a, b) => compare(b, a))
.map(v => `${v.major}.${v.minor}`))];
const winners = minors.map((minor, index) => parsed.filter(v =>
`${v.major}.${v.minor}` === minor && (index < 2 ? v.rc !== null : v.rc === null))
.sort((a, b) => compare(b, a))[0]).filter(Boolean).map(v => v.slug);
return ["latest", "dev"].filter(slug => published.some(v => v.slug === slug)).concat(winners);
}
// Export only the pure policy for Node verification; browser has no new globals.
if (typeof module !== "undefined" && module.exports) {
module.exports = { parse, select };
return;
}
const project = document.querySelector('meta[name="readthedocs-project-slug"]')?.content;
if (project && project !== "archivebox") return;
if (!document.querySelector('meta[name="readthedocs-addons-api-version"]')) {
const meta = document.createElement("meta");
meta.name = "readthedocs-addons-api-version";
meta.content = "1";
document.head.append(meta);
}
let wanted = null;
const watched = new WeakSet();
function reconcile(container) {
if (!wanted) return;
const isSelect = container.tagName === "SELECT";
const children = [...container.children].filter(n => isSelect ? n.tagName === "OPTION" : n.tagName === "DD");
const slug = n => isSelect ? n.value : n.querySelector("a")?.textContent.trim();
const selected = isSelect ? container.value : null;
const kept = wanted.map(s => children.find(n => slug(n) === s)).filter(Boolean);
const current = children.filter(n => wanted.includes(slug(n)));
for (const node of children) if (!wanted.includes(slug(node))) node.remove();
if (kept.some((node, i) => node !== current[i])) {
for (const node of kept) container.append(node);
}
if (isSelect) {
container.value = selected;
if (!kept.some(n => n.value === selected)) container.selectedIndex = -1;
}
}
function watch(container) {
reconcile(container);
if (watched.has(container)) return;
watched.add(container);
new MutationObserver(() => reconcile(container)).observe(container, { childList: true, subtree: true });
}
function scan() {
if (!wanted) return;
document.querySelectorAll("div.switch-menus > div.version-switch select").forEach(watch);
document.querySelectorAll(".rst-other-versions dl").forEach(dl => {
if (dl.querySelector("dt")?.textContent.trim() === "Versions") watch(dl);
});
document.querySelectorAll("readthedocs-flyout").forEach(host => {
const root = host.shadowRoot;
if (!root) return;
root.querySelectorAll("dl.versions").forEach(watch);
if (!watched.has(root)) {
watched.add(root);
new MutationObserver(() => root.querySelectorAll("dl.versions").forEach(watch))
.observe(root, { childList: true, subtree: true });
}
});
}
customElements.whenDefined("readthedocs-flyout").then(scan);
let discovery;
let stopDiscovery;
function ready(eventData) {
const config = eventData.data();
if (config.projects?.current?.slug !== "archivebox" || !Array.isArray(config.versions?.active)) return;
wanted = select(config.versions.active);
if (!wanted.length) return;
scan();
// Discover asynchronously inserted picker containers for at most 10 seconds.
// Afterwards, observers remain scoped to known picker/shadow containers only.
discovery?.disconnect();
clearTimeout(stopDiscovery);
discovery = new MutationObserver(scan);
discovery.observe(document.body, { childList: true, subtree: true });
stopDiscovery = setTimeout(() => discovery.disconnect(), 10000);
}
document.addEventListener("readthedocs-addons-data-ready", event => ready(event.detail));
if (globalThis.ReadTheDocsEventData) ready(globalThis.ReadTheDocsEventData);
})();
+15 -28
View File
@@ -25,36 +25,23 @@ jobs:
dh-python debhelper devscripts dput software-properties-common \
python3-distutils python3-setuptools python3-wheel python3-stdeb
- name: Build Debian/Apt sdist_dsc
run: |
rm -Rf deb_dist/*
python3 setup.py --command-packages=stdeb.command sdist_dsc
# - name: Build Debian/Apt sdist_dsc
# run: |
# ./bin/build_pip.sh
- name: Build Debian/Apt bdist_deb
run: |
python3 setup.py --command-packages=stdeb.command bdist_deb
# - name: Check ArchiveBox version
# run: |
# # must create dir needed for snaps to run as non-root on github actions
# sudo mkdir -p /run/user/1001 && sudo chmod -R 777 /run/user/1001
# mkdir "${{ github.workspace }}/data" && cd "${{ github.workspace }}/data"
# archivebox --version
# archivebox init --setup
- name: Install archivebox from deb
run: |
cd deb_dist/
sudo apt-get install ./archivebox*.deb
cd ..
python3 -c 'from distutils.core import run_setup; result = run_setup("./setup.py", stop_after="init"); print("\n".join(result.install_requires + result.extras_require["sonic"]))' > ./requirements.txt
python3 -m pip install -r ./requirements.txt
- name: Check ArchiveBox version
run: |
# must create dir needed for snaps to run as non-root on github actions
sudo mkdir -p /run/user/1001 && sudo chmod -R 777 /run/user/1001
mkdir "${{ github.workspace }}/data" && cd "${{ github.workspace }}/data"
archivebox --version
archivebox init --setup
- name: Add some links to test
run: |
cd "${{ github.workspace }}/data"
archivebox add 'https://example.com'
archivebox status
# - name: Add some links to test
# run: |
# cd "${{ github.workspace }}/data"
# archivebox add 'https://example.com'
# archivebox status
# - name: Commit built package
# run: |
+18 -7
View File
@@ -3,18 +3,20 @@ name: Build Docker image
on:
workflow_dispatch:
push:
branches:
- '**'
branches-ignore:
- docs
- 'docs/**'
tags:
- 'v*'
# pull_request:
env:
DOCKER_IMAGE: archivebox-ci
jobs:
buildx:
# Also prevent a manual dispatch on a docs repair branch from publishing.
if: github.ref != 'refs/heads/docs' && !startsWith(github.ref, 'refs/heads/docs/')
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -60,13 +62,12 @@ jobs:
uses: docker/metadata-action@v5
with:
images: archivebox/archivebox,nikisweeting/archivebox
flavor: |
latest=auto
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push
id: docker_build
@@ -78,8 +79,18 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
# This ugly bit is necessary if you don't want your cache to grow forever
# until it hits GitHub's limit of 5GB.
# Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
+34
View File
@@ -0,0 +1,34 @@
name: Historical Sphinx documentation
on:
pull_request:
branches: [main]
paths: ['.github/docs/**', '.github/.readthedocs.yaml', '.github/workflows/docs.yml', 'docs', 'README.md', 'package.json', 'archivebox/config.py']
push:
branches: [main]
paths: ['.github/docs/**', '.github/.readthedocs.yaml', '.github/workflows/docs.yml', 'docs', 'README.md', 'package.json', 'archivebox/config.py']
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- run: git submodule update --init docs
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
with:
version: '0.11.3'
python-version: '3.13'
- name: Build the pinned historical user guide
env:
READTHEDOCS: 'True'
READTHEDOCS_VERSION: latest
run: uv run --no-project --with-requirements .github/docs/requirements.txt sphinx-build -W --keep-going -b html -c .github/docs docs "$RUNNER_TEMP/docs-html"
- name: Verify the version selector
run: |
grep -q 'class="version-switch"' "$RUNNER_TEMP/docs-html/index.html"
grep -q 'readthedocs-addons-data-ready' "$RUNNER_TEMP/docs-html/_static/js/versions.js"
+58
View File
@@ -0,0 +1,58 @@
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Build GitHub Pages website
on:
# Runs on pushes targeting the default branch
push:
branches: ["dev"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
- name: Copy README.md into place
run: |
rm ./website/README.md
cp ./README.md ./website/README.md
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
with:
source: ./website
destination: ./_site
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
+12 -5
View File
@@ -19,19 +19,26 @@ jobs:
fetch-depth: 1
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.11
python-version: '3.11'
architecture: x64
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: ripgrep build-essential python3-dev python3-setuptools libssl-dev libldap2-dev libsasl2-dev zlib1g-dev libatomic1 gnupg2 curl wget python3-ldap python3-msgpack python3-mutagen python3-regex python3-pycryptodome procps
version: 1.0
- uses: pdm-project/setup-pdm@v3
with:
python-version: '3.11'
cache: true
- name: Install dependencies
run: pdm install --fail-fast --no-lock --group :all --no-self
- name: Build package
run: |
rm ./dist/archivebox-*.whl
pdm build
- name: Install from build
@@ -45,8 +52,8 @@ jobs:
archivebox version
archivebox status
- name: Publish package distributions to PyPI
run: pdm publish --no-build
#- name: Publish package distributions to PyPI
# run: pdm publish --no-build
# - name: Push build to PyPI
# run: |
+20 -11
View File
@@ -13,11 +13,12 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
python: [3.9]
os: [ubuntu-22.04]
# os: [ubuntu-22.04, macos-latest, windows-latest]
python: [3.11]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
@@ -29,13 +30,16 @@ jobs:
python-version: ${{ matrix.python }}
architecture: x64
- name: Set up Node JS 14.7.0
uses: actions/setup-node@v3
- name: Set up Node JS
uses: actions/setup-node@v4
with:
node-version: 18.12.0
node-version: 20.10.0
- name: Setup PDM
uses: pdm-project/setup-pdm@v3
with:
python-version: '3.11'
cache: true
### Install Python & JS Dependencies
- name: Get pip cache dir
@@ -44,7 +48,7 @@ jobs:
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v3
id: cache-pip
with:
path: ${{ steps.pip-cache.outputs.dir }}
@@ -52,11 +56,16 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ matrix.python }}-venv-
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: ripgrep build-essential python3-dev python3-setuptools libssl-dev libldap2-dev libsasl2-dev zlib1g-dev libatomic1 python3-minimal gnupg2 curl wget python3-ldap python3-msgpack python3-mutagen python3-regex python3-pycryptodome procps
version: 1.0
- name: Install pip dependencies
run: |
python -m pip install --upgrade pip setuptools wheel pytest bottle build
./bin/build_pip.sh
pdm install
python -m pip install -r requirements.txt
python -m pip install -e .[sonic,ldap]
- name: Get npm cache dir
id: npm-cache
@@ -64,7 +73,7 @@ jobs:
echo "::set-output name=dir::$GITHUB_WORKSPACE/node_modules"
- name: Cache npm
uses: actions/cache@v2
uses: actions/cache@v3
id: cache-npm
with:
path: ${{ steps.npm-cache.outputs.dir }}
@@ -99,7 +108,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
+34
View File
@@ -0,0 +1,34 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
submodules:
include: all
recursive: true
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
nodejs: "20"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Optionally build your docs in additional formats such as PDF and ePub
formats:
- pdf
- epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: requirements.txt
- requirements: docs/requirements.txt
+38 -25
View File
@@ -45,7 +45,10 @@ ENV TZ=UTC \
# Version config
ENV PYTHON_VERSION=3.11 \
NODE_VERSION=21
NODE_VERSION=24 \
PLAYWRIGHT_VERSION=1.59.0 \
YTDLP_VERSION=2026.3.17 \
GOSU_VERSION=1.19
# User config
ENV ARCHIVEBOX_USER="archivebox" \
@@ -73,7 +76,8 @@ COPY --chown=root:root --chmod=755 package.json "$CODE_DIR/"
RUN grep '"version": ' "${CODE_DIR}/package.json" | awk -F'"' '{print $4}' > /VERSION.txt
# Force apt to leave downloaded binaries in /var/cache/apt (massively speeds up Docker builds)
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
RUN echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
&& rm -f /etc/apt/apt.conf.d/docker-clean
# Print debug info about build and save it to disk, for human eyes only, not used by anything else
RUN (echo "[i] Docker build for ArchiveBox $(cat /VERSION.txt) starting..." \
@@ -112,10 +116,20 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
# 1. packaging dependencies
apt-transport-https ca-certificates apt-utils gnupg2 curl wget \
# 2. docker and init system dependencies
zlib1g-dev dumb-init gosu cron unzip grep \
zlib1g-dev dumb-init cron unzip grep \
# 3. frivolous CLI helpers to make debugging failed archiving easier
# nano iputils-ping dnsutils htop procps jq yq
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/* \
&& case "$TARGETARCH/$TARGETVARIANT" in \
amd64/) GOSU_ARCH=amd64; GOSU_SHA256=52c8749d0142edd234e9d6bd5237dff2d81e71f43537e2f4f66f75dd4b243dd0 ;; \
arm64/) GOSU_ARCH=arm64; GOSU_SHA256=3a8ef022d82c0bc4a98bcb144e77da714c25fcfa64dccc57f6aba7ae47ff1a44 ;; \
arm/v7) GOSU_ARCH=armhf; GOSU_SHA256=8457a0bfd28e016c2c7d8ea6e5f7eed1376033ffbd36491bb455094c8b1dc9fd ;; \
*) echo "Unsupported gosu platform: $TARGETARCH/$TARGETVARIANT"; exit 1 ;; \
esac \
&& curl -fsSL -o /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$GOSU_ARCH" \
&& echo "$GOSU_SHA256 /usr/local/bin/gosu" | sha256sum -c - \
&& chmod +x /usr/local/bin/gosu \
&& gosu --version | head -n1 | tee -a /VERSION.txt
######### Language Environments ####################################
@@ -123,7 +137,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$TARGETVARIANT --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-$TARGETARCH$TARGETVARIANT \
echo "[+] Installing Node $NODE_VERSION environment in $NODE_MODULES..." \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" >> /etc/apt/sources.list.d/nodejs.list \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& curl -fsSL "https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key" | gpg --dearmor | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& apt-get update -qq \
&& apt-get install -qq -y -t bookworm-backports --no-install-recommends \
nodejs libatomic1 python3-minimal \
@@ -163,7 +177,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
echo "[+] Installing APT extractor dependencies globally using apt..." \
&& apt-get update -qq \
&& apt-get install -qq -y -t bookworm-backports --no-install-recommends \
curl wget git yt-dlp ffmpeg ripgrep \
curl wget git ffmpeg ripgrep \
# Packages we have also needed in the past:
# youtube-dl wget2 aria2 python3-pyxattr rtmpdump libfribidi-bin mpv \
# fontconfig fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-symbola fonts-noto fonts-freefont-ttf \
@@ -171,10 +185,9 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
# Save version info
&& ( \
which curl && curl --version | head -n1 \
&& which wget && wget --version | head -n1 \
&& which yt-dlp && yt-dlp --version | head -n1 \
&& which git && git --version | head -n1 \
&& which rg && rg --version | head -n1 \
&& which wget && wget --version 2>&1 | head -n1 \
&& which git && git --version 2>&1 | head -n1 \
&& which rg && rg --version 2>&1 | head -n1 \
&& echo -e '\n\n' \
) | tee -a /VERSION.txt
@@ -184,7 +197,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
&& apt-get update -qq \
&& if [[ "$TARGETPLATFORM" == *amd64* || "$TARGETPLATFORM" == *arm64* ]]; then \
# install Chromium using playwright
pip install playwright \
pip install "playwright==$PLAYWRIGHT_VERSION" \
&& cp -r /root/.cache/ms-playwright "$PLAYWRIGHT_BROWSERS_PATH" \
&& playwright install --with-deps chromium \
&& export CHROME_BINARY="$(python -c 'from playwright.sync_api import sync_playwright; print(sync_playwright().start().chromium.executable_path)')"; \
@@ -198,16 +211,17 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
&& ln -s "$CHROME_BINARY" /usr/bin/chromium-browser \
&& mkdir -p "/home/${ARCHIVEBOX_USER}/.config/chromium/Crash Reports/pending/" \
&& chown -R $ARCHIVEBOX_USER "/home/${ARCHIVEBOX_USER}/.config" \
&& mkdir -p "$PLAYWRIGHT_BROWSERS_PATH" \
&& chown -R $ARCHIVEBOX_USER "$PLAYWRIGHT_BROWSERS_PATH" \
# Save version info
&& ( \
which chromium-browser && /usr/bin/chromium-browser --version \
which chromium-browser && /usr/bin/chromium-browser --version || /usr/lib/chromium/chromium --version \
&& echo -e '\n\n' \
) | tee -a /VERSION.txt
# Install Node dependencies
WORKDIR "$CODE_DIR"
COPY --chown=root:root --chmod=755 "package.json" "package-lock.json" "$CODE_DIR/"
COPY --chown=root:root --chmod=755 "package.json" "package-lock.json" "$CODE_DIR"/
RUN --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-$TARGETARCH$TARGETVARIANT \
echo "[+] Installing NPM extractor dependencies from package.json into $NODE_MODULES..." \
&& npm ci --prefer-offline --no-audit --cache /root/.npm \
@@ -221,9 +235,9 @@ RUN --mount=type=cache,target=/root/.npm,sharing=locked,id=npm-$TARGETARCH$TARGE
# Install ArchiveBox Python dependencies
WORKDIR "$CODE_DIR"
COPY --chown=root:root --chmod=755 "./pyproject.toml" "requirements.txt" "$CODE_DIR/"
COPY --chown=root:root --chmod=755 "./pyproject.toml" "requirements.txt" "$CODE_DIR"/
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$TARGETVARIANT --mount=type=cache,target=/root/.cache/pip,sharing=locked,id=pip-$TARGETARCH$TARGETVARIANT \
echo "[+] Installing PIP ArchiveBox dependencies from requirements.txt for ${TARGETPLATFORM}..." \
echo "[+] Installing PIP ArchiveBox dependencies from requirements.txt for ${TARGETPLATFORM}..." \
&& apt-get update -qq \
&& apt-get install -qq -y -t bookworm-backports --no-install-recommends \
build-essential \
@@ -236,9 +250,9 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
# && pdm export -o requirements.txt --without-hashes \
# && source $GLOBAL_VENV/bin/activate \
&& pip install -r requirements.txt \
&& pip install --upgrade "yt-dlp==$YTDLP_VERSION" \
&& apt-get purge -y \
build-essential \
# these are only needed to build CPython libs, we discard after build phase to shrink layer size
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
@@ -246,15 +260,15 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$T
COPY --chown=root:root --chmod=755 "." "$CODE_DIR/"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$TARGETVARIANT --mount=type=cache,target=/root/.cache/pip,sharing=locked,id=pip-$TARGETARCH$TARGETVARIANT \
echo "[*] Installing PIP ArchiveBox package from $CODE_DIR..." \
&& apt-get update -qq \
# && apt-get update -qq \
# install C compiler to build deps on platforms that dont have 32-bit wheels available on pypi
&& apt-get install -qq -y -t bookworm-backports --no-install-recommends \
build-essential \
# && apt-get install -qq -y -t bookworm-backports --no-install-recommends \
# build-essential \
# INSTALL ARCHIVEBOX python package globally from CODE_DIR, with all optional dependencies
&& pip install -e "$CODE_DIR"[sonic,ldap] \
# save docker image size and always remove compilers / build tools after building is complete
&& apt-get purge -y build-essential \
&& apt-get autoremove -y \
# && apt-get purge -y build-essential \
# && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
####################################################
@@ -276,11 +290,10 @@ ENV IN_DOCKER=True
# Print version for nice docker finish summary
RUN (echo -e "\n\n[√] Finished Docker build succesfully. Saving build summary in: /VERSION.txt" \
&& echo -e "PLATFORM=${TARGETPLATFORM} ARCH=$(uname -m) ($(uname -s) ${TARGETARCH} ${TARGETVARIANT})" \
&& echo -e "BUILD_END_TIME=$(date +"%Y-%m-%d %H:%M:%S %s") TZ=${TZ}\n\n" \
&& "$CODE_DIR/bin/docker_entrypoint.sh" \
archivebox version 2>&1 \
&& echo -e "PLATFORM=${TARGETPLATFORM} ARCH=$(uname -m) ($(uname -s) ${TARGETARCH} ${TARGETVARIANT})\n" \
&& echo -e "BUILD_END_TIME=$(date +"%Y-%m-%d %H:%M:%S %s")\n\n" \
) | tee -a /VERSION.txt
RUN "$CODE_DIR"/bin/docker_entrypoint.sh version 2>&1 | tee -a /VERSION.txt
####################################################
+325 -89
View File
@@ -1,45 +1,77 @@
<div align="center">
<em><img src="https://i.imgur.com/5B48E3N.png" height="90px"></em>
<em><img src="https://archivebox.io/icon.png" height="90px"></em>
<h1>ArchiveBox<br/><sub>Open-source self-hosted web archiving.</sub></h1>
<br/>
▶️ <a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart">Quickstart</a> |
<a href="https://demo.archivebox.io">Demo</a> |
<a href="https://github.com/ArchiveBox/ArchiveBox">GitHub</a> |
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki">Documentation</a> |
<a href="#background--motivation">Info & Motivation</a> |
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community">Community</a> |
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Roadmap">Roadmap</a>
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community">Community</a>
<pre lang="bash" align="center"><code style="white-space: pre-line; text-align: center" align="center">"Your own personal internet archive" (网站存档 / 爬虫)
curl -sSL 'https://get.archivebox.io' | sh
</code></pre>
<br/>
<!--<a href="http://webchat.freenode.net?channels=ArchiveBox&uio=d4"><img src="https://img.shields.io/badge/Community_chat-IRC-%2328A745.svg"/></a>-->
<a href="https://github.com/ArchiveBox/ArchiveBox/blob/dev/LICENSE"><img src="https://img.shields.io/badge/Open_source-MIT-green.svg?logo=git&logoColor=green"/></a>
<a href="https://github.com/ArchiveBox/ArchiveBox"><img src="https://img.shields.io/github/stars/ArchiveBox/ArchiveBox.svg?logo=github&label=Stars&logoColor=blue"/></a>
<a href="https://github.com/ArchiveBox/ArchiveBox/commits/dev"><img src="https://img.shields.io/github/last-commit/ArchiveBox/ArchiveBox.svg?logo=Sublime+Text&logoColor=green&label=active"/></a> &nbsp;
<a href="https://github.com/ArchiveBox/ArchiveBox/commits/dev"><img src="https://img.shields.io/github/last-commit/ArchiveBox/ArchiveBox.svg?logo=Sublime+Text&logoColor=green&label=Active"/></a> &nbsp;
<a href="https://pypi.org/project/archivebox/"><img src="https://img.shields.io/badge/Python-yellow.svg?logo=python&logoColor=yellow"/></a>
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Install#dependencies"><img src="https://img.shields.io/badge/Chromium-orange.svg?logo=Google+Chrome&logoColor=orange"/></a>
<a href="https://hub.docker.com/r/archivebox/archivebox"><img src="https://img.shields.io/badge/Docker-lightblue.svg?logo=docker&logoColor=lightblue"/></a>
<!--<pre lang="bash" align="left"><code style="white-space: pre-line; text-align: left" align="left">
curl -sSL 'https://get.archivebox.io' | sh # (or see pip/brew/Docker instructions below)
</code></pre>-->
<hr/>
</div>
**ArchiveBox is a powerful, self-hosted internet archiving solution to collect, save, and view sites you want to preserve offline.**
**ArchiveBox is a powerful, self-hosted internet archiving solution to collect, save, and view websites offline.**
You can set it up as a [command-line tool](#quickstart), [web app](#quickstart), and [desktop app](https://github.com/ArchiveBox/electron-archivebox) (alpha), on Linux, macOS, and Windows (WSL/Docker).
Without active preservation effort, everything on the internet eventually dissapears or degrades. Archive.org does a great job as a free central archive, but they require all archives to be public, and they can't save every type of content.
**You can feed it URLs one at a time, or schedule regular imports** from browser bookmarks or history, feeds like RSS, bookmark services like Pocket/Pinboard, and more. See <a href="#input-formats">input formats</a> for a full list.
*ArchiveBox is an open source tool that helps you archive web content on your own (or privately within an organization): save copies of browser bookmarks, preserve evidence for legal cases, backup photos from FB / Insta / Flickr, download your media from YT / Soundcloud / etc., snapshot research papers & academic citations, and more...*
**It saves snapshots of the URLs you feed it in several formats:** HTML, PDF, PNG screenshots, WARC, and more out-of-the-box, with a wide variety of content extracted and preserved automatically (article text, audio/video, git repos, etc.). See <a href="#output-formats">output formats</a> for a full list.
> ➡️ *Use ArchiveBox as a [command-line package](#quickstart) and/or [self-hosted web app](#quickstart) on Linux, macOS, or in [Docker](#quickstart).*
<hr/>
📥 **You can feed ArchiveBox URLs one at a time, or schedule regular imports** from browser bookmarks or history, feeds like RSS, bookmark services like Pocket/Pinboard, and more. See <a href="#input-formats">input formats</a> for a full list.
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/90f1ce3c-75bb-401d-88ed-6297694b76ae" alt="snapshot detail page" align="right" width="190px"/>
💾 **It saves snapshots of the URLs you feed it in several redundant formats.**
It also detects any content featured *inside* each webpage & extracts it out into a folder:
- `HTML/Generic websites -> HTML, PDF, PNG, WARC, Singlefile`
- `YouTube/SoundCloud/etc. -> MP3/MP4 + subtitles, description, thumbnail`
- `News articles -> article body TXT + title, author, featured images`
- `Github/Gitlab/etc. links -> git cloned source code`
- *[and more...](#output-formats)*
It uses normal filesystem folders to organize archives (no complicated proprietary formats), and offers a CLI + web UI.
---
🏛️ ArchiveBox is used by many *[professionals](https://zulip.archivebox.io/#narrow/stream/167-enterprise/topic/welcome/near/1191102) and [hobbyists](https://zulip.archivebox.io/#narrow/stream/158-development)* who save content off the web, for example:
- **Individuals:**
`backing up browser bookmarks/history`, `saving FB/Insta/etc. content`, `shopping lists`
- **Journalists:**
`crawling and collecting research`, `preserving quoted material`, `fact-checking and review`
- **Lawyers:**
`evidence collection`, `hashing & integrity verifying`, `search, tagging, & review`
- **Researchers:**
`collecting AI training sets`, `feeding analysis / web crawling pipelines`
The goal is to sleep soundly knowing the part of the internet you care about will be automatically preserved in durable, easily accessible formats [for decades](#background--motivation) after it goes down.
<div align="center">
<br/><br/>
<img src="https://i.imgur.com/PAzXZE8.png" height="70px" alt="bookshelf graphic"> &nbsp; <img src="https://i.imgur.com/asPNk8n.png" height="75px" alt="logo" align="top"/> &nbsp; <img src="https://i.imgur.com/PAzXZE8.png" height="70px" alt="bookshelf graphic">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/5a7d95f2-6977-4de6-9f08-42851a1fe1d2" height="70px" alt="bookshelf graphic"> &nbsp; <img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/b2765a33-0d1e-4019-a1db-920c7e00e20e" height="75px" alt="logo" align="top"/> &nbsp; <img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/5a7d95f2-6977-4de6-9f08-42851a1fe1d2" height="70px" alt="bookshelf graphic">
<br/><br/>
<small><a href="https://demo.archivebox.io">Demo</a> | <a href="#screenshots">Screenshots</a> | <a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Usage">Usage</a></small>
<br/>
@@ -77,10 +109,10 @@ ls ./archive/*/index.json # or browse directly via the filesyste
<div align="center">
<br/><br/>
<img src="https://i.imgur.com/njxgSbl.png" width="22%" alt="cli init screenshot" align="top">
<img src="https://i.imgur.com/lUuicew.png" width="22%" alt="cli init screenshot" align="top">
<img src="https://i.imgur.com/p6wK6KM.png" width="22%" alt="server snapshot admin screenshot" align="top">
<img src="https://i.imgur.com/xHvQfon.png" width="28.6%" alt="server snapshot details page screenshot" align="top"/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/8d67382c-e0ce-4286-89f7-7915f09b930c" width="22%" alt="cli init screenshot" align="top">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/dad2bc51-e7e5-484e-bb26-f956ed692d16" width="22%" alt="cli init screenshot" align="top">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/e8e0b6f8-8fdf-4b7f-8124-c10d8699bdb2" width="22%" alt="server snapshot admin screenshot" align="top">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ace0954a-ddac-4520-9d18-1c77b1ec50b2" width="28.6%" alt="server snapshot details page screenshot" align="top"/>
<br/><br/>
</div>
@@ -89,25 +121,37 @@ ls ./archive/*/index.json # or browse directly via the filesyste
- [**Free & open source**](https://github.com/ArchiveBox/ArchiveBox/blob/dev/LICENSE), doesn't require signing up online, stores all data locally
- [**Powerful, intuitive command line interface**](https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#CLI-Usage) with [modular optional dependencies](#dependencies)
- [**Comprehensive documentation**](https://github.com/ArchiveBox/ArchiveBox/wiki), [active development](https://github.com/ArchiveBox/ArchiveBox/wiki/Roadmap), and [rich community](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community)
- [**Extracts a wide variety of content out-of-the-box**](https://github.com/ArchiveBox/ArchiveBox/issues/51): [media (youtube-dl or yt-dlp), articles (readability), code (git), etc.](#output-formats)
- [**Extracts a wide variety of content out-of-the-box**](https://github.com/ArchiveBox/ArchiveBox/issues/51): [media (yt-dlp), articles (readability), code (git), etc.](#output-formats)
- [**Supports scheduled/realtime importing**](https://github.com/ArchiveBox/ArchiveBox/wiki/Scheduled-Archiving) from [many types of sources](#input-formats)
- [**Uses standard, durable, long-term formats**](#saves-lots-of-useful-stuff-for-each-imported-link) like HTML, JSON, PDF, PNG, and WARC
- [**Uses standard, durable, long-term formats**](#saves-lots-of-useful-stuff-for-each-imported-link) like HTML, JSON, PDF, PNG, MP4, TXT, and WARC
- [**Usable as a oneshot CLI**](https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#CLI-Usage), [**self-hosted web UI**](https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#UI-Usage), [Python API](https://docs.archivebox.io/en/latest/modules.html) (BETA), [REST API](https://github.com/ArchiveBox/ArchiveBox/issues/496) (ALPHA), or [desktop app](https://github.com/ArchiveBox/electron-archivebox) (ALPHA)
- [**Saves all pages to archive.org as well**](https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#save_archive_dot_org) by default for redundancy (can be [disabled](https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#stealth-mode) for local-only mode)
- Advanced users: support for archiving [content requiring login/paywall/cookies](https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#chrome_user_data_dir) (see wiki security caveats!)
- Planned: support for running [JS during archiving](https://github.com/ArchiveBox/ArchiveBox/issues/51) to adblock, [autoscroll](https://github.com/ArchiveBox/ArchiveBox/issues/80), [modal-hide](https://github.com/ArchiveBox/ArchiveBox/issues/175), [thread-expand](https://github.com/ArchiveBox/ArchiveBox/issues/345)...
- Planned: support for running [JS during archiving](https://github.com/ArchiveBox/ArchiveBox/issues/51) to adblock, [autoscroll](https://github.com/ArchiveBox/ArchiveBox/issues/80), [modal-hide](https://github.com/ArchiveBox/ArchiveBox/issues/175), [thread-expand](https://github.com/ArchiveBox/ArchiveBox/issues/345)
<br/><br/>
<br/>
## 🤝 Professional Integration
*[Contact us](https://zulip.archivebox.io/#narrow/stream/167-enterprise/topic/welcome/near/1191102) if your non-profit institution/org wants to use ArchiveBox professionally.*
- setup & support, team permissioning, hashing, audit logging, backups, custom archiving etc.
- for **individuals**, **NGOs**, **academia**, **governments**, **journalism**, **law**, and more...
*All our work is open-source and primarily geared towards non-profits.*
*Support/consulting pays for hosting and funds new ArchiveBox open-source development.*
<br/>
<div align="center">
<br/>
<img src="https://i.imgur.com/T2UAGUD.png" width="49%" alt="grass"/><img src="https://i.imgur.com/T2UAGUD.png" width="49%" alt="grass"/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/0db52ea7-4a2c-441d-b47f-5553a5d8fe96" width="49%" alt="grass"/><img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/0db52ea7-4a2c-441d-b47f-5553a5d8fe96" width="49%" alt="grass"/>
</div>
# Quickstart
**🖥&nbsp; Supported OSs:** Linux/BSD, macOS, Windows (Docker/WSL) &nbsp; **👾&nbsp; CPUs:** amd64, x86, arm8, arm7 <sup>(raspi>=3)</sup>
<i>Note: On arm7, the `playwright` package, provides easy `chromium` management, is not yet available. Do it manually with alternative methods.</i>
**🖥&nbsp; Supported OSs:** Linux/BSD, macOS, Windows (Docker) &nbsp; **👾&nbsp; CPUs:** `amd64` (`x86_64`), `arm64` (`arm8`), `arm7` <sup>(raspi>=3)</sup><br/>
<sub>Note: On `arm7` the `playwright` package is not available, so `chromium` must be installed manually if needed.</sub>
<br/>
@@ -116,7 +160,7 @@ ls ./archive/*/index.json # or browse directly via the filesyste
<details>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447182-29758200-af0b-11eb-97bd-58723fee62ab.png" alt="Docker" height="28px" align="top"/> <code>docker-compose</code></b> (macOS/Linux/Windows) &nbsp; <b>👈&nbsp; recommended</b> &nbsp; <i>(click to expand)</i></summary>
<br/>
<i>👍 Docker Compose is recommended for the easiest install/update UX + best security + all the <a href="#dependencies">extras</a> working out-of-the-box.</i>
<i>👍 Docker Compose is recommended for the easiest install/update UX + best security + all the <a href="#dependencies">extras</a> out-of-the-box.</i>
<br/><br/>
<ol>
<li>Install <a href="https://docs.docker.com/get-docker/">Docker</a> and <a href="https://docs.docker.com/compose/install/#install-using-pip">Docker Compose</a> on your system (if not already installed).</li>
@@ -139,7 +183,7 @@ See <a href="#%EF%B8%8F-cli-usage">below</a> for more usage examples using the C
</details>
<details>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447182-29758200-af0b-11eb-97bd-58723fee62ab.png" alt="Docker" height="28px" align="top"/> <code>docker</code></b> (macOS/Linux/Windows)</summary>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447182-29758200-af0b-11eb-97bd-58723fee62ab.png" alt="Docker" height="28px" align="top"/> <code>docker run</code></b> (macOS/Linux/Windows)</summary>
<br/>
<ol>
<li>Install <a href="https://docs.docker.com/get-docker/">Docker</a> on your system (if not already installed).</li>
@@ -186,12 +230,7 @@ See <a href="https://docs.sweeting.me/s/against-curl-sh">"Against curl | sh as a
<br/>
<ol>
<li>Add the ArchiveBox repository to your sources.<br/>
<pre lang="bash"><code style="white-space: pre-line"># On Ubuntu == 20.04, add the sources automatically:
sudo apt install software-properties-common
sudo add-apt-repository -u ppa:archivebox/archivebox
</code></pre>
<pre lang="bash"><code style="white-space: pre-line"># On Ubuntu >= 20.10 or <= 19.10, or other Debian-style systems, add the sources manually:
echo "deb http://ppa.launchpad.net/archivebox/archivebox/ubuntu focal main" | sudo tee /etc/apt/sources.list.d/archivebox.list
<pre lang="bash"><code style="white-space: pre-line">echo "deb http://ppa.launchpad.net/archivebox/archivebox/ubuntu focal main" | sudo tee /etc/apt/sources.list.d/archivebox.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C258F79DCC02E369
sudo apt update
</code></pre>
@@ -249,11 +288,11 @@ See the <a href="https://github.com/ArchiveBox/homebrew-archivebox"><code>homebr
</details>
<details>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447613-ba4c5d80-af0b-11eb-8f89-1d98e31b6a79.png" alt="Pip" height="28px" align="top"/> <code>pip</code></b> (macOS/Linux/Windows)</summary>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447613-ba4c5d80-af0b-11eb-8f89-1d98e31b6a79.png" alt="Pip" height="28px" align="top"/> <code>pip</code></b> (macOS/Linux/BSD)</summary>
<br/>
<ol>
<li>Install <a href="https://realpython.com/installing-python/">Python >= v3.7</a> and <a href="https://nodejs.org/en/download/package-manager/">Node >= v14</a> on your system (if not already installed).</li>
<li>Install <a href="https://realpython.com/installing-python/">Python >= v3.9</a> and <a href="https://nodejs.org/en/download/package-manager/">Node >= v18</a> on your system (if not already installed).</li>
<li>Install the ArchiveBox package using <code>pip3</code>.
<pre lang="bash"><code style="white-space: pre-line">pip3 install archivebox
</code></pre>
@@ -280,6 +319,10 @@ See the <a href="https://github.com/ArchiveBox/pip-archivebox"><code>pip-archive
<details>
<summary><img src="https://user-images.githubusercontent.com/511499/118077361-f0616580-b381-11eb-973c-ee894a3349fb.png" alt="Arch" height="28px" align="top"/> <code>pacman</code> / <img src="https://user-images.githubusercontent.com/511499/118077946-29e6a080-b383-11eb-94f0-d4871da08c3f.png" alt="FreeBSD" height="28px" align="top"/> <code>pkg</code> / <img src="https://user-images.githubusercontent.com/511499/118077861-002d7980-b383-11eb-86a7-5936fad9190f.png" alt="Nix" height="28px" align="top"/> <code>nix</code> (Arch/FreeBSD/NixOS/more)</summary>
<br/>
> [!WARNING]
> *These are contributed by external volunteers and may lag behind the official `pip` channel.*
<ul>
<li>Arch: <a href="https://aur.archlinux.org/packages/archivebox/"><code>yay -S archivebox</code></a> (contributed by <a href="https://github.com/imlonghao"><code>@imlonghao</code></a>)</li>
<li>FreeBSD: <a href="https://github.com/ArchiveBox/ArchiveBox#%EF%B8%8F-easy-setup"><code>curl -sSL 'https://get.archivebox.io' | sh</code></a> (uses <code>pkg</code> + <code>pip3</code> under-the-hood)</li>
@@ -307,7 +350,7 @@ See <a href="#%EF%B8%8F-cli-usage">below</a> for usage examples using the CLI, W
</ul>
</li>
</ol>
<img src="https://i.imgur.com/QPHUS5C.png" width="320px">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/575ef92f-bb3e-4a7c-a4ba-986c1fd76ecf" width="320px">
<br/>
<i>✨ Alpha (contributors wanted!)</i>: for more info, see the: <a href="https://github.com/ArchiveBox/electron-archivebox">Electron ArchiveBox</a> repo.
<br/>
@@ -317,13 +360,16 @@ See <a href="#%EF%B8%8F-cli-usage">below</a> for usage examples using the CLI, W
<summary><img src="https://user-images.githubusercontent.com/511499/117448723-1663b180-af0d-11eb-837f-d43959227810.png" alt="paid" height="27px" align="top"/> Paid hosting solutions (cloud VPS)</summary>
<br/>
<ul>
<li><a href="https://zulip.archivebox.io/#narrow/stream/167-enterprise/topic/welcome/near/1191102">
<img src="https://img.shields.io/badge/Custom_Development-ArchiveBox.io-%231a1a1a.svg?style=flat" height="22px"/>
</a> (<a href="https://zulip.archivebox.io/#narrow/stream/167-enterprise/topic/welcome/near/1191102">get hosting, support, and feature customization directy from us</a>)</li>
<li><a href="https://monadical.com">
<img src="https://img.shields.io/badge/Custom_Managed_Solution-Monadical.com-%231a1a1a.svg?style=flat" height="22px"/>
</a> (<a href="https://monadical.com/contact-us.html">for larger setups, get a quote</a>)</li>
<img src="https://img.shields.io/badge/General_Dev_Consulting-Monadical.com-%231a1a1a.svg?style=flat" height="22px"/>
</a> (<a href="https://monadical.com/contact-us.html">for a generalist software consultancy that helps with ArchiveBox maintainance</a>)</li>
<br/>
None of these hosting providers are officially endorsed:<br/>
<sub><i>(most still require manual setup or manual periodic updating using the methods above)</i></sub>
Other providers of paid ArchiveBox hosting (not officially endorsed):<br/>
<br/><br/>
<li><a href="https://elest.io/open-source/archivebox"><img src="https://img.shields.io/badge/Managed_Hosting-Elest.io-%23193f7e.svg?style=flat" height="22px"/></a></li>
<li><a href="https://www.stellarhosted.com/archivebox/"><img src="https://img.shields.io/badge/Semi_Managed_Hosting-StellarHosted.com-%23193f7e.svg?style=flat" height="22px"/></a> (USD $29-250/mo, <a href="https://www.stellarhosted.com/archivebox/#pricing">pricing</a>)</li>
<li><a href="https://www.pikapods.com/pods?run=archivebox"><img src="https://img.shields.io/badge/Semi_Managed_Hosting-PikaPods.com-%2343a047.svg?style=flat" height="22px"/></a> (from USD $2.6/mo)</li>
<li><a href="https://m.do.co/c/cbc4c0c17840">
@@ -396,7 +442,7 @@ ls ./archive/*/index.html # or inspect snapshots on the filesystem
<br/>
<div align="center">
<img src="https://i.imgur.com/6AmOGJT.png" width="49%" alt="grass"/><img src="https://i.imgur.com/6AmOGJT.png" width="49%" alt="grass"/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/65f82532-18dd-49c5-86f1-02b1f3100e1e" width="49%" alt="grass"/><img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/65f82532-18dd-49c5-86f1-02b1f3100e1e" width="49%" alt="grass"/>
</div>
<br/>
@@ -413,7 +459,7 @@ ls ./archive/*/index.html # or inspect snapshots on the filesystem
---
<div align="center">
<img src="https://i.imgur.com/OUmgdlH.png" width="96%" alt="lego">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ac1f897a-8baa-4f8b-8ee8-7443611f258b" width="96%" alt="lego">
</div>
<br/>
@@ -427,12 +473,12 @@ ArchiveBox supports many input formats for URLs, including Pocket & Pinboard exp
*Click these links for instructions on how to prepare your links from these sources:*
- <img src="https://nicksweeting.com/images/rss.svg" height="22px"/> TXT, RSS, XML, JSON, CSV, SQL, HTML, Markdown, or [any other text-based format...](https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#Import-a-list-of-URLs-from-a-text-file)
- <img src="https://nicksweeting.com/images/bookmarks.png" height="22px"/> [Browser history](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive) or [browser bookmarks](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive) (see instructions for: [Chrome](https://support.google.com/chrome/answer/96816?hl=en), [Firefox](https://support.mozilla.org/en-US/kb/export-firefox-bookmarks-to-backup-or-transfer), [Safari](http://i.imgur.com/AtcvUZA.png), [IE](https://support.microsoft.com/en-us/help/211089/how-to-import-and-export-the-internet-explorer-favorites-folder-to-a-32-bit-version-of-windows), [Opera](http://help.opera.com/Windows/12.10/en/importexport.html), [and more...](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive))
- <img src="https://i.imgur.com/AQyHbu8.png" height="22px"/> Browser extension [`archivebox-exporter`](https://github.com/tjhorner/archivebox-exporter) (realtime archiving from Chrome/Chromium/Firefox)
- <img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/64078483-21d7-4eb1-aa6e-9ad55afe45b8" height="22px"/> TXT, RSS, XML, JSON, CSV, SQL, HTML, Markdown, or [any other text-based format...](https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#Import-a-list-of-URLs-from-a-text-file)
- <img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/32b494e6-4de1-4984-8d88-dc02f18e5c34" height="22px"/> [Browser history](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive) or [browser bookmarks](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive) (see instructions for: [Chrome](https://support.google.com/chrome/answer/96816?hl=en), [Firefox](https://support.mozilla.org/en-US/kb/export-firefox-bookmarks-to-backup-or-transfer), [Safari](https://github.com/ArchiveBox/ArchiveBox/assets/511499/24ad068e-0fa6-41f4-a7ff-4c26fc91f71a), [IE](https://support.microsoft.com/en-us/help/211089/how-to-import-and-export-the-internet-explorer-favorites-folder-to-a-32-bit-version-of-windows), [Opera](https://help.opera.com/en/latest/features/#bookmarks:~:text=Click%20the%20import/-,export%20button,-on%20the%20bottom), [and more...](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive))
- <img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ff20d251-5347-4b85-ae9b-83037d0ac01e" height="22px"/> Browser extension [`archivebox-exporter`](https://github.com/tjhorner/archivebox-exporter) (realtime archiving from Chrome/Chromium/Firefox)
- <img src="https://getpocket.com/favicon.ico" height="22px"/> [Pocket](https://getpocket.com/export), [Pinboard](https://pinboard.in/export/), [Instapaper](https://www.instapaper.com/user), [Shaarli](https://shaarli.readthedocs.io/en/master/Usage/#importexport), [Delicious](https://www.groovypost.com/howto/howto/export-delicious-bookmarks-xml/), [Reddit Saved](https://github.com/csu/export-saved-reddit), [Wallabag](https://doc.wallabag.org/en/user/import/wallabagv2.html), [Unmark.it](http://help.unmark.it/import-export), [OneTab](https://www.addictivetips.com/web/onetab-save-close-all-chrome-tabs-to-restore-export-or-import/), [and more...](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive)
<img src="https://i.imgur.com/zM4z1aU.png" width="330px" align="right">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/e1e5bd78-b0b6-45dc-914c-e1046fee4bc4" width="330px" align="right">
```bash
@@ -459,7 +505,7 @@ It also includes a built-in scheduled import feature with `archivebox schedule`
Inside each Snapshot folder, ArchiveBox saves these different types of extractor outputs as plain files:
<img src="https://i.imgur.com/xHvQfon.png" width="330px" align="right">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ace0954a-ddac-4520-9d18-1c77b1ec50b2" width="330px" align="right">
`./archive/<timestamp>/*`
@@ -483,7 +529,7 @@ It does everything out-of-the-box by default, but you can disable or tweak [indi
## Configuration
<img src="https://i.imgur.com/H08eaia.png" width="330px" align="right">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ea672e6b-4df5-49d8-b550-7f450951fd27" width="330px" align="right">
ArchiveBox can be configured via environment variables, by using the `archivebox config` CLI, or by editing the `ArchiveBox.conf` config file directly.
@@ -517,17 +563,29 @@ MAX_MEDIA_SIZE=1500m # default: 750m raise/lower youtubedl output size
PUBLIC_INDEX=True # default: True whether anon users can view index
PUBLIC_SNAPSHOTS=True # default: True whether anon users can view pages
PUBLIC_ADD_VIEW=False # default: False whether anon users can add new URLs
CHROME_USER_AGENT="Mozilla/5.0 ..." # change these to get around bot blocking
WGET_USER_AGENT="Mozilla/5.0 ..."
CURL_USER_AGENT="Mozilla/5.0 ..."
```
<br/>
## Dependencies
To achieve high-fidelity archives in as many situations as possible, ArchiveBox depends on a variety of high-quality 3rd-party tools and libraries that specialize in extracting different types of content.
<br/>
<details>
<summary><i>Expand to learn more about ArchiveBox's dependencies...</i></summary>
<br/>
For better security, easier updating, and to avoid polluting your host system with extra dependencies, **it is strongly recommended to use the official [Docker image](https://github.com/ArchiveBox/ArchiveBox/wiki/Docker)** with everything pre-installed for the best experience.
To achieve high-fidelity archives in as many situations as possible, ArchiveBox depends on a variety of 3rd-party tools and libraries that specialize in extracting different types of content. These optional dependencies used for archiving sites include:
These optional dependencies used for archiving sites include:
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/62a02155-05d7-4f3e-8de5-75a50a145c4f" alt="archivebox --version CLI output screenshot showing dependencies installed" width="330px" align="right">
<img src="https://i.imgur.com/5vSBO2R.png" width="330px" align="right">
- `chromium` / `chrome` (for screenshots, PDF, DOM HTML, and headless JS scripts)
- `node` & `npm` (for readability, mercury, and singlefile)
@@ -551,21 +609,41 @@ archivebox --version # see info and check validity of installed dependencies
Installing directly on **Windows without Docker or WSL/WSL2/Cygwin is not officially supported** (I cannot respond to Windows support tickets), but some advanced users have reported getting it working.
For detailed information about upgrading ArchiveBox and its dependencies, see: https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives
#### Learn More
- https://github.com/ArchiveBox/ArchiveBox/wiki/Install#dependencies
- https://github.com/ArchiveBox/ArchiveBox/wiki/Chromium-Install
- https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives
- https://github.com/ArchiveBox/ArchiveBox/wiki/Troubleshooting#installing
</details>
<br/>
## Archive Layout
All of ArchiveBox's state (including the index, snapshot data, and config file) is stored in a single folder called the "ArchiveBox data folder". All `archivebox` CLI commands must be run from inside this folder, and you first create it by running `archivebox init`.
All of ArchiveBox's state (including the SQLite DB, archived assets, config, logs, etc.) is stored in a single folder called the "ArchiveBox Data Folder".
Data folders can be created anywhere (`~/archivebox` or `$PWD/data` as seen in our examples), and you can create more than one for different collections.
The on-disk layout is optimized to be easy to browse by hand and durable long-term. The main index is a standard `index.sqlite3` database in the root of the data folder (it can also be exported as static JSON/HTML), and the archive snapshots are organized by date-added timestamp in the `./archive/` subfolder.
<br/>
<details>
<summary><i>Expand to learn more about the layout of Archivebox's data on-disk...</i></summary>
<br/>
All `archivebox` CLI commands are designed to be run from inside an ArchiveBox data folder, starting with `archivebox init` to initialize a new collection inside an empty directory.
```bash
mkdir ~/archivebox && cd ~/archivebox # just an example, can be anywhere
archivebox init
```
The on-disk layout is optimized to be easy to browse by hand and durable long-term. The main index is a standard `index.sqlite3` database in the root of the data folder (it can also be [exported as static JSON/HTML](https://github.com/ArchiveBox/ArchiveBox/wiki/Publishing-Your-Archive#2-export-and-host-it-as-static-html)), and the archive snapshots are organized by date-added timestamp in the `./archive/` subfolder.
<img src="https://user-images.githubusercontent.com/511499/117453293-c7b91600-af12-11eb-8a3f-aa48b0f9da3c.png" width="400px" align="right">
```bash
./
/data/
index.sqlite3
ArchiveBox.conf
archive/
@@ -582,12 +660,26 @@ The on-disk layout is optimized to be easy to browse by hand and durable long-te
Each snapshot subfolder `./archive/<timestamp>/` includes a static `index.json` and `index.html` describing its contents, and the snapshot extractor outputs are plain files within the folder.
#### Learn More
- https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#Disk-Layout
- https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#large-archives
- https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#output-folder
- https://github.com/ArchiveBox/ArchiveBox/wiki/Publishing-Your-Archive
- https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives
</details>
<br/>
## Static Archive Exporting
You can export the main index to browse it statically without needing to run a server.
You can export the main index to browse it statically as plain HTML files in a folder (without needing to run a server).
<br/>
<details>
<summary><i>Expand to learn how to export your ArchiveBox collection...</i></summary>
<br/>
> **Note**
> These exports are not paginated, exporting many URLs or the entire archive at once may be slow. Use the filtering CLI flags on the `archivebox list` command to export specific Snapshots or ranges.
@@ -604,6 +696,14 @@ archivebox list --csv=timestamp,url,title > index.csv # export to csv spreadshe
The paths in the static exports are relative, make sure to keep them next to your `./archive` folder when backing them up or viewing them.
#### Learn More
- https://github.com/ArchiveBox/ArchiveBox/wiki/Publishing-Your-Archive#2-export-and-host-it-as-static-html
- https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#publishing
- https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#public_index--public_snapshots--public_add_view
</details>
<br/>
@@ -621,6 +721,11 @@ The paths in the static exports are relative, make sure to keep them next to you
If you're importing pages with private content or URLs containing secret tokens you don't want public (e.g Google Docs, paywalled content, unlisted videos, etc.), **you may want to disable some of the extractor methods to avoid leaking that content to 3rd party APIs or the public**.
<br/>
<details>
<summary><i>Click to expand...</i></summary>
```bash
# don't save private content to ArchiveBox, e.g.:
archivebox add 'https://docs.google.com/document/d/12345somePrivateDocument'
@@ -639,10 +744,27 @@ archivebox config --set SAVE_FAVICON=False # disable favicon fetching (
archivebox config --set CHROME_BINARY=chromium # ensure it's using Chromium instead of Chrome
```
#### Learn More
- https://github.com/ArchiveBox/ArchiveBox/wiki/Publishing-Your-Archive
- https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview
- https://github.com/ArchiveBox/ArchiveBox/wiki/Chromium-Install#setting-up-a-chromium-user-profile
- https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#chrome_user_data_dir
- https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#cookies_file
</details>
<br/>
### Security Risks of Viewing Archived JS
Be aware that malicious archived JS can access the contents of other pages in your archive when viewed. Because the Web UI serves all viewed snapshots from a single domain, they share a request context and **typical CSRF/CORS/XSS/CSP protections do not work to prevent cross-site request attacks**. See the [Security Overview](https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#stealth-mode) page and [Issue #239](https://github.com/ArchiveBox/ArchiveBox/issues/239) for more details.
<br/>
<details>
<summary><i>Click to expand...</i></summary>
```bash
# visiting an archived page with malicious JS:
https://127.0.0.1:8000/archive/1602401954/example.com/index.html
@@ -657,9 +779,48 @@ The admin UI is also served from the same origin as replayed JS, so malicious pa
*Note: Only the `wget` & `dom` extractor methods execute archived JS when viewing snapshots, all other archive methods produce static output that does not execute JS on viewing. If you are worried about these issues ^ you should disable these extractors using `archivebox config --set SAVE_WGET=False SAVE_DOM=False`.*
#### Learn More
- https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview
- https://github.com/ArchiveBox/ArchiveBox/issues/239
- https://github.com/ArchiveBox/ArchiveBox/security/advisories/GHSA-cr45-98w9-gwqx (`CVE-2023-45815`)
- https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#publishing
</details>
<br/>
### Working Around Sites that Block Archiving
For various reasons, many large sites (Reddit, Twitter, Cloudflare, etc.) actively block archiving or bots in general. There are a number of approaches to work around this.
<br/>
<details>
<summary><i>Click to expand...</i></summary>
<br/>
- Set [`CHROME_USER_AGENT`, `WGET_USER_AGENT`, `CURL_USER_AGENT`](https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#curl_user_agent) to impersonate a real browser (instead of an ArchiveBox bot)
- Set up a logged-in browser session for archiving using [`CHROME_DATA_DIR` & `COOKIES_FILE`](https://github.com/ArchiveBox/ArchiveBox/wiki/Chromium-Install#setting-up-a-chromium-user-profile)
- Rewrite your URLs before archiving to swap in an alternative frontend thats more bot-friendly e.g.
`reddit.com/some/url` -> `teddit.net/some/url`: https://github.com/mendel5/alternative-front-ends
In the future we plan on adding support for running JS scripts during archiving to block ads, cookie popups, modals, and fix other issues. Follow here for progress: [Issue #51](https://github.com/ArchiveBox/ArchiveBox/issues/51).
</details>
<br/>
### Saving Multiple Snapshots of a Single URL
First-class support for saving multiple snapshots of each site over time will be [added eventually](https://github.com/ArchiveBox/ArchiveBox/issues/179) (along with the ability to view diffs of the changes between runs). For now **ArchiveBox is designed to only archive each unique URL with each extractor type once**. The workaround to take multiple snapshots of the same URL is to make them slightly different by adding a hash:
ArchiveBox appends a hash with the current date `https://example.com#2020-10-24` to differentiate when a single URL is archived multiple times.
<br/>
<details>
<summary><i>Click to expand...</i></summary>
<br/>
Because ArchiveBox uniquely identifies snapshots by URL, it must use a workaround to take multiple snapshots of the same URL (otherwise they would show up as a single Snapshot entry). It makes the URLs of repeated snapshots unique by adding a hash with the archive date at the end:
```bash
archivebox add 'https://example.com#2020-10-24'
@@ -667,14 +828,47 @@ archivebox add 'https://example.com#2020-10-24'
archivebox add 'https://example.com#2020-10-25'
```
The <img src="https://user-images.githubusercontent.com/511499/115942091-73c02300-a476-11eb-958e-5c1fc04da488.png" alt="Re-Snapshot Button" height="24px"/> button in the Admin UI is a shortcut for this hash-date workaround.
The <img src="https://user-images.githubusercontent.com/511499/115942091-73c02300-a476-11eb-958e-5c1fc04da488.png" alt="Re-Snapshot Button" height="24px"/> button in the Admin UI is a shortcut for this hash-date multi-snapshotting workaround.
Improved support for saving multiple snapshots of a single URL without this hash-date workaround will be [added eventually](https://github.com/ArchiveBox/ArchiveBox/issues/179) (along with the ability to view diffs of the changes between runs).
#### Learn More
- https://github.com/ArchiveBox/ArchiveBox/issues/179
- https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#explanation-of-buttons-in-the-web-ui---admin-snapshots-list
</details>
<br/>
### Storage Requirements
Because ArchiveBox is designed to ingest a firehose of browser history and bookmark feeds to a local disk, it can be much more disk-space intensive than a centralized service like the Internet Archive or Archive.today. **ArchiveBox can use anywhere from ~1gb per 1000 articles, to ~50gb per 1000 articles**, mostly dependent on whether you're saving audio & video using `SAVE_MEDIA=True` and whether you lower `MEDIA_MAX_SIZE=750mb`.
Because ArchiveBox is designed to ingest a large volume of URLs with multiple copies of each URL stored by different 3rd-party tools, it can be quite disk-space intensive.
There also also some special requirements when using filesystems like NFS/SMB/FUSE.
Disk usage can be reduced by using a compressed/deduplicated filesystem like ZFS/BTRFS, or by turning off extractors methods you don't need. **Don't store large collections on older filesystems like EXT3/FAT** as they may not be able to handle more than 50k directory entries in the `archive/` folder. **Try to keep the `index.sqlite3` file on local drive (not a network mount)** or SSD for maximum performance, however the `archive/` folder can be on a network mount or spinning HDD.
<br/>
<details>
<summary><i>Click to expand...</i></summary>
<br/>
**ArchiveBox can use anywhere from ~1gb per 1000 articles, to ~50gb per 1000 articles**, mostly dependent on whether you're saving audio & video using `SAVE_MEDIA=True` and whether you lower `MEDIA_MAX_SIZE=750mb`.
Disk usage can be reduced by using a compressed/deduplicated filesystem like ZFS/BTRFS, or by turning off extractors methods you don't need. You can also deduplicate content with a tool like [fdupes](https://github.com/adrianlopezroche/fdupes) or [rdfind](https://github.com/pauldreik/rdfind). **Don't store large collections on older filesystems like EXT3/FAT** as they may not be able to handle more than 50k directory entries in the `archive/` folder. **Try to keep the `index.sqlite3` file on local drive (not a network mount)** or SSD for maximum performance, however the `archive/` folder can be on a network mount or slower HDD.
If using Docker or NFS/SMB/FUSE for the `data/archive/` folder, you may need to set [`PUID` & `PGID`](https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#puid--pgid) and [disable `root_squash`](https://github.com/ArchiveBox/ArchiveBox/issues/1304) on your fileshare server.
#### Learn More
- https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#Disk-Layout
- https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#output-folder
- https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#large-archives
- https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#puid--pgid
- https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#do-not-run-as-root
</details>
<br/>
---
@@ -684,36 +878,36 @@ Disk usage can be reduced by using a compressed/deduplicated filesystem like ZFS
## Screenshots
<div align="center" width="80%">
<img src="https://i.imgur.com/PVO88AZ.png" width="80%"/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/0362bcd1-1dfd-43c6-b4ec-db5e78da07b2" width="80%"/>
<table>
<tbody>
<tr>
<td>
<img src="https://i.imgur.com/npareKG.png" alt="brew install archivebox"><br/>
<img src="https://i.imgur.com/5vSBO2R.png" alt="archivebox version">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/e9fdcb16-344e-48c8-8be0-efa48ec155d5" alt="brew install archivebox"><br/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/e235c9d8-fda9-499d-a6a5-59b0e6a0efce" alt="archivebox version">
</td>
<td>
<img src="https://i.imgur.com/JXXxFzB.png" alt="archivebox init"><br/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/0e3da0c7-d2c2-4a71-b096-6caedafd6ef7" alt="archivebox init"><br/>
</td>
<td>
<img src="https://i.imgur.com/wNYtV3v.jpg" alt="archivebox add">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/31c47440-ec14-4a02-99a3-aae8a9078d46" alt="archivebox add">
</td>
<td>
<img src="https://i.imgur.com/uZcIOn9.png" alt="archivebox data dir">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ebcdc21d-e2af-4bf8-ad4b-bc4f3151bbef" alt="archivebox data dir">
</td>
</tr>
<tr>
<td>
<img src="https://i.imgur.com/H08eaia.png" alt="archivebox server">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ea672e6b-4df5-49d8-b550-7f450951fd27" alt="archivebox server">
</td>
<td>
<img src="https://i.imgur.com/zM4z1aU.png" alt="archivebox server add">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/e1e5bd78-b0b6-45dc-914c-e1046fee4bc4" alt="archivebox server add">
</td>
<td>
<img src="https://i.imgur.com/p6wK6KM.png" alt="archivebox server list">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/e8e0b6f8-8fdf-4b7f-8124-c10d8699bdb2" alt="archivebox server list">
</td>
<td>
<img src="https://i.imgur.com/xHvQfon.png" alt="archivebox server detail">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ace0954a-ddac-4520-9d18-1c77b1ec50b2" alt="archivebox server detail">
</td>
</tr>
</tbody>
@@ -726,20 +920,25 @@ Disk usage can be reduced by using a compressed/deduplicated filesystem like ZFS
<br/>
<div align="center">
<img src="https://i.imgur.com/ZSUm9mr.png" width="100%" alt="paisley graphic">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/ca85432e-a2df-40c6-968f-51a1ef99b24e" width="100%" alt="paisley graphic">
</div>
# Background & Motivation
ArchiveBox aims to enable more of the internet to be saved from deterioration by empowering people to self-host their own archives. The intent is for all the web content you care about to be viewable with common software in 50 - 100 years without needing to run ArchiveBox or other specialized software to replay it.
<br/>
<details>
<summary><i>Click to read more...</i></summary>
<br/>
Vast treasure troves of knowledge are lost every day on the internet to link rot. As a society, we have an imperative to preserve some important parts of that treasure, just like we preserve our books, paintings, and music in physical libraries long after the originals go out of print or fade into obscurity.
Whether it's to resist censorship by saving articles before they get taken down or edited, or just to save a collection of early 2010's flash games you love to play, having the tools to archive internet content enables to you save the stuff you care most about before it disappears.
<div align="center">
<img src="https://i.imgur.com/bC6eZcV.png" width="40%"/><br/>
<sup><i>Image from <a href="https://digiday.com/media/wtf-link-rot/">WTF is Link Rot?</a>...</i><br/></sup>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/71e36bc5-1c94-44e2-92b6-405fa898c734" width="40%"/><br/>
<sup><i>Image from <a href="https://perma.cc/">Perma.cc</a>...</i><br/></sup>
</div>
The balance between the permanence and ephemeral nature of content on the internet is part of what makes it beautiful. I don't think everything should be preserved in an automated fashion--making all content permanent and never removable, but I do think people should be able to decide for themselves and effectively archive specific content that they care about.
@@ -747,11 +946,16 @@ The balance between the permanence and ephemeral nature of content on the intern
Because modern websites are complicated and often rely on dynamic content,
ArchiveBox archives the sites in **several different formats** beyond what public archiving services like Archive.org/Archive.is save. Using multiple methods and the market-dominant browser to execute JS ensures we can save even the most complex, finicky websites in at least a few high-quality, long-term data formats.
</details>
<br/>
## Comparison to Other Projects
<img src="https://i.imgur.com/4nkFjdv.png" width="5%" align="right" alt="comparison"/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/4cac62a9-e8fb-425b-85a3-ca644aa6dd42" width="5%" align="right" alt="comparison"/>
▶ **Check out our [community page](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community) for an index of web archiving initiatives and projects.**
> [!TIP]
> **Check out our [community page](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community) for an index of web archiving initiatives and projects.**
A variety of open and closed-source archiving projects exist, but few provide a nice UI and CLI to manage a large, high-fidelity archive collection over time.
@@ -767,7 +971,10 @@ By having each user store their own content locally, we can save much larger por
ArchiveBox differentiates itself from [similar self-hosted projects](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community#Web-Archiving-Projects) by providing both a comprehensive CLI interface for managing your archive, a Web UI that can be used either independently or together with the CLI, and a simple on-disk data format that can be used without either.
ArchiveBox is neither the highest fidelity nor the simplest tool available for self-hosted archiving, rather it's a jack-of-all-trades that tries to do most things well by default. It can be as simple or advanced as you want, and is designed to do everything out-of-the-box but be tuned to suit your needs.
<details>
<summary><i>Click to see the <b>⭐️ officially recommended</b> alternatives to ArchiveBox...</i></summary>
<br/>
*If you want better fidelity for very complex interactive pages with heavy JS/streams/API requests, check out [ArchiveWeb.page](https://archiveweb.page) and [ReplayWeb.page](https://replayweb.page).*
@@ -777,16 +984,23 @@ ArchiveBox is neither the highest fidelity nor the simplest tool available for s
For more alternatives, see our [list here](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community#Web-Archiving-Projects)...
ArchiveBox is neither the highest fidelity nor the simplest tool available for self-hosted archiving, rather it's a jack-of-all-trades that tries to do most things well by default. We encourage you to try these other tools made by our friends if ArchiveBox isn't suited to your needs.
</details>
<br/>
<div align="center">
<br/>
<img src="https://i.imgur.com/q0Oe36M.png" width="100%" alt="dependencies graphic">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/04808ac2-3133-44fd-8703-3387e06dc851" width="100%" alt="dependencies graphic">
</div>
## Internet Archiving Ecosystem
Whether you want to learn which organizations are the big players in the web archiving space, want to find a specific open-source tool for your web archiving need, or just want to see where archivists hang out online, our Community Wiki page serves as an index of the broader web archiving community. Check it out to learn about some of the coolest web archiving projects and communities on the web!
<img src="https://i.imgur.com/0ZOmOvN.png" width="14%" align="right"/>
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/78d8a725-97f4-47f5-b983-1f62843ddc51" width="14%" align="right"/>
- [Community Wiki](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community)
- [The Master Lists](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community#the-master-lists)
@@ -801,20 +1015,21 @@ Whether you want to learn which organizations are the big players in the web arc
- Learn why archiving the internet is important by reading the "[On the Importance of Web Archiving](https://items.ssrc.org/parameters/on-the-importance-of-web-archiving/)" blog post.
- Reach out to me for questions and comments via [@ArchiveBoxApp](https://twitter.com/ArchiveBoxApp) or [@theSquashSH](https://twitter.com/thesquashSH) on Twitter
<br/>
**Need help building a custom archiving solution?**
> ✨ **[Hire the team that helps build Archivebox](https://monadical.com) to work on your project.** ([@MonadicalSAS](https://twitter.com/MonadicalSAS))
> ✨ **[Hire the team that built Archivebox](https://zulip.archivebox.io/#narrow/stream/167-enterprise/topic/welcome/near/1191102) to work on your project.** ([@ArchiveBoxApp](https://twitter.com/ArchiveBoxApp))
<sup>(They also do general software consulting across many industries)</sup>
<sup>(We also offer general software consulting across many industries)</sup>
<br/>
---
<div align="center">
<img src="https://i.imgur.com/SMkGW0L.png" width="100%" alt="documentation graphic">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/897f7a88-1265-4aab-b80c-b1640afaad1f" width="100%" alt="documentation graphic">
</div>
# Documentation
@@ -830,27 +1045,32 @@ You can also access the docs locally by looking in the [`ArchiveBox/docs/`](http
- [Quickstart](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart)
- [Install](https://github.com/ArchiveBox/ArchiveBox/wiki/Install)
- [Docker](https://github.com/ArchiveBox/ArchiveBox/wiki/Docker)
## Reference
- [Usage](https://github.com/ArchiveBox/ArchiveBox/wiki/Usage)
- [Configuration](https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration)
- [Supported Sources](https://github.com/ArchiveBox/ArchiveBox/wiki/Quickstart#2-get-your-list-of-urls-to-archive)
- [Supported Outputs](https://github.com/ArchiveBox/ArchiveBox/wiki#can-save-these-things-for-each-site)
## Advanced
- [Troubleshooting](https://github.com/ArchiveBox/ArchiveBox/wiki/Troubleshooting)
- [Scheduled Archiving](https://github.com/ArchiveBox/ArchiveBox/wiki/Scheduled-Archiving)
- [Publishing Your Archive](https://github.com/ArchiveBox/ArchiveBox/wiki/Publishing-Your-Archive)
- [Chromium Install](https://github.com/ArchiveBox/ArchiveBox/wiki/Chromium-Install)
- [Cookies & Sessions Setup](https://github.com/ArchiveBox/ArchiveBox/wiki/Chromium-Install#setting-up-a-chromium-user-profile)
- [Security Overview](https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview)
- [Troubleshooting](https://github.com/ArchiveBox/ArchiveBox/wiki/Troubleshooting)
- [Upgrading or Merging Archives](https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives)
## Developers
- [Developer Documentation](https://github.com/ArchiveBox/ArchiveBox#archivebox-development)
- [Python API](https://docs.archivebox.io/en/latest/modules.html) (alpha)
- [REST API](https://github.com/ArchiveBox/ArchiveBox/issues/496) (alpha)
## More Info
- [Tickets](https://github.com/ArchiveBox/ArchiveBox/issues)
- [Bug Tracker](https://github.com/ArchiveBox/ArchiveBox/issues)
- [Roadmap](https://github.com/ArchiveBox/ArchiveBox/wiki/Roadmap)
- [Changelog](https://github.com/ArchiveBox/ArchiveBox/wiki/Changelog)
- [Changelog](https://github.com/ArchiveBox/ArchiveBox/releases)
- [Donations](https://github.com/ArchiveBox/ArchiveBox/wiki/Donations)
- [Background & Motivation](https://github.com/ArchiveBox/ArchiveBox#background--motivation)
- [Web Archiving Community](https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Community)
@@ -860,7 +1080,7 @@ You can also access the docs locally by looking in the [`ArchiveBox/docs/`](http
---
<div align="center">
<img src="https://i.imgur.com/EGWjbD4.png" width="100%" alt="development">
<img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/e895e79f-5c7d-429b-ad8a-7df2cc183ca3" width="100%" alt="development">
</div>
# ArchiveBox Development
@@ -947,7 +1167,21 @@ https://stackoverflow.com/questions/1074212/how-can-i-see-the-raw-sql-queries-dj
<details><summary><i>Click to expand...</i></summary>
```bash|
##### Use a Pre-Built Image
If you're looking for the latest `dev` Docker image, it's often available pre-built on Docker Hub, simply pull and use `archivebox/archivebox:dev`.
```bash
docker pull archivebox/archivebox:dev
docker run archivebox/archivebox:dev version
# verify the BUILD_TIME and COMMIT_HASH in the output are recent
```
##### Build Branch from Source
You can also build and run any branch yourself from source, for example to build & use `dev` locally:
```bash
# docker-compose.yml:
services:
archivebox:
@@ -955,11 +1189,11 @@ services:
build: 'https://github.com/ArchiveBox/ArchiveBox.git#dev'
...
# docker:
# or with plain Docker:
docker build -t archivebox:dev https://github.com/ArchiveBox/ArchiveBox.git#dev
docker run -it -v $PWD:/data archivebox:dev init --setup
# bare metal:
# or with pip:
pip install 'git+https://github.com/pirate/ArchiveBox@dev'
npm install 'git+https://github.com/ArchiveBox/ArchiveBox.git#dev'
archivebox init --setup
@@ -1101,7 +1335,7 @@ Extractors take the URL of a page to archive, write their output to the filesyst
<img src="https://raw.githubusercontent.com/Monadical-SAS/redux-time/HEAD/examples/static/jeremy.jpg" height="40px"/>
<br/>
<i><sub>
This project is maintained mostly in <a href="https://nicksweeting.com/blog#About">my spare time</a> with the help from generous <a href="https://github.com/ArchiveBox/ArchiveBox/graphs/contributors">contributors</a> and <a href="https://monadical.com">Monadical</a> (✨ <a href="https://monadical.com">hire them</a> for dev work!).
This project is maintained mostly in <a href="https://docs.sweeting.me/s/blog#About">my spare time</a> with the help from generous <a href="https://github.com/ArchiveBox/ArchiveBox/graphs/contributors">contributors</a> and <a href="https://monadical.com">Monadical Consulting</a>.
</sub>
</i>
<br/><br/>
@@ -1113,6 +1347,8 @@ This project is maintained mostly in <a href="https://nicksweeting.com/blog#Abou
<a href="https://www.patreon.com/theSquashSH"><img src="https://img.shields.io/badge/Donate_to_support_development-via_Patreon-%23DD5D76.svg?style=flat"/></a>
<br/>
<b><sub>(网站存档 / 爬虫)</sub></b>
<a href="https://twitter.com/ArchiveBoxApp"><img src="https://img.shields.io/badge/Tweet-%40ArchiveBoxApp-blue.svg?style=flat"/></a>
<a href="https://github.com/ArchiveBox/ArchiveBox"><img src="https://img.shields.io/github/stars/ArchiveBox/ArchiveBox.svg?style=flat&label=Star+on+Github"/></a>
+7
View File
@@ -39,6 +39,12 @@ def main(args: Optional[List[str]]=None, stdin: Optional[IO]=None, pwd: Optional
default=None,
help='Run ArchiveBox once every [timeperiod] (hour/day/month/year or cron format e.g. "0 0 * * *")',
)
parser.add_argument(
'--tag', '-t',
type=str,
default='',
help="Tag the added URLs with the provided tags e.g. --tag=tag1,tag2,tag3",
)
parser.add_argument(
'--depth', # '-d',
type=int,
@@ -97,6 +103,7 @@ def main(args: Optional[List[str]]=None, stdin: Optional[IO]=None, pwd: Optional
run_all=command.run_all,
quiet=command.quiet,
every=command.every,
tag=command.tag,
depth=command.depth,
overwrite=command.overwrite,
update=command.update,
+181 -61
View File
@@ -30,6 +30,7 @@ import inspect
import getpass
import platform
import shutil
import requests
import django
from sqlite3 import dbapi2 as sqlite3
@@ -51,25 +52,6 @@ from .config_stubs import (
)
### Pre-Fetch Minimal System Config
SYSTEM_USER = getpass.getuser() or os.getlogin()
try:
import pwd
SYSTEM_USER = pwd.getpwuid(os.geteuid()).pw_name or SYSTEM_USER
except KeyError:
# Process' UID might not map to a user in cases such as running the Docker image
# (where `archivebox` is 999) as a different UID.
pass
except ModuleNotFoundError:
# pwd is only needed for some linux systems, doesn't exist on windows
pass
except Exception:
# this should never happen, uncomment to debug
# raise
pass
############################### Config Schema ##################################
CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
@@ -81,7 +63,6 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'IN_QEMU': {'type': bool, 'default': False},
'PUID': {'type': int, 'default': os.getuid()},
'PGID': {'type': int, 'default': os.getgid()},
# TODO: 'SHOW_HINTS': {'type: bool, 'default': True},
},
'GENERAL_CONFIG': {
@@ -141,6 +122,7 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'SAVE_SINGLEFILE': {'type': bool, 'default': True, 'aliases': ('FETCH_SINGLEFILE',)},
'SAVE_READABILITY': {'type': bool, 'default': True, 'aliases': ('FETCH_READABILITY',)},
'SAVE_MERCURY': {'type': bool, 'default': True, 'aliases': ('FETCH_MERCURY',)},
'SAVE_HTMLTOTEXT': {'type': bool, 'default': True, 'aliases': ('FETCH_HTMLTOTEXT',)},
'SAVE_PDF': {'type': bool, 'default': True, 'aliases': ('FETCH_PDF',)},
'SAVE_SCREENSHOT': {'type': bool, 'default': True, 'aliases': ('FETCH_SCREENSHOT',)},
'SAVE_DOM': {'type': bool, 'default': True, 'aliases': ('FETCH_DOM',)},
@@ -218,6 +200,7 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'SEARCH_BACKEND_HOST_NAME': {'type': str, 'default': 'localhost'},
'SEARCH_BACKEND_PORT': {'type': int, 'default': 1491},
'SEARCH_BACKEND_PASSWORD': {'type': str, 'default': 'SecretPassword'},
'SEARCH_PROCESS_HTML': {'type': bool, 'default': True},
# SONIC
'SONIC_COLLECTION': {'type': str, 'default': 'archivebox'},
'SONIC_BUCKET': {'type': str, 'default': 'snapshots'},
@@ -255,7 +238,7 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'POCKET_CONSUMER_KEY': {'type': str, 'default': None},
'POCKET_ACCESS_TOKENS': {'type': dict, 'default': {}},
'READWISE_READER_TOKENS': {'type': dict, 'default': {}},
'READWISE_READER_TOKENS': {'type': dict, 'default': {}},
},
}
@@ -374,23 +357,126 @@ ALLOWED_IN_OUTPUT_DIR = {
'static_index.json',
}
def get_version(config):
return importlib.metadata.version(__package__ or 'archivebox')
def get_commit_hash(config):
try:
return list((config['PACKAGE_DIR'] / '../.git/refs/heads/').glob('*'))[0].read_text().strip()
except Exception:
return None
############################## Derived Config ##################################
ALLOWDENYLIST_REGEX_FLAGS: int = re.IGNORECASE | re.UNICODE | re.MULTILINE
############################## Version Config ##################################
def get_system_user():
SYSTEM_USER = getpass.getuser() or os.getlogin()
try:
import pwd
return pwd.getpwuid(os.geteuid()).pw_name or SYSTEM_USER
except KeyError:
# Process' UID might not map to a user in cases such as running the Docker image
# (where `archivebox` is 999) as a different UID.
pass
except ModuleNotFoundError:
# pwd doesn't exist on windows
pass
except Exception:
# this should never happen, uncomment to debug
# raise
pass
return SYSTEM_USER
def get_version(config):
try:
return importlib.metadata.version(__package__ or 'archivebox')
except importlib.metadata.PackageNotFoundError:
try:
pyproject_config = (config['PACKAGE_DIR'] / 'pyproject.toml').read_text()
for line in pyproject_config:
if line.startswith('version = '):
return line.split(' = ', 1)[-1].strip('"')
except FileNotFoundError:
# building docs, pyproject.toml is not available
return 'dev'
raise Exception('Failed to detect installed archivebox version!')
def get_commit_hash(config) -> Optional[str]:
try:
git_dir = config['PACKAGE_DIR'] / '../.git'
ref = (git_dir / 'HEAD').read_text().strip().split(' ')[-1]
commit_hash = git_dir.joinpath(ref).read_text().strip()
return commit_hash
except Exception:
pass
try:
return list((config['PACKAGE_DIR'] / '../.git/refs/heads/').glob('*'))[0].read_text().strip()
except Exception:
pass
return None
def get_build_time(config) -> str:
if config['IN_DOCKER']:
docker_build_end_time = Path('/VERSION.txt').read_text().rsplit('BUILD_END_TIME=')[-1].split('\n', 1)[0]
return docker_build_end_time
src_last_modified_unix_timestamp = (config['PACKAGE_DIR'] / 'config.py').stat().st_mtime
return datetime.fromtimestamp(src_last_modified_unix_timestamp).strftime('%Y-%m-%d %H:%M:%S %s')
def get_versions_available_on_github(config):
"""
returns a dictionary containing the ArchiveBox GitHub release info for
the recommended upgrade version and the currently installed version
"""
# we only want to perform the (relatively expensive) check for new versions
# when its most relevant, e.g. when the user runs a long-running command
subcommand_run_by_user = sys.argv[3] if len(sys.argv) > 3 else 'help'
long_running_commands = ('add', 'schedule', 'update', 'status', 'server')
if subcommand_run_by_user not in long_running_commands:
return None
github_releases_api = "https://api.github.com/repos/ArchiveBox/ArchiveBox/releases"
response = requests.get(github_releases_api)
if response.status_code != 200:
stderr(f'[!] Warning: GitHub API call to check for new ArchiveBox version failed! (status={response.status_code})', color='lightyellow', config=config)
return None
all_releases = response.json()
installed_version = parse_version_string(config['VERSION'])
# find current version or nearest older version (to link to)
current_version = None
for idx, release in enumerate(all_releases):
release_version = parse_version_string(release['tag_name'])
if release_version <= installed_version:
current_version = release
break
current_version = current_version or all_releases[-1]
# recommended version is whatever comes after current_version in the release list
# (perhaps too conservative to only recommend upgrading one version at a time, but it's safest)
try:
recommended_version = all_releases[idx+1]
except IndexError:
recommended_version = None
return {'recommended_version': recommended_version, 'current_version': current_version}
def can_upgrade(config):
if config['VERSIONS_AVAILABLE'] and config['VERSIONS_AVAILABLE']['recommended_version']:
recommended_version = parse_version_string(config['VERSIONS_AVAILABLE']['recommended_version']['tag_name'])
current_version = parse_version_string(config['VERSIONS_AVAILABLE']['current_version']['tag_name'])
return recommended_version > current_version
return False
############################## Derived Config ##################################
# These are derived/computed values calculated *after* all user-provided config values are ingested
# they appear in `archivebox config` output and are intended to be read-only for the user
DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'TERM_WIDTH': {'default': lambda c: lambda: shutil.get_terminal_size((100, 10)).columns},
'USER': {'default': lambda c: SYSTEM_USER},
'USER': {'default': lambda c: get_system_user()},
'ANSI': {'default': lambda c: DEFAULT_CLI_COLORS if c['USE_COLOR'] else {k: '' for k in DEFAULT_CLI_COLORS.keys()}},
'PACKAGE_DIR': {'default': lambda c: Path(__file__).resolve().parent},
@@ -406,12 +492,17 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'CHROME_USER_DATA_DIR': {'default': lambda c: find_chrome_data_dir() if c['CHROME_USER_DATA_DIR'] is None else (Path(c['CHROME_USER_DATA_DIR']).resolve() if c['CHROME_USER_DATA_DIR'] else None)}, # None means unset, so we autodetect it with find_chrome_Data_dir(), but emptystring '' means user manually set it to '', and we should store it as None
'URL_DENYLIST_PTN': {'default': lambda c: c['URL_DENYLIST'] and re.compile(c['URL_DENYLIST'] or '', ALLOWDENYLIST_REGEX_FLAGS)},
'URL_ALLOWLIST_PTN': {'default': lambda c: c['URL_ALLOWLIST'] and re.compile(c['URL_ALLOWLIST'] or '', ALLOWDENYLIST_REGEX_FLAGS)},
'DIR_OUTPUT_PERMISSIONS': {'default': lambda c: c['OUTPUT_PERMISSIONS'].replace('6', '7').replace('4', '5')},
'DIR_OUTPUT_PERMISSIONS': {'default': lambda c: c['OUTPUT_PERMISSIONS'].replace('6', '7').replace('4', '5')}, # exec is always needed to list directories
'ARCHIVEBOX_BINARY': {'default': lambda c: sys.argv[0] or bin_path('archivebox')},
'VERSION': {'default': lambda c: get_version(c)},
'COMMIT_HASH': {'default': lambda c: get_commit_hash(c)},
'VERSION': {'default': lambda c: get_version(c).split('+', 1)[0]}, # remove +editable from user-displayed version string
'COMMIT_HASH': {'default': lambda c: get_commit_hash(c)}, # short git commit hash of codebase HEAD commit
'BUILD_TIME': {'default': lambda c: get_build_time(c)}, # docker build completed time or python src last modified time
'VERSIONS_AVAILABLE': {'default': lambda c: get_versions_available_on_github(c)},
'CAN_UPGRADE': {'default': lambda c: can_upgrade(c)},
'PYTHON_BINARY': {'default': lambda c: sys.executable},
'PYTHON_ENCODING': {'default': lambda c: sys.stdout.encoding.upper()},
'PYTHON_VERSION': {'default': lambda c: '{}.{}.{}'.format(*sys.version_info[:3])},
@@ -421,7 +512,7 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'SQLITE_BINARY': {'default': lambda c: inspect.getfile(sqlite3)},
'SQLITE_VERSION': {'default': lambda c: sqlite3.version},
#'SQLITE_JOURNAL_MODE': {'default': lambda c: 'wal'}, # set at runtime below, interesting but unused for now
#'SQLITE_JOURNAL_MODE': {'default': lambda c: 'wal'}, # set at runtime below, interesting if changed later but unused for now because its always expected to be wal
#'SQLITE_OPTIONS': {'default': lambda c: ['JSON1']}, # set at runtime below
'USE_CURL': {'default': lambda c: c['USE_CURL'] and (c['SAVE_FAVICON'] or c['SAVE_TITLE'] or c['SAVE_ARCHIVE_DOT_ORG'])},
@@ -463,6 +554,7 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'CHROME_BINARY': {'default': lambda c: c['CHROME_BINARY'] or find_chrome_binary()},
'USE_CHROME': {'default': lambda c: c['USE_CHROME'] and c['CHROME_BINARY'] and (c['SAVE_PDF'] or c['SAVE_SCREENSHOT'] or c['SAVE_DOM'] or c['SAVE_SINGLEFILE'])},
'CHROME_VERSION': {'default': lambda c: bin_version(c['CHROME_BINARY']) if c['USE_CHROME'] else None},
'CHROME_USER_AGENT': {'default': lambda c: c['CHROME_USER_AGENT'].format(**c)},
'SAVE_PDF': {'default': lambda c: c['USE_CHROME'] and c['SAVE_PDF']},
'SAVE_SCREENSHOT': {'default': lambda c: c['USE_CHROME'] and c['SAVE_SCREENSHOT']},
@@ -480,7 +572,7 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'DATA_LOCATIONS': {'default': lambda c: get_data_locations(c)},
'CHROME_OPTIONS': {'default': lambda c: get_chrome_info(c)},
'SAVE_ALLOWLIST_PTN': {'default': lambda c: c['SAVE_ALLOWLIST'] and {re.compile(k, ALLOWDENYLIST_REGEX_FLAGS): v for k, v in c['SAVE_ALLOWLIST'].items()}},
'SAVE_DENYLIST_PTN': {'default': lambda c: c['SAVE_DENYLIST'] and {re.compile(k, ALLOWDENYLIST_REGEX_FLAGS): v for k, v in c['SAVE_DENYLIST'].items()}},
'SAVE_DENYLIST_PTN': {'default': lambda c: c['SAVE_DENYLIST'] and {re.compile(k, ALLOWDENYLIST_REGEX_FLAGS): v for k, v in c['SAVE_DENYLIST'].items()}},
}
@@ -496,47 +588,60 @@ def load_config_val(key: str,
config_file_vars: Optional[Dict[str, str]]=None) -> ConfigValue:
"""parse bool, int, and str key=value pairs from env"""
assert isinstance(config, dict)
is_read_only = type is None
if is_read_only:
if callable(default):
return default(config)
return default
# get value from environment variables or config files
config_keys_to_check = (key, *(aliases or ()))
val = None
for key in config_keys_to_check:
if env_vars:
val = env_vars.get(key)
if val:
break
if config_file_vars:
val = config_file_vars.get(key)
if val:
break
if type is None or val is None:
is_unset = val is None
if is_unset:
if callable(default):
assert isinstance(config, dict)
return default(config)
return default
elif type is bool:
if val.lower() in ('true', 'yes', '1'):
# calculate value based on expected type
BOOL_TRUEIES = ('true', 'yes', '1')
BOOL_FALSEIES = ('false', 'no', '0')
if type is bool:
if val.lower() in BOOL_TRUEIES:
return True
elif val.lower() in ('false', 'no', '0'):
elif val.lower() in BOOL_FALSEIES:
return False
else:
raise ValueError(f'Invalid configuration option {key}={val} (expected a boolean: True/False)')
elif type is str:
if val.lower() in ('true', 'false', 'yes', 'no', '1', '0'):
raise ValueError(f'Invalid configuration option {key}={val} (expected a string)')
if val.lower() in (*BOOL_TRUEIES, *BOOL_FALSEIES):
raise ValueError(f'Invalid configuration option {key}={val} (expected a string, but value looks like a boolean)')
return val.strip()
elif type is int:
if not val.isdigit():
if not val.strip().isdigit():
raise ValueError(f'Invalid configuration option {key}={val} (expected an integer)')
return int(val)
return int(val.strip())
elif type is list or type is dict:
return json.loads(val)
raise Exception('Config values can only be str, bool, int or json')
raise Exception('Config values can only be str, bool, int, or json')
def load_config_file(out_dir: str=None) -> Optional[Dict[str, str]]:
@@ -678,9 +783,11 @@ def load_config(defaults: ConfigDefaultDict,
return extended_config
# def write_config(config: ConfigDict):
# with open(os.path.join(config['OUTPUT_DIR'], CONFIG_FILENAME), 'w+') as f:
def parse_version_string(version: str) -> Tuple[int, int, int]:
"""parses a version tag string formatted like 'vx.x.x' into (major, minor, patch) ints"""
base = version.split('+')[0].split('v')[-1] # remove 'v' prefix and '+editable' suffix
return tuple(int(part) for part in base.split('.'))[:3]
# Logging Helpers
@@ -729,7 +836,10 @@ def bin_version(binary: Optional[str]) -> Optional[str]:
if not version_str:
version_str = run([abspath, "--version"], stdout=PIPE).stdout.strip().decode()
# take first 3 columns of first line of version info
return ' '.join(version_str.split('\n')[0].strip().split()[:3])
version_parts = version_str.split('\n')[0].strip().split()
if version_parts[:4] == ['Google', 'Chrome', 'for', 'Testing']:
return ' '.join(version_parts[:5])
return ' '.join(version_parts[:3])
except OSError:
pass
# stderr(f'[X] Unable to find working version of dependency: {binary}', color='red')
@@ -769,6 +879,7 @@ def find_chrome_binary() -> Optional[str]:
# Precedence: Chromium, Chrome, Beta, Canary, Unstable, Dev
# make sure data dir finding precedence order always matches binary finding order
default_executable_paths = (
# '~/Library/Caches/ms-playwright/chromium-*/chrome-mac/Chromium.app/Contents/MacOS/Chromium',
'chromium-browser',
'chromium',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
@@ -1059,9 +1170,9 @@ globals().update(CONFIG)
# Set timezone to UTC and umask to OUTPUT_PERMISSIONS
assert TIMEZONE == 'UTC', 'The server timezone should always be set to UTC' # we may allow this to change later
os.environ["TZ"] = TIMEZONE
os.umask(0o777 - int(DIR_OUTPUT_PERMISSIONS, base=8)) # noqa: F821
assert TIMEZONE == 'UTC', 'The server timezone should always be set to UTC' # noqa: F821
os.environ["TZ"] = TIMEZONE # noqa: F821
os.umask(0o777 - int(DIR_OUTPUT_PERMISSIONS, base=8)) # noqa: F821
# add ./node_modules/.bin to $PATH so we can use node scripts in extractors
NODE_BIN_PATH = str((Path(CONFIG["OUTPUT_DIR"]).absolute() / 'node_modules' / '.bin'))
@@ -1078,7 +1189,6 @@ sys.path.append(NODE_BIN_PATH)
# disable stderr "you really shouldnt disable ssl" warnings with library config
if not CONFIG['CHECK_SSL_VALIDITY']:
import urllib3
import requests
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -1095,14 +1205,25 @@ if not CONFIG['CHECK_SSL_VALIDITY']:
def check_system_config(config: ConfigDict=CONFIG) -> None:
### Check system environment
if config['USER'] == 'root':
if config['USER'] == 'root' or str(config['PUID']) == "0":
stderr('[!] ArchiveBox should never be run as root!', color='red')
stderr(' For more information, see the security overview documentation:')
stderr(' https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#do-not-run-as-root')
if config['IN_DOCKER']:
attempted_command = ' '.join(sys.argv[:3])
stderr('')
stderr(' {lightred}Hint{reset}: When using Docker, you must run commands with {green}docker run{reset} instead of {lightyellow}docker exec{reset}, e.g.:'.format(**config['ANSI']))
stderr(f' docker compose run archivebox {attempted_command}')
stderr(f' docker run -it -v $PWD/data:/data archivebox/archivebox {attempted_command}')
stderr(' or:')
stderr(f' docker compose exec --user=archivebox archivebox /bin/bash -c "archivebox {attempted_command}"')
stderr(f' docker exec -it --user=archivebox <container id> /bin/bash -c "archivebox {attempted_command}"')
raise SystemExit(2)
### Check Python environment
if sys.version_info[:3] < (3, 6, 0):
if sys.version_info[:3] < (3, 7, 0):
stderr(f'[X] Python version is not new enough: {config["PYTHON_VERSION"]} (>3.6 is required)', color='red')
stderr(' See https://github.com/ArchiveBox/ArchiveBox/wiki/Troubleshooting#python for help upgrading your Python installation.')
raise SystemExit(2)
@@ -1178,7 +1299,7 @@ def check_dependencies(config: ConfigDict=CONFIG, show_help: bool=True) -> None:
if config['USE_YOUTUBEDL'] and config['MEDIA_TIMEOUT'] < 20:
stderr(f'[!] Warning: MEDIA_TIMEOUT is set too low! (currently set to MEDIA_TIMEOUT={config["MEDIA_TIMEOUT"]} seconds)', color='red')
stderr(' Youtube-dl will fail to archive all media if set to less than ~20 seconds.')
stderr(' youtube-dl/yt-dlp will fail to archive any media if set to less than ~20 seconds.')
stderr(' (Setting it somewhere over 60 seconds is recommended)')
stderr()
stderr(' If you want to disable media archiving entirely, set SAVE_MEDIA=False instead:')
@@ -1266,8 +1387,7 @@ def setup_django(out_dir: Path=None, check_db=False, config: ConfigDict=CONFIG,
with open(settings.ERROR_LOG, "a", encoding='utf-8') as f:
command = ' '.join(sys.argv)
ts = datetime.now(timezone.utc).strftime('%Y-%m-%d__%H:%M:%S')
f.write(f"\n> {command}; ts={ts} version={config['VERSION']} docker={config['IN_DOCKER']} is_tty={config['IS_TTY']}\n")
f.write(f"\n> {command}; TS={ts} VERSION={config['VERSION']} IN_DOCKER={config['IN_DOCKER']} IS_TTY={config['IS_TTY']}\n")
if check_db:
# Enable WAL mode in sqlite3
+3
View File
@@ -48,9 +48,11 @@ class TagInline(admin.TabularInline):
from django.contrib.admin.helpers import ActionForm
from django.contrib.admin.widgets import AutocompleteSelectMultiple
# WIP: broken by Django 3.1.2 -> 4.0 migration
class AutocompleteTags:
model = Tag
search_fields = ['name']
name = 'tags'
class AutocompleteTagsAdminStub:
name = 'admin'
@@ -60,6 +62,7 @@ class SnapshotActionForm(ActionForm):
tags = forms.ModelMultipleChoiceField(
queryset=Tag.objects.all(),
required=False,
# WIP: broken by Django 3.1.2 -> 4.0 migration
widget=AutocompleteSelectMultiple(
AutocompleteTags(),
AutocompleteTagsAdminStub(),
+1
View File
@@ -3,4 +3,5 @@ from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'core'
# WIP: broken by Django 3.1.2 -> 4.0 migration
default_auto_field = 'django.db.models.UUIDField'
@@ -0,0 +1,18 @@
# Generated by Django 3.1.14 on 2023-10-23 20:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0021_auto_20220914_0934'),
]
operations = [
migrations.AlterField(
model_name='archiveresult',
name='extractor',
field=models.CharField(choices=[('favicon', 'favicon'), ('headers', 'headers'), ('singlefile', 'singlefile'), ('pdf', 'pdf'), ('screenshot', 'screenshot'), ('dom', 'dom'), ('wget', 'wget'), ('title', 'title'), ('readability', 'readability'), ('mercury', 'mercury'), ('htmltotext', 'htmltotext'), ('git', 'git'), ('media', 'media'), ('archive_org', 'archive_org')], max_length=32),
),
]
+3
View File
@@ -124,6 +124,7 @@ if LDAP:
}
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'django_auth_ldap.backend.LDAPBackend',
]
except ModuleNotFoundError:
@@ -268,6 +269,8 @@ AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
# WIP: broken by Django 3.1.2 -> 4.0 migration
DEFAULT_AUTO_FIELD = 'django.db.models.UUIDField'
################################################################################
### Shell Settings
+7
View File
@@ -8,6 +8,10 @@ from django.views.generic.base import RedirectView
from core.views import HomepageView, SnapshotView, PublicIndexView, AddView, HealthCheckView
# GLOBAL_CONTEXT doesn't work as-is, disabled for now: https://github.com/ArchiveBox/ArchiveBox/discussions/1306
# from config import VERSION, VERSIONS_AVAILABLE, CAN_UPGRADE
# GLOBAL_CONTEXT = {'VERSION': VERSION, 'VERSIONS_AVAILABLE': VERSIONS_AVAILABLE, 'CAN_UPGRADE': CAN_UPGRADE}
# print('DEBUG', settings.DEBUG)
@@ -31,6 +35,9 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')),
path('admin/', admin.site.urls),
# do not add extra_context like this as not all admin views (e.g. ModelAdmin.autocomplete_view accept extra kwargs)
# path('admin/', admin.site.urls, {'extra_context': GLOBAL_CONTEXT}),
path('health/', HealthCheckView.as_view(), name='healthcheck'),
path('error/', lambda _: 1/0),
+2
View File
@@ -23,6 +23,7 @@ from ..config import (
PUBLIC_SNAPSHOTS,
PUBLIC_ADD_VIEW,
VERSION,
COMMIT_HASH,
FOOTER_INFO,
SNAPSHOTS_PER_PAGE,
)
@@ -217,6 +218,7 @@ class PublicIndexView(ListView):
return {
**super().get_context_data(**kwargs),
'VERSION': VERSION,
'COMMIT_HASH': COMMIT_HASH,
'FOOTER_INFO': FOOTER_INFO,
}
+15 -4
View File
@@ -37,6 +37,7 @@ from .wget import should_save_wget, save_wget
from .singlefile import should_save_singlefile, save_singlefile
from .readability import should_save_readability, save_readability
from .mercury import should_save_mercury, save_mercury
from .htmltotext import should_save_htmltotext, save_htmltotext
from .pdf import should_save_pdf, save_pdf
from .screenshot import should_save_screenshot, save_screenshot
from .dom import should_save_dom, save_dom
@@ -59,14 +60,26 @@ def get_default_archive_methods() -> List[ArchiveMethodEntry]:
('screenshot', should_save_screenshot, save_screenshot),
('dom', should_save_dom, save_dom),
('wget', should_save_wget, save_wget),
('title', should_save_title, save_title), # keep title and readability below wget and singlefile, as it depends on them
# keep title, readability, and htmltotext below wget and singlefile, as they depend on them
('title', should_save_title, save_title),
('readability', should_save_readability, save_readability),
('mercury', should_save_mercury, save_mercury),
('htmltotext', should_save_htmltotext, save_htmltotext),
('git', should_save_git, save_git),
('media', should_save_media, save_media),
('archive_org', should_save_archive_dot_org, save_archive_dot_org),
]
ARCHIVE_METHODS_INDEXING_PRECEDENCE = [
('readability', 1),
('mercury', 2),
('htmltotext', 3),
('singlefile', 4),
('dom', 5),
('wget', 6)
]
@enforce_types
def get_archive_methods_for_link(link: Link) -> Iterable[ArchiveMethodEntry]:
DEFAULT_METHODS = get_default_archive_methods()
@@ -86,8 +99,6 @@ def get_archive_methods_for_link(link: Link) -> Iterable[ArchiveMethodEntry]:
return (m for m in DEFAULT_METHODS if m[0] in allowed_methods)
ARCHIVE_METHODS_INDEXING_PRECEDENCE = [('readability', 1), ('singlefile', 2), ('dom', 3), ('wget', 4)]
@enforce_types
def ignore_methods(to_ignore: List[str]) -> Iterable[str]:
ARCHIVE_METHODS = get_default_archive_methods()
@@ -173,7 +184,7 @@ def archive_link(link: Link, overwrite: bool=False, methods: Optional[Iterable[s
link.url,
command,
ts
) + "\n"))
) + "\n" + str(e) + "\n"))
#f.write(f"\n> {command}; ts={ts} version={config['VERSION']} docker={config['IN_DOCKER']} is_tty={config['IS_TTY']}\n")
# print(' ', stats)
+154
View File
@@ -0,0 +1,154 @@
__package__ = 'archivebox.extractors'
from html.parser import HTMLParser
import io
from pathlib import Path
from typing import Optional
from ..config import (
SAVE_HTMLTOTEXT,
TIMEOUT,
VERSION,
)
from ..index.schema import Link, ArchiveResult, ArchiveError
from ..logging_util import TimedProgress
from ..system import atomic_write
from ..util import (
enforce_types,
is_static_file,
)
from .title import get_html
class HTMLTextExtractor(HTMLParser):
TEXT_ATTRS = [
"alt", "cite", "href", "label",
"list", "placeholder", "title", "value"
]
NOTEXT_TAGS = ["script", "style", "template"]
NOTEXT_HREF = ["data:", "javascript:", "#"]
def __init__(self):
super().__init__()
self.output = io.StringIO()
self._tag_stack = []
def _is_text_attr(self, name, value):
if not isinstance(value, str):
return False
if name == "href" and any(map(lambda p: value.startswith(p), self.NOTEXT_HREF)):
return False
if name in self.TEXT_ATTRS:
return True
return False
def _parent_tag(self):
try:
return self._tag_stack[-1]
except IndexError:
return None
def _in_notext_tag(self):
return any([t in self._tag_stack for t in self.NOTEXT_TAGS])
def handle_starttag(self, tag, attrs):
self._tag_stack.append(tag)
# Don't write out attribute values if any ancestor
# is in NOTEXT_TAGS
if self._in_notext_tag():
return
for name, value in attrs:
if self._is_text_attr(name, value):
self.output.write(f"({value.strip()}) ")
def handle_endtag(self, tag):
orig_stack = self._tag_stack.copy()
try:
# Keep popping tags until we find the nearest
# ancestor matching this end tag
while tag != self._tag_stack.pop():
pass
# Write a space after every tag, to ensure that tokens
# in tag text aren't concatenated. This may result in
# excess spaces, which should be ignored by search tokenizers.
if not self._in_notext_tag() and tag not in self.NOTEXT_TAGS:
self.output.write(" ")
except IndexError:
# Got to the top of the stack, but somehow missed
# this end tag -- maybe malformed markup -- restore the
# stack
self._tag_stack = orig_stack
def handle_data(self, data):
# Don't output text data if any ancestor is in NOTEXT_TAGS
if self._in_notext_tag():
return
data = data.lstrip()
len_before_rstrip = len(data)
data = data.rstrip()
spaces_rstripped = len_before_rstrip - len(data)
if data:
self.output.write(data)
if spaces_rstripped:
# Add back a single space if 1 or more
# whitespace characters were stripped
self.output.write(' ')
def __str__(self):
return self.output.getvalue()
@enforce_types
def should_save_htmltotext(link: Link, out_dir: Optional[Path]=None, overwrite: Optional[bool]=False) -> bool:
if is_static_file(link.url):
return False
out_dir = out_dir or Path(link.link_dir)
if not overwrite and (out_dir / 'htmltotext.txt').exists():
return False
return SAVE_HTMLTOTEXT
@enforce_types
def save_htmltotext(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEOUT) -> ArchiveResult:
"""extract search-indexing-friendly text from an HTML document"""
out_dir = Path(out_dir or link.link_dir)
output = "htmltotext.txt"
timer = TimedProgress(timeout, prefix=' ')
extracted_text = None
try:
extractor = HTMLTextExtractor()
document = get_html(link, out_dir)
if not document:
raise ArchiveError('htmltotext could not find HTML to parse for article text')
extractor.feed(document)
extractor.close()
extracted_text = str(extractor)
atomic_write(str(out_dir / output), extracted_text)
except (Exception, OSError) as err:
status = 'failed'
output = err
cmd = ['(internal) archivebox.extractors.htmltotext', './{singlefile,dom}.html']
finally:
timer.end()
return ArchiveResult(
cmd=cmd,
pwd=str(out_dir),
cmd_version=VERSION,
output=output,
status=status,
index_texts=[extracted_text] if extracted_text else [],
**timer.stats,
)
+5 -6
View File
@@ -67,13 +67,12 @@ def save_readability(link: Link, out_dir: Optional[str]=None, timeout: int=TIMEO
temp_doc.name,
link.url,
]
result = run(cmd, cwd=out_dir, timeout=timeout)
try:
result_json = json.loads(result.stdout)
assert result_json and 'content' in result_json, 'Readability output is not valid JSON'
except json.JSONDecodeError:
raise ArchiveError('Readability was not able to archive the page', result.stdout + result.stderr)
raise ArchiveError('Readability was not able to archive the page (invalid JSON)', result.stdout + result.stderr)
output_folder.mkdir(exist_ok=True)
readability_content = result_json.pop("textContent")
@@ -81,8 +80,6 @@ def save_readability(link: Link, out_dir: Optional[str]=None, timeout: int=TIMEO
atomic_write(str(output_folder / "content.txt"), readability_content)
atomic_write(str(output_folder / "article.json"), result_json)
# parse out number of files downloaded from last line of stderr:
# "Downloaded: 76 files, 4.0M in 1.6s (2.52 MB/s)"
output_tail = [
line.strip()
for line in (result.stdout + result.stderr).decode().rsplit('\n', 5)[-5:]
@@ -95,11 +92,13 @@ def save_readability(link: Link, out_dir: Optional[str]=None, timeout: int=TIMEO
# Check for common failure cases
if (result.returncode > 0):
raise ArchiveError('Readability was not able to archive the page', hints)
raise ArchiveError(f'Readability was not able to archive the page (status={result.returncode})', hints)
except (Exception, OSError) as err:
status = 'failed'
output = err
cmd = [cmd[0], './{singlefile,dom}.html']
# prefer Chrome dom output to singlefile because singlefile often contains huge url(data:image/...base64) strings that make the html too long to parse with readability
cmd = [cmd[0], './{dom,singlefile}.html']
finally:
timer.end()
+3 -1
View File
@@ -66,7 +66,9 @@ def get_html(link: Link, path: Path, timeout: int=TIMEOUT) -> str:
"""
canonical = link.canonical_outputs()
abs_path = path.absolute()
sources = [canonical["singlefile_path"], canonical["wget_path"], canonical["dom_path"]]
# prefer chrome-generated DOM dump to singlefile as singlefile output often includes HUGE url(data:image/...base64) strings that crash parsers
sources = [canonical["dom_path"], canonical["singlefile_path"], canonical["wget_path"]]
document = None
for source in sources:
try:
+1 -1
View File
@@ -143,7 +143,7 @@ def snapshot_icons(snapshot) -> str:
"mercury": "🅼",
"warc": "📦"
}
exclude = ["favicon", "title", "headers", "archive_org"]
exclude = ["favicon", "title", "headers", "htmltotext", "archive_org"]
# Missing specific entry for WARC
extractor_outputs = defaultdict(lambda: None)
+2
View File
@@ -429,6 +429,7 @@ class Link:
'singlefile_path': 'singlefile.html',
'readability_path': 'readability/content.html',
'mercury_path': 'mercury/content.html',
'htmltotext_path': 'htmltotext.txt',
'pdf_path': 'output.pdf',
'screenshot_path': 'screenshot.png',
'dom_path': 'output.html',
@@ -452,6 +453,7 @@ class Link:
'singlefile_path': static_path,
'readability_path': static_path,
'mercury_path': static_path,
'htmltotext_path': static_path,
})
return canonical
+5 -3
View File
@@ -109,11 +109,13 @@ def write_sql_link_details(link: Link, out_dir: Path=OUTPUT_DIR) -> None:
snap = Snapshot.objects.get(url=link.url)
except Snapshot.DoesNotExist:
snap = write_link_to_sql_index(link)
snap.title = link.title
tag_list = list(dict.fromkeys(
tag.strip() for tag in re.split(TAG_SEPARATOR_PATTERN, link.tags or '')
))
tag_list = list(
{tag.strip() for tag in re.split(TAG_SEPARATOR_PATTERN, link.tags or '')}
| set(snap.tags.values_list('name', flat=True))
)
snap.save()
snap.save_tags(tag_list)
+14 -4
View File
@@ -393,7 +393,11 @@ def log_link_archiving_finished(link: "Link", link_dir: str, is_new: bool, stats
else:
_LAST_RUN_STATS.succeeded += 1
size = get_dir_size(link_dir)
try:
size = get_dir_size(link_dir)
except FileNotFoundError:
size = (0, None, '0')
end_ts = datetime.now(timezone.utc)
duration = str(end_ts - start_ts).split('.')[0]
print(' {black}{} files ({}) in {}s {reset}'.format(size[2], printable_filesize(size[0]), duration, **ANSI))
@@ -409,7 +413,7 @@ def log_archive_method_finished(result: "ArchiveResult"):
"""
# Prettify CMD string and make it safe to copy-paste by quoting arguments
quoted_cmd = ' '.join(
'"{}"'.format(arg) if ' ' in arg else arg
'"{}"'.format(arg) if (' ' in arg) or (':' in arg) else arg
for arg in result.cmd
)
@@ -444,12 +448,18 @@ def log_archive_method_finished(result: "ArchiveResult"):
for line in list(hints)[:5] if line.strip()
)
docker_hints = ()
if IN_DOCKER:
docker_hints = (
' docker run -it -v $PWD/data:/data archivebox/archivebox /bin/bash',
)
# Collect and prefix output lines with indentation
output_lines = [
*hint_header,
*hints,
'{}Run to see full output:{}'.format(ANSI['lightred'], ANSI['reset']),
*docker_hints,
*([' cd {};'.format(result.pwd)] if result.pwd else []),
' {}'.format(quoted_cmd),
]
@@ -517,8 +527,8 @@ def log_shell_welcome_msg():
from .cli import list_subcommands
print('{green}# ArchiveBox Imports{reset}'.format(**ANSI))
print('{green}from core.models import Snapshot, User{reset}'.format(**ANSI))
print('{green}from archivebox import *\n {}{reset}'.format("\n ".join(list_subcommands().keys()), **ANSI))
print('{green}from archivebox.core.models import Snapshot, ArchiveResult, Tag, User{reset}'.format(**ANSI))
print('{green}from archivebox.cli import *\n {}{reset}'.format("\n ".join(list_subcommands().keys()), **ANSI))
print()
print('[i] Welcome to the ArchiveBox Shell!')
print(' https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#Shell-Usage')
+52 -34
View File
@@ -93,11 +93,16 @@ from .config import (
SQL_INDEX_FILENAME,
ALLOWED_IN_OUTPUT_DIR,
SEARCH_BACKEND_ENGINE,
LDAP,
get_version,
check_dependencies,
check_data_folder,
write_config_file,
VERSION,
VERSIONS_AVAILABLE,
CAN_UPGRADE,
COMMIT_HASH,
BUILD_TIME,
CODE_LOCATIONS,
EXTERNAL_LOCATIONS,
DATA_LOCATIONS,
@@ -217,32 +222,40 @@ def version(quiet: bool=False,
print(VERSION)
if not quiet:
# 0.6.3
# ArchiveBox v0.6.3 Cpython Linux Linux-4.19.121-linuxkit-x86_64-with-glibc2.28 x86_64 (in Docker) (in TTY)
# DEBUG=False IN_DOCKER=True IN_QEMU=False IS_TTY=True TZ=UTC FS_ATOMIC=True FS_REMOTE=False FS_PERMS=644 FS_USER=501:20 SEARCH_BACKEND=ripgrep
# 0.7.1
# ArchiveBox v0.7.1+editable COMMIT_HASH=951bba5 BUILD_TIME=2023-12-17 16:46:05 1702860365
# IN_DOCKER=False IN_QEMU=False ARCH=arm64 OS=Darwin PLATFORM=macOS-14.2-arm64-arm-64bit PYTHON=Cpython
# FS_ATOMIC=True FS_REMOTE=False FS_USER=501:20 FS_PERMS=644
# DEBUG=False IS_TTY=True TZ=UTC SEARCH_BACKEND=ripgrep LDAP=False
p = platform.uname()
print(
'ArchiveBox v{}'.format(VERSION),
*((COMMIT_HASH[:7],) if COMMIT_HASH else ()),
sys.implementation.name.title(),
p.system,
platform.platform(),
p.machine,
'ArchiveBox v{}'.format(get_version(CONFIG)),
*((f'COMMIT_HASH={COMMIT_HASH[:7]}',) if COMMIT_HASH else ()),
f'BUILD_TIME={BUILD_TIME}',
)
print(
f'IN_DOCKER={IN_DOCKER}',
f'IN_QEMU={IN_QEMU}',
f'ARCH={p.machine}',
f'OS={p.system}',
f'PLATFORM={platform.platform()}',
f'PYTHON={sys.implementation.name.title()}',
)
OUTPUT_IS_REMOTE_FS = DATA_LOCATIONS['OUTPUT_DIR']['is_mount'] or DATA_LOCATIONS['ARCHIVE_DIR']['is_mount']
print(
f'DEBUG={DEBUG}',
f'IN_DOCKER={IN_DOCKER}',
f'IN_QEMU={IN_QEMU}',
f'IS_TTY={IS_TTY}',
f'TZ={TIMEZONE}',
#f'DB=django.db.backends.sqlite3 (({CONFIG["SQLITE_JOURNAL_MODE"]})', # add this if we have more useful info to show eventually
f'FS_ATOMIC={ENFORCE_ATOMIC_WRITES}',
f'FS_REMOTE={OUTPUT_IS_REMOTE_FS}',
f'FS_USER={PUID}:{PGID}',
f'FS_PERMS={OUTPUT_PERMISSIONS}',
)
print(
f'DEBUG={DEBUG}',
f'IS_TTY={IS_TTY}',
f'TZ={TIMEZONE}',
f'SEARCH_BACKEND={SEARCH_BACKEND_ENGINE}',
f'LDAP={LDAP}',
#f'DB=django.db.backends.sqlite3 (({CONFIG["SQLITE_JOURNAL_MODE"]})', # add this if we have more useful info to show eventually
)
print()
@@ -271,7 +284,7 @@ def version(quiet: bool=False,
print(printable_folder_status(name, path))
else:
print()
print('{white}[i] Data locations:{reset}'.format(**ANSI))
print('{white}[i] Data locations:{reset} (not in a data directory)'.format(**ANSI))
print()
check_dependencies()
@@ -591,7 +604,7 @@ def add(urls: Union[str, List[str]],
out_dir: Path=OUTPUT_DIR) -> List[Link]:
"""Add a new URL or list of URLs to your archive"""
from core.models import Tag
from core.models import Snapshot, Tag
assert depth in (0, 1), 'Depth must be 0 or 1 (depth >1 is not supported yet)'
@@ -635,6 +648,19 @@ def add(urls: Union[str, List[str]],
write_main_index(links=new_links, out_dir=out_dir)
all_links = load_main_index(out_dir=out_dir)
tags = [
Tag.objects.get_or_create(name=name.strip())[0]
for name in tag.split(',')
if name.strip()
]
if tags:
for link in imported_links:
snapshot = Snapshot.objects.get(url=link.url)
snapshot.tags.add(*tags)
snapshot.tags_str(nocache=True)
snapshot.save()
# print(f' √ Tagged {len(imported_links)} Snapshots with {len(tags)} tags {tags_str}')
if index_only:
# mock archive all the links using the fake index_only extractor method in order to update their state
if overwrite:
@@ -666,21 +692,8 @@ def add(urls: Union[str, List[str]],
stderr(f'[*] [{ts}] Archiving {len(new_links)}/{len(all_links)} URLs from added set...', color='green')
archive_links(new_links, overwrite=False, **archive_kwargs)
# add any tags to imported links
tags = [
Tag.objects.get_or_create(name=name.strip())[0]
for name in tag.split(',')
if name.strip()
]
if tags:
for link in imported_links:
snapshot = link.as_snapshot()
snapshot.tags.add(*tags)
snapshot.tags_str(nocache=True)
snapshot.save()
# print(f' √ Tagged {len(imported_links)} Snapshots with {len(tags)} tags {tags_str}')
if CAN_UPGRADE:
hint(f"There's a new version of ArchiveBox available! Your current version is {VERSION}. You can upgrade to {VERSIONS_AVAILABLE['recommended_version']['tag_name']} ({VERSIONS_AVAILABLE['recommended_version']['html_url']}). For more on how to upgrade: https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives\n")
return all_links
@@ -1005,9 +1018,9 @@ def setup(out_dir: Path=OUTPUT_DIR) -> None:
stderr('\n Installing SINGLEFILE_BINARY, READABILITY_BINARY, MERCURY_BINARY automatically using npm...')
if not NODE_VERSION:
stderr('[X] You must first install node using your system package manager', color='red')
stderr('[X] You must first install node & npm using your system package manager', color='red')
hint([
'curl -sL https://deb.nodesource.com/setup_15.x | sudo -E bash -',
'https://github.com/nodesource/distributions#table-of-contents',
'or to disable all node-based modules run: archivebox config --set USE_NODE=False',
])
raise SystemExit(1)
@@ -1155,6 +1168,7 @@ def schedule(add: bool=False,
run_all: bool=False,
quiet: bool=False,
every: Optional[str]=None,
tag: str='',
depth: int=0,
overwrite: bool=False,
update: bool=not ONLY_NEW,
@@ -1188,6 +1202,7 @@ def schedule(add: bool=False,
'add',
*(['--overwrite'] if overwrite else []),
*(['--update'] if update else []),
*([f'--tag={tag}'] if tag else []),
f'--depth={depth}',
f'"{import_path}"',
] if import_path else ['update']),
@@ -1270,6 +1285,9 @@ def schedule(add: bool=False,
print('\n{green}[√] Stopped.{reset}'.format(**ANSI))
raise SystemExit(1)
if CAN_UPGRADE:
hint(f"There's a new version of ArchiveBox available! Your current version is {VERSION}. You can upgrade to {VERSIONS_AVAILABLE['recommended_version']['tag_name']} ({VERSIONS_AVAILABLE['recommended_version']['html_url']}). For more on how to upgrade: https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives\n")
@enforce_types
def server(runserver_args: Optional[List[str]]=None,
+3 -3
View File
@@ -1,13 +1,13 @@
{
"name": "archivebox",
"version": "0.7.0",
"version": "0.7.4",
"description": "ArchiveBox: The self-hosted internet archive",
"author": "Nick Sweeting <archivebox-npm@sweeting.me>",
"repository": "github:ArchiveBox/ArchiveBox",
"license": "MIT",
"dependencies": {
"@postlight/parser": "^2.2.3",
"readability-extractor": "git+https://github.com/ArchiveBox/readability-extractor.git",
"single-file-cli": "^1.1.12"
"readability-extractor": "github:ArchiveBox/readability-extractor",
"single-file-cli": "^2.0.83"
}
}
+3
View File
@@ -155,6 +155,9 @@ def save_text_as_source(raw_text: str, filename: str='{ts}-stdin.txt', out_dir:
referenced_texts = ''
for entry in raw_text.split():
# Skip URLs - Path() can't handle them and they aren't local files
if any(entry.startswith(s) for s in ('http://', 'https://', 'ftp://')):
continue
try:
if Path(entry).exists():
referenced_texts += Path(entry).read_text()
+1
View File
@@ -0,0 +1 @@
templates/static
+5 -3
View File
@@ -185,17 +185,19 @@ def dedupe_cron_jobs(cron: CronTab) -> CronTab:
class suppress_output(object):
'''
"""
A context manager for doing a "deep suppression" of stdout and stderr in
Python, i.e. will suppress all print, even if the print originates in a
compiled C/Fortran sub-function.
This will not suppress raised exceptions, since exceptions are printed
This will not suppress raised exceptions, since exceptions are printed
to stderr just before a script exits, and after the context manager has
exited (at least, I think that is why it lets exceptions through).
with suppress_stdout_stderr():
rogue_function()
'''
"""
def __init__(self, stdout=True, stderr=True):
# Open a pair of null files
# Save the actual stdout (1) and stderr (2) file descriptors.
+57 -1
View File
@@ -12,7 +12,26 @@
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}">
{% block extrastyle %}{% endblock %}
{% block extrastyle %}
<style>
#upgrade-banner {
position: fixed;
right: 20px;
bottom: 20px;
background-color: #f8f8f8;
color: #333333;
border: 2px solid #772948;
padding: 10px 20px;
z-index: 1000;
text-align: center;
}
#dismiss-btn {
background: #aa1e55;
color: white;
cursor: pointer;
}
</style>
{% endblock %}
{% if LANGUAGE_BIDI %}
<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">
@@ -122,6 +141,43 @@
{% block footer %}<div id="footer"></div>{% endblock %}
</div>
{% if user.is_authenticated and user.is_superuser and CAN_UPGRADE %}
<script>
if (!localStorage.getItem("bannerDismissed")) {
const upgradeVersionTag = "{{VERSIONS_AVAILABLE.recommended_version.tag_name}}"
const upgradeVersionURL = "{{VERSIONS_AVAILABLE.recommended_version.html_url}}"
const currentVersionTag = "{{VERSION}}"
const currentVersionURL = "{{VERSIONS_AVAILABLE.recommended_version.html_url}}"
createBanner(currentVersionTag, currentVersionURL, upgradeVersionTag, upgradeVersionURL)
}
function createBanner(currentVersionTag, currentVersionURL, upgradeVersionTag, upgradeVersionURL) {
const banner = document.createElement('div')
banner.setAttribute('id', 'upgrade-banner');
banner.innerHTML = `
<p>There's a new version of ArchiveBox available!</p>
Your version: <a href=${currentVersionURL}>${currentVersionTag}</a> | New version: <a href=${upgradeVersionURL}>${upgradeVersionTag}</a>
<p>
<a href=https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives>Upgrade Instructions</a> | <a href=https://github.com/ArchiveBox/ArchiveBox/releases>Changelog</a> | <a href=https://github.com/ArchiveBox/ArchiveBox/wiki/Roadmap>Roadmap</a>
</p>
<button id="dismiss-btn">Dismiss</button>
`
document.body.appendChild(banner);
const dismissButton = document.querySelector("#dismiss-btn")
if (dismissButton) {
dismissButton.addEventListener("click", dismissBanner)
}
}
function dismissBanner() {
const banner = document.getElementById("upgrade-banner")
banner.style.display = "none"
localStorage.setItem("bannerDismissed", "true")
}
</script>
{% endif %}
<script>
$ = django.jQuery;
$.fn.reverse = [].reverse;
+5 -2
View File
@@ -38,8 +38,11 @@
<br />
<center>
<small>
Archive created using <a href="https://github.com/ArchiveBox/ArchiveBox" title="Github">ArchiveBox</a> version
<a href="https://github.com/ArchiveBox/ArchiveBox/releases/tag/v{{VERSION}}" title="Releases">v{{VERSION}}</a>.
Archive created using <a href="https://github.com/ArchiveBox/ArchiveBox" title="Github">ArchiveBox</a>
<a href="https://github.com/ArchiveBox/ArchiveBox/releases/tag/v{{VERSION}}" title="Releases">v{{VERSION}}</a>
{% if COMMIT_HASH %}
(<a href="https://github.com/ArchiveBox/ArchiveBox/commit/{{COMMIT_HASH}}"><code>{{COMMIT_HASH|truncatechars:9}}</code></a>)
{% endif %}.
<br/><br/>
{{FOOTER_INFO}}
</small>
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../../package-lock.json
+1
View File
@@ -0,0 +1 @@
../../package.json
+12 -2
View File
@@ -23,6 +23,7 @@ SUPPORTED_PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7"
TAG_NAME="${1:-$(git rev-parse --abbrev-ref HEAD)}"
VERSION="$(jq -r '.version' < "$REPO_DIR/package.json")"
SHORT_VERSION="$(echo "$VERSION" | perl -pe 's/(\d+)\.(\d+)\.(\d+)/$1.$2/g')"
GIT_SHA=sha-"$(git rev-parse --short HEAD)"
SELECTED_PLATFORMS="${2:-$SUPPORTED_PLATFORMS}"
echo "[+] Building Docker image: tag=$TAG_NAME version=$SHORT_VERSION arch=$SELECTED_PLATFORMS"
@@ -50,6 +51,7 @@ function create_builder() {
docker buildx use xbuilder && return 0
echo "[+] Creating new xbuilder for: $SELECTED_PLATFORMS"
echo
docker pull 'moby/buildkit:buildx-stable-1'
# Switch to buildx builder if already present / previously created
docker buildx create --name xbuilder --driver docker-container --bootstrap --use --platform "$SELECTED_PLATFORMS" || true
@@ -71,9 +73,10 @@ check_platforms || (recreate_builder && check_platforms) || exit 1
# Build python package lists
echo "[+] Generating requirements.txt and pdm.lock from pyproject.toml..."
pdm lock --group=':all' --strategy="cross_platform" --production
pdm lock --group=':all' --strategy="inherit_metadata" --production
pdm export --group=':all' --production --without-hashes -o requirements.txt
echo "[+] Building archivebox:$VERSION docker image..."
# docker builder prune
# docker build . --no-cache -t archivebox-dev \
@@ -83,12 +86,19 @@ docker buildx build --platform "$SELECTED_PLATFORMS" --load . \
-t archivebox/archivebox:$TAG_NAME \
-t archivebox/archivebox:$VERSION \
-t archivebox/archivebox:$SHORT_VERSION \
-t archivebox/archivebox:$GIT_SHA \
-t archivebox/archivebox:latest \
-t archivebox/archivebox:stable \
-t nikisweeting/archivebox \
-t nikisweeting/archivebox:$TAG_NAME \
-t nikisweeting/archivebox:$VERSION \
-t nikisweeting/archivebox:$SHORT_VERSION \
-t nikisweeting/archivebox:$GIT_SHA \
-t nikisweeting/archivebox:latest \
-t nikisweeting/archivebox:stable \
-t ghcr.io/archivebox/archivebox/archivebox:$TAG_NAME \
-t ghcr.io/archivebox/archivebox/archivebox:$VERSION \
-t ghcr.io/archivebox/archivebox/archivebox:$SHORT_VERSION
-t ghcr.io/archivebox/archivebox/archivebox:$SHORT_VERSION \
-t ghcr.io/archivebox/archivebox/archivebox:$GIT_SHA \
-t ghcr.io/archivebox/archivebox/archivebox:latest \
-t ghcr.io/archivebox/archivebox/archivebox:stable
+112 -30
View File
@@ -1,20 +1,55 @@
#!/bin/bash
# This Docker ENTRYPOINT script is called by `docker run archivebox ...` or `docker compose run archivebox ...`.
# It takes a CMD as $* shell arguments and runs it following these setup steps:
# - Set the archivebox user to use the correct PUID & PGID
# 1. highest precedence is for valid PUID and PGID env vars passsed in explicitly
# 2. fall back to DETECTED_PUID of files found within existing data dir
# 3. fall back to DEFAULT_PUID if no data dir or its owned by root
# - Create a new /data dir if necessary and set the correct ownership on it
# - Create a new /browsers dir if necessary and set the correct ownership on it
# - Check whether we're running inside QEMU emulation and show a warning if so.
# - Check that enough free space is available on / and /data
# - Drop down to archivebox user permisisons and execute passed CMD command.
# Bash Environment Setup
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
# https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
# set -o xtrace
# set -o nounset
set -o errexit
set -o errtrace
set -o pipefail
# IFS=$'\n'
# Load global invariants (set by Dockerfile during image build time, not intended to be customized by users at runtime)
export DATA_DIR="${DATA_DIR:-/data}"
export ARCHIVEBOX_USER="${ARCHIVEBOX_USER:-archivebox}"
# default PUID and PGID if data dir is empty and no PUID+PGID is set
# Global default PUID and PGID if data dir is empty and no intended PUID+PGID is set manually by user
export DEFAULT_PUID=911
export DEFAULT_PGID=911
# if data directory already exists, autodetect detect owner by looking at files within
export DETECTED_UID="$(stat -c '%u' "$DATA_DIR/logs/errors.log" 2>/dev/null || echo "$DEFAULT_PUID")"
export DETECTED_GID="$(stat -c '%g' "$DATA_DIR/logs/errors.log" 2>/dev/null || echo "$DEFAULT_PGID")"
# If user tires to set PUID and PGID to root values manually, catch and reject because root is not allowed
if [[ "$PUID" == "0" ]]; then
echo -e "\n[X] Error: Got PUID=$PUID and PGID=$PGID but ArchiveBox is not allowed to be run as root, please change or unset PUID & PGID and try again." > /dev/stderr
echo -e " Hint: some NFS/SMB/FUSE/etc. filesystems force-remap/ignore all permissions," > /dev/stderr
echo -e " leave PUID/PGID unset, or use values the filesystem prefers (defaults to $DEFAULT_PUID:$DEFAULT_PGID)" > /dev/stderr
echo -e " https://linux.die.net/man/8/mount.cifs#:~:text=does%20not%20provide%20unix%20ownership" > /dev/stderr
exit 3
fi
# Set the archivebox user to use the configured UID & GID
# prefers PUID and PGID env vars passsed in explicitly, falls back to autodetected defaults
groupmod -o -g "${PUID:-$DETECTED_UID}" "$ARCHIVEBOX_USER" > /dev/null 2>&1
usermod -o -u "${PGID:-$DETECTED_GID}" "$ARCHIVEBOX_USER" > /dev/null 2>&1
# If data directory already exists, autodetect detect owner by looking at files within
export DETECTED_PUID="$(stat -c '%u' "$DATA_DIR/logs/errors.log" 2>/dev/null || echo "$DEFAULT_PUID")"
export DETECTED_PGID="$(stat -c '%g' "$DATA_DIR/logs/errors.log" 2>/dev/null || echo "$DEFAULT_PGID")"
# If data directory exists but is owned by root, use defaults instead of root because root is not allowed
[[ "$DETECTED_PUID" == "0" ]] && export DETECTED_PUID="$DEFAULT_PUID"
# Set archivebox user and group ids to desired PUID/PGID
usermod -o -u "${PUID:-$DETECTED_PUID}" "$ARCHIVEBOX_USER" > /dev/null 2>&1
groupmod -o -g "${PGID:-$DETECTED_PGID}" "$ARCHIVEBOX_USER" > /dev/null 2>&1
# re-set PUID and PGID to values reported by system instead of values we tried to set,
# in case wonky filesystems or Docker setups try to play UID/GID remapping tricks on us
@@ -29,12 +64,15 @@ if [[ -d "$DATA_DIR/archive" ]]; then
# echo "[√] Permissions are correct"
else
# the only time this fails is if the host filesystem doesn't allow us to write as root (e.g. some NFS mapall/maproot problems, connection issues, drive dissapeared, etc.)
echo -e "\n[X] Error: archivebox user (PUID=$PUID) is not able to write to your ./data dir." >&2
echo -e " Change ./data to be owned by PUID=$PUID PGID=$PGID on the host and retry:"
echo -e " \$ chown -R $PUID:$PGID ./data\n" >&2
echo -e " Configure the PUID & PGID environment variables to change the desired owner:" >&2
echo -e " https://docs.linuxserver.io/general/understanding-puid-and-pgid\n" >&2
exit 1
echo -e "\n[X] Error: archivebox user (PUID=$PUID) is not able to write to your ./data dir (currently owned by $(stat -c '%u' "$DATA_DIR"):$(stat -c '%g' "$DATA_DIR")." >&2
echo -e " Change ./data to be owned by PUID=$PUID PGID=$PGID on the host and retry:" > /dev/stderr
echo -e " \$ chown -R $PUID:$PGID ./data\n" > /dev/stderr
echo -e " Configure the PUID & PGID environment variables to change the desired owner:" > /dev/stderr
echo -e " https://docs.linuxserver.io/general/understanding-puid-and-pgid\n" > /dev/stderr
echo -e " Hint: some NFS/SMB/FUSE/etc. filesystems force-remap/ignore all permissions," > /dev/stderr
echo -e " leave PUID/PGID unset, or use values the filesystem prefers (defaults to $DEFAULT_PUID:$DEFAULT_PGID)" > /dev/stderr
echo -e " https://linux.die.net/man/8/mount.cifs#:~:text=does%20not%20provide%20unix%20ownership" > /dev/stderr
exit 3
fi
else
# create data directory
@@ -46,30 +84,74 @@ fi
chown $PUID:$PGID "$DATA_DIR"
chown $PUID:$PGID "$DATA_DIR"/*
# also chown BROWSERS_DIR because otherwise 'archivebox setup' wont be able to install chrome
PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-/browsers}"
# also chown BROWSERS_DIR because otherwise 'archivebox setup' wont be able to install chrome at runtime
export PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-/browsers}"
mkdir -p "$PLAYWRIGHT_BROWSERS_PATH/permissions_test_safe_to_delete"
chown $PUID:$PGID "$PLAYWRIGHT_BROWSERS_PATH"
chown $PUID:$PGID "${PLAYWRIGHT_BROWSERS_PATH}/*"
chown $PUID:$PGID "$PLAYWRIGHT_BROWSERS_PATH"/*
rm -Rf "$PLAYWRIGHT_BROWSERS_PATH/permissions_test_safe_to_delete"
# (this check is written in blood, QEMU silently breaks things in ways that are not obvious)
export IN_QEMU="$(pmap 1 | grep qemu | wc -l | grep -E '^0$' >/dev/null && echo 'False' || echo 'True')"
if [[ "$IN_QEMU" == 'True' ]]; then
echo -e "\n[!] Warning: Running $(uname -m) emulated container in QEMU, some things will break!" >&2
echo -e " chromium (screenshot, pdf, dom), singlefile, and any dependencies that rely on inotify will not run in QEMU." >&2
echo -e " See here for more info: https://github.com/microsoft/playwright/issues/17395#issuecomment-1250830493\n" >&2
# (this check is written in blood in 2023, QEMU silently breaks things in ways that are not obvious)
export IN_QEMU="$(pmap 1 | grep qemu >/dev/null && echo 'True' || echo 'False')"
if [[ "$IN_QEMU" == "True" ]]; then
echo -e "\n[!] Warning: Running $(uname -m) docker image using QEMU emulation, some things will break!" > /dev/stderr
echo -e " chromium (screenshot, pdf, dom), singlefile, and any dependencies that rely on inotify will not run in QEMU." > /dev/stderr
echo -e " See here for more info: https://github.com/microsoft/playwright/issues/17395#issuecomment-1250830493\n" > /dev/stderr
fi
# check disk space free on / and /data, warn on <500Mb free, error on <100Mb free
export ROOT_USAGE="$(df --output=pcent,avail / | tail -n 1 | xargs)"
export ROOT_USED_PCT="${ROOT_USAGE%%%*}"
export ROOT_AVAIL_KB="$(echo "$ROOT_USAGE" | awk '{print $2}')"
if [[ "$ROOT_AVAIL_KB" -lt 100000 ]]; then
echo -e "\n[!] Warning: Docker root filesystem is completely out of space! (${ROOT_USED_PCT}% used on /)" > /dev/stderr
echo -e " you need to free up at least 100Mb in your Docker VM to continue:" > /dev/stderr
echo -e " \$ docker system prune\n" > /dev/stderr
df -kh / > /dev/stderr
exit 3
elif [[ "$ROOT_USED_PCT" -ge 99 ]] || [[ "$ROOT_AVAIL_KB" -lt 500000 ]]; then
echo -e "\n[!] Warning: Docker root filesystem is running out of space! (${ROOT_USED_PCT}% used on /)" > /dev/stderr
echo -e " you may need to free up space in your Docker VM soon:" > /dev/stderr
echo -e " \$ docker system prune\n" > /dev/stderr
df -kh / > /dev/stderr
fi
export DATA_USAGE="$(df --output=pcent,avail /data | tail -n 1 | xargs)"
export DATA_USED_PCT="${DATA_USAGE%%%*}"
export DATA_AVAIL_KB="$(echo "$DATA_USAGE" | awk '{print $2}')"
if [[ "$DATA_AVAIL_KB" -lt 100000 ]]; then
echo -e "\n[!] Warning: Docker data volume is completely out of space! (${DATA_USED_PCT}% used on /data)" > /dev/stderr
echo -e " you need to free up at least 100Mb on the drive holding your data directory" > /dev/stderr
echo -e " \$ ncdu -x data\n" > /dev/stderr
df -kh /data > /dev/stderr
sleep 5
elif [[ "$DATA_USED_PCT" -ge 99 ]] || [[ "$ROOT_AVAIL_KB" -lt 500000 ]]; then
echo -e "\n[!] Warning: Docker data volume is running out of space! (${DATA_USED_PCT}% used on /data)" > /dev/stderr
echo -e " you may need to free up space on the drive holding your data directory soon" > /dev/stderr
echo -e " \$ ncdu -x data\n" > /dev/stderr
df -kh /data > /dev/stderr
fi
export ARCHIVEBOX_BIN_PATH="$(which archivebox)"
# Drop permissions to run commands as the archivebox user
if [[ "$1" == /* || "$1" == "bash" || "$1" == "sh" || "$1" == "echo" || "$1" == "cat" || "$1" == "archivebox" ]]; then
# handle "docker run archivebox /some/non-archivebox/command --with=some args" by passing args directly to bash -c
# e.g. "docker run archivebox /venv/bin/archivebox-alt init"
if [[ "$1" == /* || "$1" == "bash" || "$1" == "sh" || "$1" == "echo" || "$1" == "cat" || "$1" == "whoami" || "$1" == "archivebox" ]]; then
# handle "docker run archivebox /bin/somecommand --with=some args" by passing args directly to bash -c
# e.g. "docker run archivebox archivebox init:
# "docker run archivebox /venv/bin/ipython3"
# "docker run archivebox /bin/bash -c '...'"
# "docker run archivebox echo test"
exec gosu "$PUID" bash -c "$*"
# "docker run archivebox cat /VERSION.txt"
exec gosu "$PUID" /bin/bash -c "exec $(printf ' %q' "$@")"
# printf requotes shell parameters properly https://stackoverflow.com/a/39463371/2156113
# gosu spawns an ephemeral bash process owned by archivebox user (bash wrapper is needed to load env vars, PATH, and setup terminal TTY)
# outermost exec hands over current process ID to inner bash process, inner exec hands over inner bash PID to user's command
else
# handle "docker run archivebox add some subcommand --with=args abc" by calling archivebox to run as args as CLI subcommand
# e.g. "docker run archivebox add --depth=1 https://example.com"
# e.g. "docker run archivebox help"
# "docker run archivebox add --depth=1 https://example.com"
# "docker run archivebox manage createsupseruser"
# "docker run archivebox server 0.0.0.0:8000"
exec gosu "$PUID" bash -c "archivebox $*"
exec gosu "$PUID" "$ARCHIVEBOX_BIN_PATH" "$@"
fi
+12 -3
View File
@@ -18,6 +18,7 @@ SUPPORTED_PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7"
TAG_NAME="${1:-$(git rev-parse --abbrev-ref HEAD)}"
VERSION="$(jq -r '.version' < "$REPO_DIR/package.json")"
SHORT_VERSION="$(echo "$VERSION" | perl -pe 's/(\d+)\.(\d+)\.(\d+)/$1.$2/g')"
GIT_SHA=sha-"$(git rev-parse --short HEAD)"
SELECTED_PLATFORMS="${2:-$SUPPORTED_PLATFORMS}"
@@ -25,8 +26,8 @@ SELECTED_PLATFORMS="${2:-$SUPPORTED_PLATFORMS}"
# docker login --username=nikisweeting
# docker login ghcr.io --username=pirate
echo "[^] Building docker image"
./bin/build_docker.sh "$TAG_NAME" "$SELECTED_PLATFORMS"
# echo "[^] Building docker image"
# ./bin/build_docker.sh "$TAG_NAME" "$SELECTED_PLATFORMS"
echo "[^] Uploading docker image"
docker buildx build --platform "$SELECTED_PLATFORMS" --push . \
@@ -34,12 +35,20 @@ docker buildx build --platform "$SELECTED_PLATFORMS" --push . \
-t archivebox/archivebox:$TAG_NAME \
-t archivebox/archivebox:$VERSION \
-t archivebox/archivebox:$SHORT_VERSION \
-t archivebox/archivebox:$GIT_SHA \
-t archivebox/archivebox:latest \
-t archivebox/archivebox:stable \
-t nikisweeting/archivebox \
-t nikisweeting/archivebox:$TAG_NAME \
-t nikisweeting/archivebox:$VERSION \
-t nikisweeting/archivebox:$SHORT_VERSION \
-t nikisweeting/archivebox:$GIT_SHA \
-t nikisweeting/archivebox:latest \
-t nikisweeting/archivebox:stable \
-t ghcr.io/archivebox/archivebox/archivebox:$TAG_NAME \
-t ghcr.io/archivebox/archivebox/archivebox:$VERSION \
-t ghcr.io/archivebox/archivebox/archivebox:$SHORT_VERSION
-t ghcr.io/archivebox/archivebox/archivebox:$SHORT_VERSION \
-t ghcr.io/archivebox/archivebox/archivebox:$GIT_SHA \
-t ghcr.io/archivebox/archivebox/archivebox:latest \
-t ghcr.io/archivebox/archivebox/archivebox:stable
Submodule deb_dist updated: 88e4b7e5a6...92f8fe8f34.
+1 -1
Submodule docker updated: 2cbe77e39c...1fc6dd7f0f.
+8 -13
View File
@@ -8,11 +8,10 @@
# Documentation:
# https://github.com/ArchiveBox/ArchiveBox/wiki/Docker#docker-compose
version: '3.9'
services:
archivebox:
image: ${DOCKER_IMAGE:-archivebox/archivebox:dev}
#image: ${DOCKER_IMAGE:-archivebox/archivebox:dev}
image: archivebox/archivebox:stable
command: server --quick-init 0.0.0.0:8000
ports:
- 8000:8000
@@ -21,7 +20,6 @@ services:
# - ./etc/crontabs:/var/spool/cron/crontabs # uncomment this and archivebox_scheduler below to set up automatic recurring archive jobs
# - ./archivebox:/app/archivebox # uncomment this to mount the ArchiveBox source code at runtime (for developers working on archivebox)
# build: . # uncomment this to build the image from source code at buildtime (for developers working on archivebox)
environment:
- ALLOWED_HOSTS=* # restrict this to only accept incoming traffic via specific domain name
# - PUBLIC_INDEX=True # set to False to prevent anonymous users from viewing snapshot list
@@ -41,7 +39,7 @@ services:
# ...
# add further configuration options from archivebox/config.py as needed (to apply them only to this container)
# or set using `docker compose run archivebox config --set SOME_KEY=someval` (to persist config across all containers)
# For ad-blocking during archiving, uncomment this section and pihole service section below
# networks:
# - dns
@@ -51,13 +49,10 @@ services:
######## Optional Addons: tweak examples below as needed for your specific use case ########
### Example: To run the Sonic full-text search backend, first download the config file to sonic.cfg
# $ curl -O https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/master/etc/sonic.cfg
# After starting, backfill any existing Snapshots into the full-text index:
# $ docker-compose run archivebox update --index-only
### Example: To run the Sonic full-text search backend, uncomment this block and the sonic config above
# sonic:
# image: valeriansaliou/sonic:latest
# image: archivebox/sonic:latest
# expose:
# - 1491
# environment:
@@ -65,8 +60,8 @@ services:
# volumes:
# - ./sonic.cfg:/etc/sonic.cfg:ro
# - ./data/sonic:/var/lib/sonic/store
### Example: To run pihole in order to block ad/tracker requests during archiving,
# uncomment this block and set up pihole using its admin interface
@@ -161,4 +156,4 @@ networks:
ipam:
driver: default
config:
- subnet: 172.20.0.0/24
- subnet: 172.20.0.0/24
+1 -1
Submodule docs updated: bfc5f76a61...a1b69c51ba.
+8
View File
@@ -0,0 +1,8 @@
# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/tmp/tmpe3dawo9u installed on Tue Jun 13 23:21:48 2023)
# (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
@daily cd /data && /usr/local/bin/archivebox add --depth=0 "https://example.com/3" >> /data/logs/schedule.log 2>&1 # archivebox_schedule
@daily cd /data && /usr/local/bin/archivebox add --depth=0 "https://example.com/2" >> /data/logs/schedule.log 2>&1 # archivebox_schedule
@daily cd /data && /usr/local/bin/archivebox add --depth=0 "https://example.com" >> /data/logs/schedule.log 2>&1 # archivebox_schedule
@daily cd /data && /usr/local/bin/archivebox add --depth=0 "update" >> /data/logs/schedule.log 2>&1 # archivebox_schedule
+123 -1312
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -1,13 +1,13 @@
{
"name": "archivebox",
"version": "0.7.1",
"version": "0.7.4",
"description": "ArchiveBox: The self-hosted internet archive",
"author": "Nick Sweeting <archivebox-npm@sweeting.me>",
"repository": "github:ArchiveBox/ArchiveBox",
"license": "MIT",
"dependencies": {
"@postlight/parser": "^2.2.3",
"readability-extractor": "git+https://github.com/ArchiveBox/readability-extractor.git",
"single-file-cli": "^1.1.12"
"readability-extractor": "github:ArchiveBox/readability-extractor",
"single-file-cli": "^2.0.83"
}
}
Generated
+300 -554
View File
File diff suppressed because it is too large. Load diff
+29 -37
View File
@@ -1,26 +1,24 @@
[project]
name = "archivebox"
version = "0.7.1"
version = "0.7.4"
description = "Self-hosted internet archiving solution."
authors = [
{name = "Nick Sweeting", email = "setup.py@archivebox.io"},
{name = "Nick Sweeting", email = "pyproject.toml@archivebox.io"},
]
dependencies = [
# "setuptools>=68.2.2",
"croniter>=0.3.34",
"dateparser>=1.0.0",
"croniter>=6.2.2",
"dateparser>=1.2.2",
"django-extensions>=3.0.3",
"django>=3.1.3,<3.2",
"ipython>5.0.0",
"mypy-extensions>=0.4.3",
"python-crontab>=2.5.1",
"requests>=2.24.0",
"w3lib>=1.22.0",
# "youtube-dl>=2021.04.17",
"yt-dlp>=2021.4.11",
"mypy-extensions>=1.1.0",
"python-crontab>=3.3.0",
"requests>=2.32.5",
"w3lib>=2.3.1",
"yt-dlp>=2025.10.14",
# "playwright>=1.39.0; platform_machine != 'armv7l'",
]
requires-python = ">=3.9"
requires-python = ">=3.9,<3.12"
readme = "README.md"
license = {text = "MIT"}
classifiers = [
@@ -41,6 +39,9 @@ classifiers = [
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: Indexing/Search",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
@@ -56,34 +57,26 @@ classifiers = [
# pdm lock -G:all
# pdm install -G:all
[tool.pdm.dev-dependencies]
build = [
"setuptools",
dev = [
# build
"setuptools>=69.0.3",
"wheel",
"pdm",
# "bottle",
# "stdeb",
# "twine",
]
lint = [
"flake8",
"mypy",
"django-stubs",
]
test = [
"pytest",
]
debug = [
"django-debug-toolbar",
"djdt_flamegraph",
"ipdb",
]
doc = [
"homebrew-pypi-poet>=0.10.0",
# docs
"recommonmark",
"sphinx",
"sphinx-rtd-theme",
]
dev = [
"homebrew-pypi-poet>=0.10.0",
# debug
"django-debug-toolbar",
"djdt_flamegraph",
"ipdb",
# test
"pytest",
# lint
"flake8",
"mypy",
"django-stubs",
]
[tool.pdm.scripts]
@@ -99,7 +92,7 @@ sonic = [
]
ldap = [
# apt install libldap2-dev libsasl2-dev
"python-ldap>=3.4.3",
"python-ldap>=3.4.6",
"django-auth-ldap>=4.1.0",
]
# playwright = [
@@ -126,4 +119,3 @@ Community = "https://github.com/ArchiveBox/ArchiveBox/wiki/Web-Archiving-Communi
Demo = "https://demo.archivebox.io"
Donate = "https://github.com/ArchiveBox/ArchiveBox/wiki/Donations"
+47
View File
@@ -0,0 +1,47 @@
# This file is @generated by PDM.
# Please do not edit it manually.
asgiref==3.11.1
asttokens==3.0.1
certifi==2026.4.22
charset-normalizer==3.4.7
colorama==0.4.6; sys_platform == "win32"
croniter==6.2.2
dateparser==1.2.2
decorator==5.3.1
django==3.1.14
django-auth-ldap==4.1.0
django-extensions==3.1.5
exceptiongroup==1.3.1; python_version < "3.11"
executing==2.2.1
idna==3.15
ipython==8.18.1
jedi==0.19.2
matplotlib-inline==0.2.2
mypy-extensions==1.1.0
parso==0.8.7
pexpect==4.9.0; sys_platform != "win32"
prompt-toolkit==3.0.52
ptyprocess==0.7.0; sys_platform != "win32"
pure-eval==0.2.3
pyasn1==0.6.3
pyasn1-modules==0.4.2
pygments==2.20.0
python-crontab==3.3.0
python-dateutil==2.9.0.post0
python-ldap==3.4.6
pytz==2026.2
regex==2026.1.15
requests==2.32.5
six==1.17.0
sonic-client==1.0.0
sqlparse==0.5.5
stack-data==0.6.3
traitlets==5.15.0
typing-extensions==4.15.0; python_version < "3.11"
tzdata==2026.2; platform_system == "Windows"
tzlocal==5.3.1
urllib3==2.6.3
w3lib==2.3.1
wcwidth==0.7.0
yt-dlp==2025.10.14
-11
View File
@@ -1,11 +0,0 @@
[DEFAULT]
Source: archivebox
Package: archivebox
Package3: archivebox
Suite: focal
Suite3: focal
Build-Depends: debhelper, dh-python, python3-all, python3-pip, python3-setuptools, python3-wheel, python3-stdeb
Depends3: nodejs, wget, curl, git, ffmpeg, yt-dlp, ripgrep, python3-all, python3-pip, python3-setuptools, python3-croniter, python3-crontab, python3-dateparser, python3-django, python3-django-extensions, python3-django-jsonfield, python3-mypy-extensions, python3-requests, python3-w3lib
X-Python3-Version: >= 3.9
XS-Python-Version: >= 3.9
Setup-Env-Vars: DEB_BUILD_OPTIONS=nocheck
+1
View File
@@ -17,6 +17,7 @@ def disable_extractors_dict():
"USE_SINGLEFILE": "false",
"USE_READABILITY": "false",
"USE_MERCURY": "false",
"SAVE_HTMLTOTEXT": "false",
"SAVE_PDF": "false",
"SAVE_SCREENSHOT": "false",
"SAVE_DOM": "false",
+8
View File
@@ -78,6 +78,14 @@ def test_mercury_works(tmp_path, process, disable_extractors_dict):
output_file = archived_item_path / "mercury" / "content.html"
assert output_file.exists()
def test_htmltotext_works(tmp_path, process, disable_extractors_dict):
disable_extractors_dict.update({"SAVE_HTMLTOTEXT": "true"})
add_process = subprocess.run(['archivebox', 'add', 'http://127.0.0.1:8080/static/example.com.html'],
capture_output=True, env=disable_extractors_dict)
archived_item_path = list(tmp_path.glob("archive/**/*"))[0]
output_file = archived_item_path / "htmltotext.txt"
assert output_file.exists()
def test_readability_works_with_wget(tmp_path, process, disable_extractors_dict):
disable_extractors_dict.update({"USE_READABILITY": "true", "USE_WGET": "true"})
add_process = subprocess.run(['archivebox', 'add', 'http://127.0.0.1:8080/static/example.com.html'],
View File
File renamed without changes.
+1
View File
@@ -0,0 +1 @@
../README.md
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.