Compare commits

...
801 Commits
Author SHA1 Message Date
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 7387d4b93c fix pihole compose setup 2023-11-08 00:14:07 -08:00
Nick Sweeting fd9803f3b7 chown PLAYWRIGHT_BROWSERS_PATH in entrypoint to fix archivebox setup 2023-11-07 01:49:48 -08:00
Nick Sweeting c808e53b44 chown PLAYWRIGHT_BROWSERS_PATH after installing so archivebox setup can run 2023-11-07 01:42:16 -08:00
Nick Sweeting 56f27fdb13 remove dev packages from lockfiles 2023-11-03 21:54:17 -07:00
Nick Sweeting 07b9710970 update pip dist script 2023-11-03 21:35:08 -07:00
Nick Sweeting 1323b812ce fix brew release process 2023-11-03 21:30:28 -07:00
Nick Sweeting ebb716514d Update README.md security notice about extractors 2023-11-03 21:17:37 -07:00
Nick Sweeting 5c4a50557b bump brew dist version 2023-11-03 20:31:09 -07:00
Nick Sweeting 7377d9ebc3 bump version to 0.7.1 and fix version autodetection code 2023-11-03 20:07:39 -07:00
Nick Sweeting 907d170614 add pdm lockfile changes 2023-11-03 19:14:42 -07:00
Nick Sweeting 5ce77d3d0b Update pip.yml 2023-11-03 19:12:19 -07:00
Nick Sweeting c6e5a565c0 switch back to locked cache instead of private but put targetarch in cache id to separate them 2023-10-31 19:43:41 -07:00
Nick Sweeting 57f033be60 add targetos to build args 2023-10-31 19:43:01 -07:00
Nick Sweeting db733151f7 bump user agent browser version 2023-10-31 19:16:15 -07:00
Nick Sweeting 7283e9b91d fix docker build tags 2023-10-31 19:11:14 -07:00
Nick Sweeting 00d671240e use private caches for each architecture build so as not to pollute caches with bad binaries 2023-10-31 16:06:19 -07:00
Nick Sweeting 41bd7c6480 fix cache locking errors during parallel builds 2023-10-31 14:59:34 -07:00
Nick Sweeting 2c692191ef fixes for ci 2023-10-31 06:48:55 -07:00
Nick Sweeting bc555cca96 working arm v7 build 2023-10-31 06:28:11 -07:00
Nick Sweeting 12c966477f bump docker build action ci versions 2023-10-31 05:37:26 -07:00
Nick Sweeting 43a020c534 fewer packages 2023-10-31 05:33:30 -07:00
Nick Sweeting 211ca363b5 better dockerfiles caching 2023-10-31 05:31:19 -07:00
Nick Sweeting 562fd5a3bb revert back to python-3.11 base image and add cache dirs for massive build speed increase 2023-10-31 03:58:24 -07:00
Nick Sweeting ce4df6d965 use global pip cache 2023-10-31 03:19:35 -07:00
Nick Sweeting da80a41afb use bookworm-backports for everything 2023-10-31 03:11:53 -07:00
Nick Sweeting 47d9161168 fix Dockerfile 2023-10-31 03:06:02 -07:00
Nick Sweeting aabc89d6a7 add requirements.txt 2023-10-31 01:06:57 -07:00
Nick Sweeting f0d51c377b attempt fix for python build 2023-10-31 01:05:39 -07:00
Nick Sweeting 214016d8a1 fix broken arg 2023-10-31 00:31:14 -07:00
Nick Sweeting b87af19235 use pdm sync instead of pdm install in docker 2023-10-31 00:30:27 -07:00
Nick Sweeting 479a6f44ea dont install site packages as editable 2023-10-31 00:27:31 -07:00
Nick Sweeting 166573d417 autodetect qemu and warn about broken chrome in emulated containers 2023-10-31 00:23:19 -07:00
Nick Sweeting 32e363848e install all optionall dependencies in docker 2023-10-31 00:00:09 -07:00
Nick Sweeting 1bfbf5e9c3 add pip packages build step 2023-10-30 23:58:13 -07:00
Nick Sweeting ce76c324a5 autodetect uid and gid from data dir contents 2023-10-30 23:50:27 -07:00
Nick Sweeting 019c012951 fix entrypoint chowning 2023-10-30 23:42:12 -07:00
Nick Sweeting a677ef7113 fix conditional on architecture 2023-10-30 23:34:26 -07:00
Nick Sweeting 2ab4be71c1 Add instructions to run specific branch in docker compose 2023-10-30 23:33:29 -07:00
Nick Sweeting d46c9b9bc8 bump pip dist folder 2023-10-30 23:28:49 -07:00
Nick Sweeting 6b35c30e17 fix armv7 build for Docker and bare metal 2023-10-30 23:25:51 -07:00
Nick Sweeting 62e077a5bc Merge pull request #1241 from overhacked/fts5-search 2023-10-30 18:49:52 -07:00
Nick Sweeting 8467d89400 Merge pull request #1255 from c01o/fix_python_test 2023-10-30 18:48:28 -07:00
Nick Sweeting 3b005b5faf Add PDM 2023-10-30 18:48:10 -07:00
Nick Sweeting 851f59b4ba Merge pull request #1254 from c01o/dev 2023-10-30 18:44:55 -07:00
Nick Sweeting 9b0b7c6f64 remove setup.py and youtube-dl dependency 2023-10-30 18:40:55 -07:00
Nick Sweeting db360dea1f Fix writing to stderr from docker entrypoint 2023-10-30 15:44:33 -07:00
Nick Sweeting e7795550fc add help message to permissions error output 2023-10-30 15:39:12 -07:00
Nick Sweeting e2bcb19603 Merge pull request #1259 from mAAdhaTTah/fix-readwise-token 2023-10-30 11:59:08 -07:00
mAAdhaTTah 18e89bee3d Fix readwise token
Accidentally shipped this hard-coded. Token is long-size revoked,
but obviously hard to use this way!
2023-10-29 17:27:04 -04:00
Nick Sweeting a680724367 Merge branch 'dev' into search_index_extract_html_text 2023-10-27 23:09:28 -07:00
Nick Sweeting 720061185c Merge pull request #1195 from overhacked/method_allow_deny 2023-10-27 23:05:30 -07:00
c01o bb1646ddc1 Fix python-tests dependency
Starting from b6113cc, it requires `build` package to be built.
2023-10-27 07:21:55 +09:00
c01o 619c5ae6a2 Upgrade test python version
`pyproject.toml` says the minimum requirement is python 3.9, no need to test on 3.7.
Actually `setuptools 68.1.0+` requires `python 3.8+`.
2023-10-27 07:18:46 +09:00
c01o 8c25a81009 Remove playwright from armv7 2023-10-27 05:59:25 +09:00
Nick Sweeting 7a80b61bea Merge pull request #1252 from c01o/patch-1 2023-10-25 03:28:42 -07:00
c01o 08cffb8742 Fix docker build
Lack of `fi` resulted in a syntax error.
Also, change `;` to `||` to ensure it builds successfully on architectures other than `linux/arm/v7`.


```
[2023-10-24T10:46:49.941Z] ------
 > [dev_container_auto_added_stage_label 11/18] RUN mkdir -p "/home/archivebox/.config/chromium/Crash Reports/pending/"     && chown -R archivebox "/home/archivebox/.config"     ; if [[ "linux/amd64" == "linux/arm/v7" ]]; then $exit 0; else exit 1:
0.401 /bin/bash: -c: line 2: syntax error: unexpected end of file
------
WARNING: buildx: git was not found in the system. Current commit information was not captured by the build
[2023-10-24T10:46:49.942Z] Dockerfile-with-features:135
--------------------
 134 |         && ln -s "$CHROME_BINARY" /usr/bin/chromium-browser
 135 | >>> RUN mkdir -p "/home/${ARCHIVEBOX_USER}/.config/chromium/Crash Reports/pending/" \
 136 | >>>     && chown -R $ARCHIVEBOX_USER "/home/${ARCHIVEBOX_USER}/.config" \
 137 | >>>     ; if [[ "$TARGETPLATFORM" == "linux/arm/v7" ]]; then $exit 0; else exit 1
 138 |         # ignore failure for architectures where no playwright release is available yet
--------------------
ERROR: failed to solve: process "/bin/bash -c mkdir -p \"/home/${ARCHIVEBOX_USER}/.config/chromium/Crash Reports/pending/\"     && chown -R $ARCHIVEBOX_USER \"/home/${ARCHIVEBOX_USER}/.config\"     ; if [[ \"$TARGETPLATFORM\" == \"linux/arm/v7\" ]]; then $exit 0; else exit 1" did not complete successfully: exit code: 2
```
2023-10-24 19:58:23 +09: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
Nick Sweeting 2681f752f1 ignore playwright chrome install on arm v7 2023-10-21 14:17:50 -07:00
Nick Sweeting 2928c19119 fix multiarch chromium install 2023-10-20 05:45:08 -07:00
Nick Sweeting 988b6585c9 better multiarch builds 2023-10-20 05:30:45 -07:00
Nick Sweeting 25d2c17bb1 add dev build helper script 2023-10-20 05:10:18 -07:00
Nick Sweeting 015737b1e4 fix buildx helper script 2023-10-20 05:10:03 -07:00
Nick Sweeting bf7a6e4c09 add missing cpython packages needed for arm v8 docker build 2023-10-20 04:33:26 -07:00
Nick Sweeting 63ad43f46c Merge branch 'dev' into method_allow_deny 2023-10-20 04:25:44 -07:00
Nick Sweeting 53355be46a Merge branch 'dev' into fts5-search 2023-10-20 04:23:50 -07:00
Nick Sweeting a58535baff Merge pull request #1249 from ArchiveBox/pdm 2023-10-20 04:23:25 -07:00
Nick Sweeting 82d8662c74 add more readability error output 2023-10-20 04:14:28 -07:00
Nick Sweeting f3e81c1487 bump npm lockfile 2023-10-20 04:09:03 -07:00
Nick Sweeting 76f9b91ed3 dockerfile fixes 2023-10-20 04:08:38 -07:00
Nick Sweeting 6736e63ef0 new Dockerfile layout with better layering 2023-10-20 02:47:34 -07:00
Nick Sweeting 86c662421b show FS_USER in version output debug string 2023-10-20 02:47:23 -07:00
Nick Sweeting 7910a5b34b fix pretty_path printing logic 2023-10-20 02:47:03 -07:00
Nick Sweeting d4ca2d1154 update mercury bin path to postlight parser 2023-10-20 02:46:41 -07:00
Nick Sweeting 75eeb12ebe dont install youtubedl anymore 2023-10-20 02:46:09 -07:00
Nick Sweeting 16796a63fd share PUID and PGID with child procs from entrypoint 2023-10-20 02:45:54 -07:00
Nick Sweeting e0e34e6377 update package.json and npm lockfile 2023-10-20 02:45:37 -07:00
Nick Sweeting 3b3bdab97d update ignore files 2023-10-20 02:45:20 -07:00
Nick Sweeting 63c276a93d redirect add page back to snapshots list automatically 2023-10-19 18:24:19 -07:00
Nick Sweeting 22bcffe5ee remove accidental duplicate template code 2023-10-19 18:24:05 -07:00
Nick Sweeting 85f8583d62 catch ldap configuration and packaging errors and make them non fatal 2023-10-19 18:05:59 -07:00
Nick Sweeting 53cff45ec0 switch from pipenv and setup.py to PDM for python packaging 2023-10-19 17:49:06 -07:00
Nick Sweeting 8caffc4b4e ignore .pdm-python file 2023-10-19 17:09:48 -07:00
Nick Sweeting b6113cc00c use pypa build instead of setup.py build and move ldap to extras 2023-10-19 16:44:48 -07:00
Nick Sweeting 4b26ab9d1f only load image into docker when building, and push on deploy 2023-10-19 16:29:51 -07:00
Nick Sweeting 9fa210515e Bump required python version to 3.9 in stdeb.cfg 2023-10-19 16:22:06 -07:00
Nick Sweeting 748363e685 Bump required Python version to 3.9 2023-10-19 16:21:32 -07:00
Nick Sweeting 6619e8005e Update pip.yml github action python version 2023-10-19 16:20:43 -07:00
Nick Sweeting a61f544547 Update SECURITY.md 2023-10-19 16:18:32 -07:00
Nick Sweeting edde40898b fix markdown underlining everything as link 2023-10-19 12:58:38 -07:00
Nick Sweeting a8ce927937 fix markdown formatting 2023-10-19 12:57:05 -07:00
Nick Sweeting 0e3475a0eb Create SECURITY.md policy 2023-10-18 18:40:58 -07:00
Nick Sweeting 4f655fc4a1 Replace chown of entire data directory when ownership mismatch detected 2023-10-18 18:24:43 -07:00
Nick Sweeting e9c85aa812 Update README.md 2023-10-18 17:53:49 -07:00
Nick Sweeting 6f4a7e4e68 Add ADMIN_USERNAME and ADMIN_PASSWORD to example options 2023-10-18 11:53:13 -07:00
Nick Sweeting 2a1104c76a Merge pull request #1245 from benmuth/add-user-env-vars 2023-10-18 11:50:58 -07:00
Nick Sweeting d286dca925 better to log before doing a thing than after 2023-10-18 11:47:55 -07:00
Ben Muthalaly 9e6a87114b Fix formatting, logging, logic issues 2023-10-18 12:07:36 -05:00
Ben Muthalaly 521ea70e0c Add check for existing user, change varable names 2023-10-18 03:07:54 -05:00
Ross Williams 9b85f35b63 sqlite search: check SQLite version when indexing
If creating the FTS5 tables fails due to a known version
incompatiblity, report the required version to the user.
2023-10-16 14:50:48 -04:00
Ross Williams 1e604a1352 sqlite search: clean up errors and type-checking
Clean up error handling, and report a better error message
on search and flush if FTS5 tables haven't yet been created.

Add some mypy comments to clean up type-checking errors.
2023-10-16 14:31:52 -04:00
Ross Williams adb9f0ecc9 sqlite search: Rename connection to get_connection
`connection` could cause confusion with `django.db.connection` and
`get_connection` is a better callable name.
2023-10-16 13:23:26 -04:00
Ross Williams e0f8eeeaa7 Improve search.backends.sqlite retry logic
Retry with table creation should fail if it is attempted for a second
time.
2023-10-16 13:23:26 -04:00
Ross Williams 8fe5faf4d0 Introduce SQLite FTS5-powered search backend
Use SQLite's FTS5 extension to power full-text search without any
additional dependencies. FTS5 was introduced in SQLite 3.9.0,
[released][1] in 2015 so should be available on most SQLite
installations at this point in time.

[1]: https://www.sqlite.org/changes.html#version_3_9_0
2023-10-16 13:23:26 -04:00
Ross Williams c53ec45a29 WIP: add sqlite search backend boilerplate 2023-10-16 13:23:26 -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
Ben Muthalaly 44a94157be Remove logging of configured username and password 2023-10-15 23:36:47 -05:00
Ben Muthalaly 11d473e536 Add config options to add admin user on first run 2023-10-14 00:38:04 -05: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
Nick Sweeting d7b883b049 fix broken link 2023-10-12 00:22:47 -07:00
Nick Sweeting dcef217e5e Merge pull request #1242 from benmuth/fix-titles-with-empty-tag 2023-10-09 21:39:26 -07:00
Ben Muthalaly 77917e9b55 Fix HTML title parsing bugs.
This slightly modifies the HTML_TITLE_REGEX to fix two parsing errors.
The first occurred when title tags were empty (e.g. "<title></title>")
which was parsed as "</title". The second occurred when titles were a
single character (e.g. "<title>A</title>") which was not matched by the
regex, and so would fall back to link.base_url.

Now when tags are empty, it falls back to link.base_url, and single
character titles are parsed correctly.

The way the regex works now is still a bit wonky for some edge cases.
I couldn't find any cases of incorrect behavior, but it still might be
worth reworking more completely for robustness.
2023-10-09 02:00:01 -05:00
Nick Sweeting 4950cee3b6 Merge pull request #1229 from jamesob/dev 2023-09-18 12:19:00 -07:00
James O'Beirne b28b3b7e67 README: update outdated links
Most frustratingly, the outdated docker-compose link prompts users to
download an older version of the docker-compose.yml file, which packages
a broken YouTube retrieval method.
2023-09-18 10:37:35 -04:00
Nick Sweeting 5c1a14e4f2 ignore errors while getting system user name 2023-09-14 03:39:44 -07:00
Nick Sweeting ffe2968e4f improve some comments 2023-09-14 02:41:27 -07:00
Nick Sweeting f809efce4d Merge pull request #996 from barthalion/dev 2023-09-03 21:40:49 -07:00
Nick Sweeting aaca74f6a8 only start parsing json after the first open brace 2023-09-03 21:40:12 -07:00
Nick Sweeting cd9f228b2f Merge pull request #1214 from DanielBatteryStapler/DanielBatteryStapler-patch-1 2023-09-03 21:25:12 -07:00
Nick Sweeting 16d278fbdb Merge pull request #1168 from mAAdhaTTah/add-readwise-reader 2023-09-03 21:24:49 -07:00
Nick Sweeting 110a22ee32 Merge branch 'dev' into DanielBatteryStapler-patch-1 2023-08-31 15:20:46 -07:00
Nick Sweeting 73a5f74d38 update default YOUTUBEDL_ARGS to fix subs and filesize 2023-08-31 15:17:45 -07:00
Nick Sweeting 86366d5640 Update logging_util.py to fix generator subscripting error 2023-08-31 15:12:43 -07:00
Nick Sweeting a837f870af Merge pull request #1221 from spresse1/update-singlefile 2023-08-29 17:04:04 -07:00
spresse1 c8597a7fa1 Update singlefile to the latest version and switch it to single-file-cli. Unfortunately, this requires a rewrite of NPM dependency files. 2023-08-29 20:28:48 +02:00
Nick Sweeting 62fb56354b Merge pull request #1219 from spresse1/chrome-cleanup 2023-08-28 20:02:22 -07:00
spresse1 603ce7ec10 After a timeout, chrome will leave behind a SingletonLock, which prevents future instances of chrome from starting. When an extractor fails due to a timeout, remove this file. 2023-08-28 17:27:03 +02:00
Nick Sweeting 0b6064b7dd Update docker_entrypoint.sh to use /bin/bash 2023-08-22 16:35:43 -07:00
root 23f086aa40 add LDAP support 2023-08-17 19:51:02 -05:00
Nick Sweeting 00ecf57b0f Merge pull request #1211 from DanielBatteryStapler/DanielBatteryStapler-patch-1 2023-08-15 21:34:53 -07:00
DanielBatteryStapler 94dacc49c7 Fix archive_org icon "exists" 2023-08-15 23:49:54 -04:00
Nick Sweeting 68e936e7c2 Merge pull request #1186 from ArchiveBox/dependabot/npm_and_yarn/word-wrap-1.2.4 2023-08-13 16:43:09 -07:00
Nick Sweeting a7d7644dca Merge pull request #1205 from overhacked/fix_url_regex_hyphen 2023-08-09 14:20:07 -07:00
Ross Williams c039ef05b3 Fix hyphen placement in util.URL_REGEX
Incorrect hyphen placement in `URL_REGEX` was allowing it to match more
characters than intended. In a regex character class, a literal hyphen
can only appear as the first character in the class, or it will be
interpreted as the delimiter of a range of characters.

The issue fixed here caused the range of characters from `[$-_]`
be treated as valid URL characters, instead of the intended set of three
characters `[-_$]`. The incorrect range interpretation inadvertantly
included most ASCII punctuation, most importantly the angle brackets,
square brackets, and single quote that the expression uses
to mark the end of a match.

This causes the expression to match a URL that has a "hostname" portion
beginning with one of the intended "stop parsing" characters. For
example:

```
https://<b>www</b>.example.com/  # MATCHES but should not
https://[for example]            # MATCHES but should not
scheme='https://'                # MATCHES, including final quote, but should not
```

Some test cases have been added to the `URL_REGEX` assert in
archivebox.parsers to cover this possibility.
2023-08-08 15:24:16 -04:00
Ross Williams 2076474252 Drop use of TypeAlias to maintain Python 3.9 compat
TypeAlias annotation was introduced in Python 3.10, and is not strictly
necessary. Drop use of it to maintain Python 3.9 compatibility.
2023-08-02 10:56:48 -04:00
Ross Williams b44f7e68b1 Add URL-specific method allow/deny lists
Allows enabling only allow-listed extractors or disabling specific
deny-listed extractors for a regular expression matched against an added
site's URL.
2023-08-02 09:36:40 -04:00
Ross Williams 46e80dd509 Rename URL_(WHITE|BLACK)LIST to URL_(ALLOW|DENY)LIST
Retain aliases for old configuration files
2023-08-02 09:31:48 -04:00
Nick Sweeting b773041952 Merge pull request #1199 from overhacked/chrome_version_detection_fix 2023-08-01 10:14:18 -07:00
Nick Sweeting 5b7ecfc872 Merge pull request #1197 from overhacked/bin_version_env_fix 2023-08-01 10:12:59 -07:00
Ross Williams d0e65eba7f More reliably detect Google Chrome version number
Previous method was splitting on the first whitespace, and missing the
version number when it appeared as `"Google Chrome 115.0.234.2342"`
instead of, i.e. `"Chromium 115.0.234.8283"`.

This commit changes the version detection to regex search for
whitespace, then one or more digits followed by a period, then at least
one more digit. Only the first sequence of digits is captured. Unless
Chrome radically changes their version numbering, this should capture
the first group of digits after the reported browser name, which would
be the major version.
2023-07-31 15:34:58 -04:00
Ross Williams 9d9872d325 bin_version means to modify, not replace environ
the `bin_version` function means to modify the environment,
not replace it entirely. Fixes bugs that occur when it wipes out the
PATH environment variable, such as when running in a virtual
environment.
2023-07-31 11:36:34 -04:00
Nick Sweeting 3e5e9c7a41 Merge pull request #1194 from wogong/patch-1 2023-07-29 09:29:34 -07:00
Zhen 3e9e221232 Fix Instapaper export link in README.md
Original link to Instapaper export `https://www.instapaper.com/user/export` is broken: `405: Method Not Allowed`.
2023-07-28 07:58:58 +08:00
dependabot[bot] 0bf739b736 Bump word-wrap from 1.2.3 to 1.2.4
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-19 02:03:00 +00:00
Nick Sweeting 40ddd33602 Update README.md 2023-07-07 09:37:42 -07:00
mAAdhaTTah 181501fd36 Add Readwise Reader API parser
Implemented similar to the Pocket API.
2023-07-02 11:20:58 -04:00
Nick Sweeting 0d26538a4b Update README.md example commands to use new docker compose format 2023-06-13 17:46:32 -07:00
Nick Sweeting 37d238cd31 Update README.md 2023-06-13 17:43:40 -07:00
Nick Sweeting 2580f76a2e Update README.md 2023-06-13 17:41:08 -07:00
Nick Sweeting 571131d5f3 Update README.md to simplify intro instructions 2023-06-13 17:35:00 -07:00
Nick Sweeting 733dbfa1f3 Update scheduler to persist single shared contab via volume instead of requiring separate container for each job 2023-06-13 17:13:55 -07:00
Nick Sweeting 58d784cdd8 limit nginx config to only serve archive directory instead of main data folder root 2023-06-13 16:43:37 -07:00
Nick Sweeting 0e0b06bef1 Merge pull request #1159 from ArchiveBox/pirate-patch-1 2023-06-13 05:49:58 -07:00
Nick Sweeting 406e2b681d Update docker-compose.yml scheduled task image and container name 2023-06-13 05:49:22 -07:00
Nick Sweeting 347b8d977d Merge pull request #1154 from sissbruecker/fix/oneshot_exit_code 2023-05-30 17:34:25 -07:00
Sascha Ißbrücker 7bf4f40da0 just use out_dir 2023-05-29 10:03:49 +02:00
Sascha Ißbrücker 40c122515a fix: make oneshot command return successful exist code 2023-05-29 10:01:27 +02:00
Nick Sweeting 32eef51081 Merge pull request #1153 from ArchiveBox/pirate-patch-1
Add links to new social media profiles under Further Reading
2023-05-27 06:08:25 -07:00
Nick Sweeting d6780d1ab7 Add links to new social media profiles under Further Reading 2023-05-27 06:08:04 -07:00
Nick Sweeting dbe1cd8dc0 Merge pull request #1152 from jess-sol/patch-1 2023-05-26 21:34:15 -07:00
J aa5533b80f Update export_browser_history.sh
Add tags to export of Firefox bookmarks. Ignore invalid bookmarks such as 'javascript:' or 'place:'
2023-05-25 09:08:13 -06:00
Nick Sweeting 081ba7e81b Merge pull request #1147 from mrled/set-app-permissions-dockerfile 2023-05-06 00:20:50 -07:00
Micah R Ledbetter 3edb319ba9 Set /app permissions in the Dockerfile
Add a note in readme to be careful of permissions with --reload
2023-05-05 23:47:02 -05:00
Nick Sweeting 5bbc5228cc Merge pull request #1146 from mrled/add-favicon-provider 2023-05-05 19:00:15 -07:00
Micah R Ledbetter 1e50ca243e Add FAVICON_PROVIDER option for custom favicon service 2023-05-05 20:42:36 -05:00
Nick Sweeting a1e2fce405 Update debian CI builds to use ubuntu 22.04 and actions/checkout@v3 2023-04-18 22:17:58 -07:00
Nick Sweeting 32fac5999e Merge pull request #1131 from dcalano/dev 2023-03-30 13:14:17 -06:00
David Calano f48e48e6da Fix for Issue #1008
- Added missing decode() when setting pkg_path variable
2023-03-29 01:48:12 -04:00
Nick Sweeting f86891bc33 Merge pull request #1128 from tejr/dev 2023-03-26 18:46:06 -06:00
Tom Ryder 53af810ff8 Add missing closing quote to style attribute 2023-03-27 10:54:04 +13:00
Nick Sweeting 829e377890 Merge pull request #1123 from fa0311/dev 2023-03-18 08:07:23 -06:00
ふぁ 44a5a5ed7e add explicitly specify --headless=new
Signed-off-by: ふぁ <yuki@yuki0311.com>
2023-03-17 19:30:14 +09:00
Nick Sweeting f5e631dbfa Merge pull request #1120 from fa0311/dev 2023-03-15 17:05:46 -06:00
Nick Sweeting 9f42a3bf29 fix whitespace 2023-03-15 16:01:02 -07:00
ふぁ d77c770c47 add CHROME_TIMEOUT args
Signed-off-by: ふぁ <yuki@yuki0311.com>
2023-03-14 20:29:41 +09:00
Nick Sweeting cc53eceda2 recreate dockerx builder if missing platforms 2023-03-13 06:55:28 -06:00
Nick Sweeting e5eee4e0b6 add cross-platform docker build support 2023-03-13 11:57:20 +00:00
Nick Sweeting 606fa397a4 disable passing timeout arg to chrome because v111 is crashing when passed 2023-03-13 10:50:18 +00:00
Nick Sweeting 1f1c70a8b1 remove --single-process from chrome args and add some rendering optimization args 2023-03-13 10:49:57 +00:00
Nick Sweeting 9599845b56 ensure DOM HTML dump is non-zero length file when retrying 2023-03-13 10:49:26 +00:00
Nick Sweeting f94e8cd9b2 bump docker base image version and node version 2023-03-13 10:49:02 +00:00
Nick Sweeting d72b1495d0 Merge pull request #1115 from fa0311/dev 2023-03-08 23:30:51 -08:00
Nick Sweeting a580e4627d add instructions on how to install to comment 2023-03-08 23:30:30 -08:00
ふぁ 67e9c77ab7 remove dash
Signed-off-by: ふぁ <yuki@yuki0311.com>
2023-03-09 14:56:53 +09:00
ふぁ 0e6fdb854f add systemd config
Signed-off-by: ふぁ <yuki@yuki0311.com>
2023-03-08 13:17:47 +09:00
Nick Sweeting c2bc6c6781 add ssl to setup script curl usage 2023-02-28 10:26:04 -08:00
Nick Sweeting 8208a1ca89 Merge pull request #1104 from ArchiveBox/revert-1103-codesee-arch-diagram-workflow-1676842676316 2023-02-19 14:48:34 -08:00
Nick Sweeting 30cb3a38bc Revert "Install the CodeSee workflow." 2023-02-19 14:48:23 -08:00
Nick Sweeting ae26c71954 Update README.md 2023-02-19 14:02:18 -08:00
Nick Sweeting 135ad7b75d Merge pull request #1103 from ArchiveBox/codesee-arch-diagram-workflow-1676842676316 2023-02-19 13:38:38 -08:00
codesee-maps[bot] 9b38de49ec Install the CodeSee workflow. Learn more at https://docs.codesee.io 2023-02-19 21:37:56 +00:00
Nick Sweeting 9952724688 fix pypi test link 2023-01-31 00:23:34 -08:00
Nick Sweeting 992a02f87b fix spelling typos 2023-01-30 23:25:59 -08:00
Nick Sweeting 9c763cbda9 Update README.md 2023-01-30 23:11:09 -08:00
Nick Sweeting 7fb65a985b simplify readme shields 2023-01-30 23:08:44 -08:00
Nick Sweeting b0a6b081a0 another newline sigh 2023-01-30 17:19:09 -08:00
Nick Sweeting b8bd9bf598 add newline after npm note 2023-01-30 17:18:43 -08:00
Nick Sweeting 61ad22516b move npm/node note location 2023-01-30 17:17:28 -08:00
Nick Sweeting 8e1cfe0cf8 add NodeJS and npm install note for debian/ubuntu method 2023-01-30 17:15:04 -08:00
Nick Sweeting 5408028265 remove broken lgtm shields 2023-01-11 02:40:01 -08:00
Nick Sweeting 7cb5d8aebe Merge pull request #1081 from ArchiveBox/dependabot/npm_and_yarn/moment-2.29.4 2023-01-09 18:23:03 -08:00
Nick Sweeting 0487cb9733 Merge pull request #866 from ajgon/feat/reverse-proxy-auth 2023-01-09 18:22:46 -08:00
Nick Sweetinganddugite-code dca69933eb Update archivebox/config.py
Co-authored-by: dugite-code <dugite-code@users.noreply.github.com>
2023-01-09 18:22:01 -08:00
Nick Sweeting 2538b170c7 Merge branch 'dev' into feat/reverse-proxy-auth 2023-01-09 18:20:45 -08:00
Nick Sweeting 0cbeeb4346 Merge pull request #1021 from renaisun/dev 2023-01-09 18:17:39 -08:00
dependabot[bot] b26ecc2b7f Bump moment from 2.29.3 to 2.29.4
Bumps [moment](https://github.com/moment/moment) from 2.29.3 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.3...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-10 01:58:35 +00:00
Nick Sweeting 8a96563169 Merge pull request #1026 from turian/feature/kludge-984-UTF8-bug 2023-01-09 17:57:49 -08:00
Nick Sweeting d4c18e4142 Merge pull request #1068 from mwestza/freebsd13 2022-12-30 08:13:48 -08:00
Matthew West 86208c0267 get setup.sh to run on FreeBSD again (13.x) 2022-12-25 17:36:01 +00:00
Joseph Turian 07de4a79a1 Merge branch 'dev' into feature/kludge-984-UTF8-bug 2022-12-20 11:39:01 +01:00
Nick Sweeting e114b1f6dc Merge pull request #1027 from turian/feature/migrations-0021_auto_20220914_0934.py 2022-11-27 19:28:55 -08:00
Nick Sweeting f17f4c6bb6 Merge pull request #1023 from turian/feature/better-dockerfil 2022-11-27 19:28:32 -08:00
Nick Sweeting f86c9fc130 Merge pull request #1048 from EsEnZeT/dev 2022-11-22 16:17:29 -08:00
SnZ 2db830c6a8 Method typo?
Fixes '[Errno 2] No such file or directory' error during add
2022-11-20 01:51:16 +01:00
Nick Sweeting 9b65639a59 Merge pull request #1020 from turian/feature/kludge-984-UTF8-bug 2022-11-02 02:12:47 -07:00
Joseph Turian a26a91d09f Merge branch 'feature/migrations-0021_auto_20220914_0934.py' into feature/kludge-984-UTF8-bug 2022-09-14 09:44:55 +00:00
Joseph Turian 22d8e57637 Add missing migration 0021 2022-09-14 09:36:17 +00:00
Joseph Turian 30947aeb07 yt-dlp flag cleanup 2022-09-14 06:29:57 +02:00
Joseph Turian f729bbe122 yt-dlp fixes 2022-09-14 06:27:58 +02:00
Joseph Turian caa8b782fb Remove tab 2022-09-12 21:52:01 +00:00
Joseph Turian 4ce392846c Fix actions 2022-09-12 21:51:15 +00:00
Joseph Turian 081a12b079 Add ts 2022-09-12 21:32:47 +00:00
Joseph Turian daef48e59b flake8 2022-09-12 21:31:33 +00:00
Joseph Turian 983f485cc0 flake8 2022-09-12 21:29:43 +00:00
Joseph Turian b864c38d9e Don't be strict on unicode errors 2022-09-12 20:40:45 +00:00
Joseph Turian dba423a568 A few more youtube-dl tweaks 2022-09-12 20:36:23 +00:00
Joseph Turian f5f7aff3b4 Added yt-dlp everywhere 2022-09-12 20:34:02 +00:00
renaisun 0ea955b3ed add a missing comma 2022-09-12 09:08:28 +08:00
notevenaperson 40659b5e9d singlefile.py: Code to ensure options are deduplicated 2022-09-12 09:08:28 +08:00
Joseph Turian 35a052ae8b Another comment 2022-09-11 22:13:22 +02:00
Joseph Turian cc56785695 Added standalone dockerfile instructions 2022-09-11 22:11:13 +02:00
Joseph Turian e41f313fa3 Change actions 2022-09-11 12:31:11 +02:00
Joseph Turian 2b58cce43f Attempted to warn on #984 and #1014 2022-09-11 12:19:16 +02:00
Bartłomiej Piotrowski eb97fd427b Skip first line of the "JSON" file
ArchiveBox moves the file to parse to the sources directory and adds the
original filename at the top, making the file invalid.
2022-07-05 10:56:40 +02:00
Nick Sweeting 03eb7e5875 Update config.py 2022-06-09 01:04:55 -07:00
renaisun 8899fe0b92 Add SINGLEFILE_ARGS to control single-file arguments 2022-06-09 14:35:48 +08:00
Nick Sweeting d586a8babc show mount paths with at symbol in version output 2022-06-08 20:22:58 -07:00
Nick Sweeting 319ea481b8 Update config.py 2022-06-08 20:17:38 -07:00
Nick Sweeting 01555dfe34 Update main.py 2022-06-08 20:17:31 -07:00
Nick Sweeting 2bbc742017 typo fix 2022-06-08 20:16:08 -07:00
Nick Sweeting e2fa68dba6 resolve config paths before using 2022-06-08 20:15:22 -07:00
Nick Sweeting ccce4a6a2f use new is_mount and COMMIT_HASH config options 2022-06-08 20:13:22 -07:00
Nick Sweeting 9f90a2d60d disable unused sqlite3 stuff 2022-06-08 20:12:55 -07:00
Nick Sweeting c78a2edc42 add is_mount and COMMIT_HASH to config.py 2022-06-08 20:04:01 -07:00
Nick Sweeting 375ba9d135 Update settings.py 2022-06-08 20:00:29 -07:00
Nick Sweeting ae5c8f2bf8 fix newline included in commit hash 2022-06-08 19:57:38 -07:00
Nick Sweeting cb3ebbe69a fix git commit hash location 2022-06-08 19:52:48 -07:00
Nick Sweeting 413aa2ef04 fix commit hash detection 2022-06-08 19:51:46 -07:00
Nick Sweeting 33ec2117e9 Update main.py 2022-06-08 19:50:45 -07:00
Nick Sweeting dd29e1bf78 clean up first line of CLI version output for easier downstream parsing 2022-06-08 19:46:09 -07:00
Nick Sweeting 0c6d4c82c3 Update config.py 2022-06-08 19:11:02 -07:00
Nick Sweeting f9c5808940 Update config.py 2022-06-08 19:09:11 -07:00
Nick Sweeting 5509b5cd8b Update main.py 2022-06-08 19:08:33 -07:00
Nick Sweeting 19b88d30b2 fix missing brace 2022-06-08 19:06:03 -07:00
Nick Sweeting 31d5fbbf17 Update config.py 2022-06-08 19:04:06 -07:00
Nick Sweeting 6b019da3e9 Update config.py 2022-06-08 19:01:55 -07:00
Nick Sweeting c752c7053d Update main.py 2022-06-08 18:59:08 -07:00
Nick Sweeting f9c82841ad fix sqlite option detection 2022-06-08 18:58:15 -07:00
Nick Sweeting 1fd5830f58 enforce UTC timezone on server 2022-06-08 18:41:22 -07:00
Nick Sweeting 3e3c011f86 enforce UTC timezone on server 2022-06-08 18:40:48 -07:00
Nick Sweeting e06717419c fix sqlite3 version detection 2022-06-08 18:35:31 -07:00
Nick Sweeting d0f129295f move sqlite3 checks up a level 2022-06-08 18:29:53 -07:00
Nick Sweeting 0c7d7deb32 add missing brace 2022-06-08 18:26:42 -07:00
Nick Sweeting ca16c88a3d show PUID, PGID, ENFORCE_ATOMIC_WRITES, and OUTPUT_PERMISSIONS in version output header 2022-06-08 18:24:58 -07:00
Nick Sweeting 89175ccb22 check SQLite3 version and enabled extensions on startup 2022-06-08 18:24:17 -07:00
Nick Sweeting c245d36e44 add PUID and PGID as config options in archivebox 2022-06-08 17:42:52 -07:00
Nick Sweeting 880ab07f10 Update README.md 2022-06-08 16:59:42 -07:00
Nick Sweeting c7370aaebc Merge pull request #974 from m3nu/misc/add-pikapods-hosting 2022-05-11 13:50:22 -07:00
Manu 5a7828fb08 Add PikaPods as hosting option 2022-05-10 15:28:10 +04:00
Nick Sweeting eb77908273 add missing migration 2022-05-09 23:59:42 -07:00
Nick Sweeting c5fc3e1e65 --ammend 2022-05-09 23:59:27 -07:00
Nick Sweeting 0b4df768ba hack to check for generator type cause too lazy to import 2022-05-09 23:50:56 -07:00
Nick Sweeting 9d6a59485c add missing migration 2022-05-09 23:45:01 -07:00
Nick Sweeting 74b3c59314 fix helper install script handling of python3 version 2022-05-09 21:58:30 -07:00
Nick Sweeting 5e4ddbbf25 fix mercury bin parsing back 2022-05-09 21:58:17 -07:00
Nick Sweeting e96c1bcf13 bump mercury parser to git head version 2022-05-09 21:48:41 -07:00
Nick Sweeting d581a5081f correctly handle bytes strings in hints 2022-05-09 21:29:37 -07:00
Nick Sweeting a6767671fb append content of referenced files to imports 2022-05-09 21:21:39 -07:00
Nick Sweeting f6d6a06c78 always show all totals in log output 2022-05-09 21:21:26 -07:00
Nick Sweeting d05510f844 fix version string parsing on macOS in some cases where LANG C is not supported 2022-05-09 21:21:08 -07:00
Nick Sweeting 4b8b17e788 add update flag support to archivebox schedule 2022-05-09 20:18:43 -07:00
Nick Sweeting 8cfe6f4afb cleanup update flag handling and show better logging to clarify when its working 2022-05-09 20:15:55 -07:00
Nick Sweeting 38e54b93fe allow parsing to continue even when fetching URL contents fails 2022-05-09 19:56:24 -07:00
Nick Sweeting ecbcb6a1b3 fix bracing in template tag for PREVIEW_ORIGINALS 2022-05-09 19:56:08 -07:00
Nick Sweeting 8ebf3e2f93 add config option PREVIEW_ORIGINALS to hide original iframes in snapshot detail pages 2022-05-09 19:31:41 -07:00
Nick Sweeting e83132c24e bump js lockfile 2022-05-09 19:24:05 -07:00
Nick Sweeting 5ddffae83e bump sonic version to support ARM 2022-05-09 19:14:26 -07:00
Nick Sweeting acd53c854d handle new wallabag export format with newlines mid-tag attributes 2022-05-09 19:07:48 -07:00
Nick Sweeting 808ae1a351 Merge pull request #969 from ArchiveBox/dependabot/npm_and_yarn/node-fetch-2.6.7
Bump node-fetch from 2.6.1 to 2.6.7
2022-05-09 18:38:23 -07:00
Nick Sweeting 950b5cbbb6 Merge pull request #924 from prnake/dev
improve title extractor
2022-05-09 18:38:12 -07:00
Nick Sweeting 6e66863871 add max 5s writing delay for concurrent writers and flush WAL slower 2022-05-09 18:36:40 -07:00
Nick Sweeting c5ccb05d4a update Dockerfile buildx instructions to exclude intel/386 2022-04-21 07:35:34 -07:00
Nick Sweeting ed9ef588d9 add instructions for multi-arch build at top of Dockerfile 2022-04-21 07:29:27 -07:00
dependabot[bot] 66f79fc041 Bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-21 14:12:19 +00:00
Nick Sweeting 2b35d14c0f update npm package versions 2022-04-21 07:11:35 -07:00
Nick Sweeting 57df65f28f use yt-dlp for media archiving instead of youtube-dl 2022-04-21 07:11:35 -07:00
Nick Sweeting eb81d41f84 bump Dockerfile base image version and install yt-dlp 2022-04-21 07:11:35 -07:00
Nick Sweeting 1d2fd83b1b Merge pull request #967 from rossvor/dev 2022-04-20 17:22:51 -07:00
Ross c63822a5e5 Fix missing input redirection in a hint text 2022-04-19 22:25:49 +01:00
Nick Sweeting 9e74676af3 Update README.md 2022-04-12 20:24:44 -04:00
Nick Sweeting 7d4033e73b Add links to DB troubleshooting and upgrading instructions 2022-04-12 18:27:22 -04:00
Nick Sweeting 183253e983 Update README.md 2022-04-12 18:24:24 -04:00
Nick Sweeting f1095f547c Merge pull request #961 from kusold/patch-1 2022-04-06 13:39:26 -07:00
Mike Kusold 4febbacef6 Fix entrypoint
docker-compose requires `$$` for env vars that are supposed to be executed inside the container.
2022-04-05 20:58:40 -06:00
Mike Kusold dfef21a814 Update pywb entrypoint
wb-manager first needs you to init the collection. If you attempt to init a collection that already exists, it exits with a `2`.

Also, `wayback --proxy` requires an argument, so I remove the proxy argument.
2022-04-05 20:42:29 -06:00
Igor Rzegocki d4f534e612 add LOGOUT_REDIRECT_URL 2022-03-31 21:40:14 +02:00
Nick Sweeting d268513877 cleanup example configs 2022-03-22 20:55:17 -04:00
Nick Sweeting 0ec5ba0595 fix pihole admin port format in docker-compose 2022-03-16 14:15:37 -07:00
Nick Sweeting a15350cc77 Update docker-compose.yml 2022-03-16 14:14:35 -07:00
Nick Sweeting 5cd2b328c0 Add example pihole docker-compose config for ad/tracker blocking during archiving 2022-03-16 14:12:18 -07:00
Nick Sweeting 0612ff751a Merge pull request #923 from CrazyPython/patch-1 2022-03-05 03:21:09 -05:00
Nick Sweeting feafe9a2a2 Merge pull request #936 from pellaeon/pr-binversion 2022-03-05 03:20:04 -05:00
Nick Sweeting 51ad780047 Merge pull request #932 from hyfen/patch-1 2022-03-05 03:17:49 -05:00
Nick Sweeting dc8bd1d78f Merge pull request #942 from xfq/archive_dot_org 2022-03-05 03:14:32 -05:00
Fuqiao Xue 29ee73660b Fix broken URI fragment 2022-03-03 21:04:19 +08:00
Pellaeon Lin 5e9d05483e Fix bin_version: set LANG=C when calling executables to avoid parsing
localized output.
2022-02-24 17:01:00 +08:00
Andrew Louis 532aeca8a0 Fix typo in README.md 2022-02-18 13:03:11 -05:00
prnake 011bd104cb remove unused import 2022-02-09 10:48:51 +08:00
papersnake de8e22efb7 improve title extractor 2022-02-08 23:17:52 +08:00
James 6dae0cc252 Fix arch installation command
The linked package is on AUR, typically installed with yay. "pacman install" is not a valid command.
2022-02-06 15:39:44 -05:00
Nick Sweeting bf432d4931 Merge pull request #918 from akhilleusuggo/patch-1
typo
2022-01-30 18:55:11 -05:00
akhilleusuggo 9231e7f091 typo
ERROR: The Compose file './docker-compose.yml' is invalid because:
services.pywb.volumes contains an invalid type, it should be an array
2022-01-27 19:24:59 +01:00
Nick Sweeting 663918a372 Merge pull request #911 from hannah98/dev 2022-01-10 20:54:44 -05:00
Nick Sweeting 666ab20df5 Update archivebox/config.py 2022-01-10 20:42:09 -05:00
hannah98 fc3d2bb4dc rename TAG_SEPARATORS to TAG_SEPARATOR_PATTERN 2022-01-06 14:14:41 +00:00
hannah98 049f88def9 Added TAG_SEPARATORS option to supply a regex of characters to use when splitting tags 2021-12-30 20:19:48 +00:00
Nick Sweeting 745f6573c1 bump node dependencies 2021-12-23 16:03:51 -05:00
Nick Sweeting 7a7217abb4 Bump mercury-parser version 2021-12-23 15:59:50 -05:00
Nick Sweeting 0aa75c861b also install npm packages when testing brew package 2021-12-23 15:56:28 -05:00
Nick Sweeting 9850333b88 Update _config.yml 2021-12-23 15:43:13 -05:00
Nick Sweeting f144f7b5c6 bump brew disk version 2021-12-23 15:39:49 -05:00
Nick Sweeting 2c7be14bb7 Merge pull request #909 from ArchiveBox/dependabot/npm_and_yarn/jszip-3.7.1 2021-12-23 12:55:25 -05:00
Nick Sweeting d6a856264f Merge pull request #910 from ArchiveBox/dependabot/npm_and_yarn/ansi-regex-5.0.1 2021-12-23 12:55:15 -05:00
dependabot[bot] 2c3a25765f Bump ansi-regex from 5.0.0 to 5.0.1
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-23 17:49:43 +00:00
dependabot[bot] 1f7e15e3c0 Bump jszip from 3.6.0 to 3.7.1
Bumps [jszip](https://github.com/Stuk/jszip) from 3.6.0 to 3.7.1.
- [Release notes](https://github.com/Stuk/jszip/releases)
- [Changelog](https://github.com/Stuk/jszip/blob/master/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.6.0...v3.7.1)

---
updated-dependencies:
- dependency-name: jszip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-23 17:49:42 +00:00
Nick Sweeting 559c395c1e run archivebox version after homebrew bottle build 2021-12-23 12:49:03 -05:00
Nick Sweeting d7f01922f3 fix direct assignment of tags to many-to-many set 2021-12-23 12:29:17 -05:00
Nick Sweeting b1b7ee2b85 Update sql.py 2021-12-23 12:17:55 -05:00
Nick Sweeting f363738aae Merge pull request #908 from hannah98/dev 2021-12-21 00:24:48 -05:00
hannah98 4b8962b60b Fix #725 - correctly parse tags on json import 2021-12-20 08:58:58 -06:00
Nick Sweeting 989937796a Merge pull request #906 from tuupola/bug-901 2021-12-19 23:46:27 -05:00
Mika Tuupola f14a861605 Change logfile open to write mode only 2021-12-19 23:17:33 +02:00
Nick Sweeting ea0d9c1d99 Update README.md 2021-12-17 17:55:40 -05:00
Nick Sweeting e3d4d0aa91 Merge pull request #904 from TheCakeIsNaOH/hints-string 2021-12-17 16:53:36 -05:00
TheCakeIsNaOH decab91ea2 (#847) Decode error output hints to string if needed 2021-12-16 16:46:12 -06:00
Nick Sweeting b279c30b8d Update docker-compose.yml 2021-12-16 05:18:33 -05:00
Nick Sweeting 63fc835a70 link to archivebox-exporter browser extension 2021-12-06 16:23:31 -05:00
Nick Sweeting b40a1e19d1 comment out healthcheck 2021-12-02 21:03:19 -05:00
Nick Sweeting 84b927e3e5 change header title 2021-11-16 21:13:19 -05:00
Nick Sweeting 44f5338470 fix typo in pocket_api articl variable name 2021-11-12 19:23:47 -05:00
Nick Sweeting 8878dcc5e8 Merge pull request #843 from bltavares/patch-1 2021-11-12 15:57:19 -08:00
Nick Sweeting d5240f1a1d Merge pull request #885 from adamwolf/safari-admin-actions 2021-11-12 08:56:31 -08:00
Nick Sweeting f809e3be46 Merge pull request #886 from adamwolf/safari-add 2021-10-30 18:07:58 -07:00
Adam Wolf 18e1fb0d96 Fixes Add button behavior on Safari
Previously, when you clicked the Add button, the page wouldn't change.

It looked like it wasn't doing anything, as noted by @rcarmo
(https://github.com/ArchiveBox/ArchiveBox/issues/658#issuecomment-948300055)

I didn't track it down the exact reason why. It may be that Safari
didn't like the two opening <h3>s, but I was able to find a bunch of
people complaining about Safari being very finicky with innerHTML
and actually repainting the page, enough that I decided to try just
extending the block hide/show behavior already done with the
delay-warning, and it works for me now in both Chrome and Safari.

For #658.
2021-10-28 22:31:54 -05:00
Adam Wolf 83731f5a68 Tweak JS so Safari can choose admin actions
I noticed that Safari was submitting both the empty option and the
selected options back to the server.

Digging into it, I was able to get Safari to deselect the ---------
option by using '[selected]' as the selector.

For #658
2021-10-28 22:22:46 -05:00
Nick Sweeting c20c3be02e Merge pull request #884 from adamwolf/dev-docs 2021-10-28 09:58:35 -07:00
Adam Wolf 3b6ef764c5 Update README.md
Bind the data directory when initializing a dev environment using
Docker.
2021-10-28 10:01:34 -05:00
Nick Sweeting b54076054c Update README.md 2021-10-26 02:02:23 -04:00
Nick Sweeting aaa459082d Update README.md 2021-10-26 01:58:16 -04:00
Nick Sweeting 569a4582dd Merge pull request #873 from ajgon/feature/healthcheck 2021-10-21 15:08:57 -07:00
Igor Rzegocki 05de1c9fe6 healthcheck endpoint 2021-10-03 19:12:03 +02:00
Igor Rzegocki 95cf85f8cf Support for Reverse Proxy authentication backends (like authelia) 2021-09-30 17:40:13 +02:00
Bruno Tavares bb2a2e758a Avoid KeyError on Pocket API parser
When trying to import my pocket library I got a lot of ` KeyError`  on Python. Pocket API has a few idiosyncrasies, such as sometimes returning the keys on json, sometimes not.

` ` ` sh
archivebox add --parser pocket_api pocket://my_username
` ` ` 

Gave me this errors
` ` ` 
  File "/app/archivebox/parsers/pocket_api.py", line 54, in link_from_article
    title = article['resolved_title'] or article['given_title'] or url
KeyError: 'resolved_title'
` ` ` 

This commit are the patches I've changed to successfully import my library
2021-09-07 21:53:36 -03:00
Nick Sweeting 63693bdc77 add nix install instructions, thanks @siraben! 2021-08-11 11:12:58 -04:00
Nick Sweeting 2e5937d9e1 Merge pull request #822 from overhacked/bug_pinboard_rss
Fix Pinboard RSS parsing valid links as `None`
2021-08-04 12:23:40 -04:00
Ross Williams f6cf35a45d Fix Pinboard RSS parsing valid links as None
`item.find(p)` returns either an `ElementTree.Element` or `None`.  The
[lambda on line 24][lambda] coerces the return value to a bool, which is
`False` if the `<link>` element has no children (see
[`ElementTree.py` line 207][etbooldef]), so the lambda returns `None`.

Further, returning a `Link` with `url=None` violates
[an assertion in `index/schema.py`][assertion], which crashes
the `archivebox add` command.

[lambda]: https://github.com/ArchiveBox/ArchiveBox/blob/3d54b1321bf8c56627aaa50efcc809cd99caee52/archivebox/parsers/pinboard_rss.py#L24
[etbooldef]: https://github.com/python/cpython/blob/3d8993a744813c5144851da5347d7b4b1885f234/Lib/xml/etree/ElementTree.py#L207
[assertion]: https://github.com/ArchiveBox/ArchiveBox/blob/3d54b1321bf8c56627aaa50efcc809cd99caee52/archivebox/index/schema.py#L165
2021-08-04 10:13:37 -04:00
Nick Sweeting 3d54b1321b Merge pull request #804 from munzevibirinsan/patch-1 2021-07-21 08:28:08 -04:00
munzevibirinsan ee86eb534d Update setup.sh 2021-07-21 13:24:31 +03:00
Nick Sweeting 2427e6d3dc Merge pull request #799 from Inndy/dev
Discard Referer header from iframe and link to original URL
2021-07-19 10:16:52 -04:00
Inndy 0e81a0722e Discard Referer header from iframe and link to original URL 2021-07-19 21:48:01 +08:00
Nick Sweeting ca819b8015 Update README.md 2021-07-07 06:55:07 -04:00
Nick Sweeting 9100ad63a7 Merge pull request #784 from ArchiveBox/dependabot/npm_and_yarn/ws-5.2.3
Bump ws from 5.2.2 to 5.2.3
2021-07-07 00:44:33 -04:00
dependabot[bot] 1084f6aacc Bump ws from 5.2.2 to 5.2.3
Bumps [ws](https://github.com/websockets/ws) from 5.2.2 to 5.2.3.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/5.2.2...5.2.3)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-07 03:42:46 +00:00
Nick Sweeting 767e32c08c bump version number 2021-07-06 23:42:05 -04:00
Nick Sweeting 5a2c78e14b add proper support for URL_WHITELIST instead of using negation regexes 2021-07-06 23:42:00 -04:00
Nick Sweeting e4974d3536 support negation patterns by checking both re.search and re.match 2021-07-06 23:17:05 -04:00
Nick Sweeting 65d452fe7b add documentation for contributing a new extractor 2021-07-01 22:48:39 -04:00
Nick Sweeting f6aad7f9a3 Merge pull request #777 from tjhorner/exempt-add-from-csrf
Exempt /add route from CSRF
2021-07-01 21:40:10 -04:00
TJ Horner cdcfb7fa44 Exempt /add route from CSRF 2021-07-01 20:55:51 -04:00
Nick Sweeting 66f17cb46b Merge pull request #766 from Namdrib/dev
Docs: Improve spelling on readme
2021-06-10 21:01:44 -04:00
Namdrib 6cffe7fa0f Docs: Improve spelling on readme 2021-06-07 21:36:28 +09:30
Nick Sweeting e0a2b2e252 ominous warnings 2021-06-01 03:03:42 -04:00
Nick Sweeting aa53fe653c fix use of uneeded perms arg 2021-06-01 02:58:36 -04:00
Nick Sweeting c2d1a57581 fix umask dir permissions 2021-06-01 00:50:18 -04:00
Nick Sweeting 4715ace7dd ignore BaseException lgtm errors 2021-05-31 20:59:05 -04:00
Nick Sweeting 9f1470cf03 fix output permissions tests 2021-05-31 20:57:46 -04:00
Nick Sweeting 8230f88d80 change default OUTPUT_PERMISSIONS to disallow execution except on dirs 2021-05-31 19:31:51 -04:00
Nick Sweeting 1112526543 add option ENFORCE_ATOMIC_WRITES to allow disabling forced FSYNC writes on network drives 2021-05-31 19:31:51 -04:00
Nick Sweeting e529f550d8 Update README.md 2021-05-13 05:34:03 -04:00
Nick Sweeting ecc178b87f Update README.md 2021-05-13 05:31:40 -04:00
Nick Sweeting 9dbc9d64fb Update README.md 2021-05-13 05:27:48 -04:00
Nick Sweeting 11a7c4ef11 Update README.md 2021-05-13 05:24:36 -04:00
Nick Sweeting a7fb8556ff Update README.md 2021-05-13 05:21:49 -04:00
Nick Sweeting decd18eea0 Update README.md 2021-05-13 05:14:39 -04:00
Nick Sweeting 254ff41b1a Update README.md 2021-05-13 05:11:54 -04:00
Nick Sweeting 49faec8f6d add no-zygote and single-process args to try and prevent orphan chrome processes after exit 2021-05-13 05:04:23 -04:00
Nick Sweeting 930fb394c3 Update README.md 2021-05-13 04:19:22 -04:00
Nick Sweeting 64a9200ac9 Update README.md 2021-05-13 00:55:35 -04:00
Nick Sweeting d8c1be3f06 Update README.md 2021-05-13 00:43:29 -04:00
Nick Sweeting b512b372ba Update README.md 2021-05-13 00:39:27 -04:00
Nick Sweeting 4187afdebc Update README.md 2021-05-13 00:37:18 -04:00
Nick Sweeting c63fe0250e Update README.md 2021-05-13 00:30:12 -04:00
Nick Sweeting eb4d3bca9d Update readability.py 2021-05-13 00:13:32 -04:00
Nick Sweeting 8504d47984 Update README.md 2021-05-12 23:37:19 -04:00
Nick Sweeting 76f29d0325 fix merge 2021-05-12 23:35:57 -04:00
Nick Sweeting 54a11d7927 add python dependencies 2021-05-12 23:35:25 -04:00
Nick Sweeting a8c1ffc5ba Update README.md 2021-05-07 09:43:50 -04:00
Nick Sweeting d43ce963de Update README.md 2021-05-07 09:43:30 -04:00
Nick Sweeting cac13964cb Update README.md 2021-05-07 09:30:44 -04:00
Nick Sweeting 987915e5ea Update README.md 2021-05-07 09:27:42 -04:00
Nick Sweeting 257529625d Update README.md 2021-05-07 09:26:22 -04:00
Nick Sweeting 8e6a65639e Update README.md 2021-05-07 09:18:42 -04:00
Nick Sweeting 4d2936fd4c Update README.md 2021-05-07 09:14:35 -04:00
Nick Sweeting 5d37063313 Update README.md 2021-05-07 09:11:24 -04:00
Nick Sweeting 595839d887 Update README.md 2021-05-07 09:01:57 -04:00
Nick Sweeting f52b9340d0 Update README.md 2021-05-07 08:57:21 -04:00
Nick Sweeting 7da8142ee9 Update README.md 2021-05-07 08:53:03 -04:00
Nick Sweeting e9b26cc6b7 Update README.md 2021-05-07 08:50:44 -04:00
Nick Sweeting 26bc786329 Update README.md 2021-05-07 08:50:00 -04:00
Nick Sweeting 39d2695ca0 Update README.md 2021-05-07 08:47:51 -04:00
Nick Sweeting 76783da161 Update README.md 2021-05-07 08:45:21 -04:00
Nick Sweeting 13ef6a994c Update README.md 2021-05-07 08:43:09 -04:00
Nick Sweeting b92192ed17 Update README.md 2021-05-07 08:40:17 -04:00
Nick Sweeting 9bc9c2495b Update README.md 2021-05-07 08:32:38 -04:00
Nick Sweeting 301c2d67fa Update README.md 2021-05-07 08:26:42 -04:00
Nick Sweeting ff32ffe6eb Update README.md 2021-05-07 08:21:27 -04:00
Nick Sweeting a1c70dda54 Update README.md 2021-05-07 08:15:06 -04:00
Nick Sweeting b443f58d87 Update README.md 2021-05-07 08:12:44 -04:00
Nick Sweeting d2a7d8284c Update README.md 2021-05-07 08:02:45 -04:00
Nick Sweeting a1fae0a5c1 Update README.md 2021-05-07 07:52:04 -04:00
Nick Sweeting df5fcf8f43 Update README.md 2021-05-07 07:50:02 -04:00
Nick Sweeting 4770ec82d6 Update README.md 2021-05-07 07:44:57 -04:00
Nick Sweeting efebd8e02b Merge pull request #739 from FiddlyRumpus/patch-1
point to master image, not latest
2021-05-07 05:29:19 -04:00
Evan Wade bac5a10eae point to master image, not latest
Following the docker-compose quick-start guide, used "latest" tagged docker image in compose file. Returned an exec error on my arm64 raspi 3b+. Seems like this needs to be "master" tag to really support multi-arch... Worked when I changed it to "master" on my machine.
2021-05-07 02:03:54 -07:00
Nick Sweeting ae56636bed Update README.md 2021-04-27 15:10:23 -04:00
Nick Sweeting b9a1c14e29 Update README.md 2021-04-27 15:04:41 -04:00
Nick Sweeting cf817990a3 Update README.md 2021-04-27 15:02:31 -04:00
Nick Sweeting 3d56e44c24 Update README.md 2021-04-27 14:59:31 -04:00
Nick Sweeting edcb7b39a4 Update README.md 2021-04-27 14:58:16 -04:00
Nick Sweeting 0d97069fc7 Update README.md 2021-04-27 14:54:57 -04:00
Nick Sweeting 71ef88b4d9 Update README.md 2021-04-27 14:51:45 -04:00
Nick Sweeting 7113e56859 Update README.md 2021-04-27 14:51:00 -04:00
Nick Sweeting 4386552810 Update docker-compose.yml 2021-04-24 07:27:52 -04:00
Nick Sweeting 5940dd65fb add pip install post-step 2021-04-24 07:17:53 -04:00
Nick Sweeting 0e3ef41d4b add version check 2021-04-24 07:08:56 -04:00
Nick Sweeting c3d009e4ec fix python file execution checking 2021-04-24 04:43:02 -04:00
Nick Sweeting 3d40693f1c change version checking order 2021-04-24 04:31:05 -04:00
Nick Sweeting 3e7d8e5264 install requirements from setup.py 2021-04-24 04:21:43 -04:00
Nick Sweeting bfdd8a9dc1 only install django requirement 2021-04-24 04:14:09 -04:00
Nick Sweeting e974b82db2 fix wrapping error 2021-04-24 04:07:18 -04:00
Nick Sweeting 96514e67c4 force isntall newer django version on ubuntu build ci 2021-04-24 04:05:59 -04:00
Nick Sweeting 79e19ecd47 shield pwd import for windows 2021-04-24 03:51:38 -04:00
Nick Sweeting 3db77fd1a2 fix lint errors 2021-04-24 03:49:01 -04:00
Nick Sweeting 3d4e23e627 Update setup.sh 2021-04-24 03:43:15 -04:00
Nick Sweeting 02874f6ae9 Update setup.sh 2021-04-24 03:42:06 -04:00
Nick Sweeting 3992e0fee3 auto prepend python binary to args when running system command with python file as first argument 2021-04-24 03:29:22 -04:00
Nick Sweeting fa84f749ff run archivebox version using python binary 2021-04-24 03:29:22 -04:00
Nick Sweeting 226e26852c dont try to autodetect whether node, chrome, etc are needed during setup 2021-04-24 03:29:22 -04:00
Nick Sweeting 831d82a7a1 alias python3 to python3.7 on bsd 2021-04-24 03:26:19 -04:00
Nick Sweeting 2f9312f69b Update setup.sh 2021-04-24 03:21:22 -04:00
Nick Sweeting f129b9f443 allow executing main 2021-04-24 03:11:06 -04:00
Nick Sweeting 8bbad5c5d0 Update setup.sh 2021-04-24 03:09:18 -04:00
Nick Sweeting c3f05de602 Update setup.sh 2021-04-24 02:56:48 -04:00
Nick Sweeting 8ba567ef09 Update setup.sh 2021-04-24 02:54:18 -04:00
Nick Sweeting 4b4b85f09b require python3-pip in apt dependencies 2021-04-24 02:52:09 -04:00
Nick Sweeting 208f866fc4 fix user detection on freebsd always returning root 2021-04-24 02:52:09 -04:00
Nick Sweeting 4356085a43 switch instructions to curl sh instead of curl bash 2021-04-24 02:41:29 -04:00
Nick Sweeting 0b26e91d9d Update setup.sh 2021-04-24 02:40:45 -04:00
Nick Sweeting c850a1a920 Update setup.sh 2021-04-24 02:30:31 -04:00
Nick Sweeting a5410484d3 Update setup.sh 2021-04-24 02:27:30 -04:00
Nick Sweeting df1a4e6e47 Update setup.sh 2021-04-24 02:26:25 -04:00
Nick Sweeting dd4f84e8d0 Update setup.sh 2021-04-24 02:11:04 -04:00
Nick Sweeting 2dcb5d1bc5 check to make sure python is installed first 2021-04-24 02:09:53 -04:00
Nick Sweeting f4a373748c Update README.md 2021-04-24 01:48:28 -04:00
Nick Sweeting 85374e02a3 Update README.md 2021-04-24 01:46:24 -04:00
Nick Sweeting 19ab7310fc Update README.md 2021-04-24 01:45:36 -04:00
Nick Sweeting c2fbb18f2e Update README.md 2021-04-24 01:40:01 -04:00
Nick Sweeting b30a0d0aea Update README.md 2021-04-24 01:38:39 -04:00
Nick Sweeting c1dbf03d1d Update README.md 2021-04-24 01:34:09 -04:00
Nick Sweeting 84202a5849 Update README.md 2021-04-24 01:32:53 -04:00
Nick Sweeting 1ebcb8cc88 Update README.md 2021-04-24 01:32:02 -04:00
Nick Sweeting 3cfbaf39e0 Update README.md 2021-04-24 01:30:59 -04:00
Nick Sweeting a437c750dd Update README.md 2021-04-24 01:28:02 -04:00
Nick Sweeting 11850bc700 Update README.md 2021-04-24 01:24:37 -04:00
Nick Sweeting 9e9a7793c2 Update README.md 2021-04-24 01:20:58 -04:00
Nick Sweeting 6543757ddf Update README.md 2021-04-24 01:16:00 -04:00
Nick Sweeting 34605eac92 add createsupersuer example in final help text 2021-04-24 01:13:59 -04:00
Nick Sweeting 0644856aae Update setup.sh 2021-04-24 01:04:58 -04:00
Nick Sweeting 2e0781375f Update setup.sh 2021-04-24 01:02:12 -04:00
Nick Sweeting 4ae965cb07 Update setup.sh 2021-04-24 00:55:51 -04:00
Nick Sweeting d5af216b4e Update setup.sh 2021-04-24 00:50:05 -04:00
Nick Sweeting fced474c38 Update README.md 2021-04-24 00:41:10 -04:00
Nick Sweeting 2bdeda2658 Update README.md 2021-04-24 00:40:34 -04:00
Nick Sweeting 5fd138eb69 add get.archivebox.io helper script 2021-04-24 00:38:19 -04:00
Nick Sweeting 3ff20c5724 Update README.md 2021-04-24 00:29:41 -04:00
Nick Sweeting b7507de983 Update setup.sh 2021-04-24 00:23:14 -04:00
Nick Sweeting 430f28ca93 fix lint action 2021-04-24 00:09:52 -04:00
Nick Sweeting 06f6084e3b ignore stdin when passed instead of throwing an error 2021-04-24 00:09:52 -04:00
Nick Sweeting 431020e6e4 Update setup.sh 2021-04-24 00:08:17 -04:00
Nick Sweeting 9f4e13d8b1 Update setup.sh 2021-04-24 00:06:07 -04:00
Nick Sweeting 0624ac5a74 try docker in setup script before anything else 2021-04-23 23:56:55 -04:00
Nick Sweeting 77e5560762 Update setup.sh 2021-04-23 23:50:11 -04:00
Nick Sweeting 4a26d20849 Update setup.sh 2021-04-23 23:49:44 -04:00
Nick Sweeting b7bf9644de Update setup.sh 2021-04-23 23:43:41 -04:00
Nick Sweeting 98b11eb6d4 add usage to browser history export script and fix output dirs 2021-04-23 23:18:15 -04:00
Nick Sweeting 457556a2a8 add deprecation notice to old archive helper bin 2021-04-23 23:12:14 -04:00
Nick Sweeting 9fc6399034 Update README.md 2021-04-23 23:09:40 -04:00
Nick Sweeting 1aed8a9870 update setup.py 2021-04-23 23:07:55 -04:00
Nick Sweeting eb80dc26a1 cleanup config files 2021-04-23 22:58:44 -04:00
Nick Sweeting 1cdd2bf496 add link to docker config details 2021-04-23 22:48:30 -04:00
Nick Sweeting 2d7c81f6dc add backwards-compatible anchor 2021-04-23 22:37:33 -04:00
Nick Sweeting 6a63bbd9a4 more consice sections 2021-04-23 22:33:45 -04:00
Nick Sweeting bdd8c4693a rearrange sections 2021-04-23 22:26:42 -04:00
Nick Sweeting d96745b592 shorten next steps 2021-04-23 22:20:36 -04:00
Nick Sweeting 3bb0867669 add next steps section to quickstart 2021-04-23 22:17:21 -04:00
Nick Sweeting 9d66594942 simplify CLI usage section in quickstart 2021-04-23 22:08:32 -04:00
Nick Sweeting 44e034041b simplify usagegap 2021-04-23 22:08:23 -04:00
Nick Sweeting dfd61fd021 tweak recommended alternative projects 2021-04-23 22:02:01 -04:00
Nick Sweeting ab680479c4 add re-snapshot ui button to readme 2021-04-23 20:58:06 -04:00
Nick Sweeting 07f4a63635 shorten dependencies section 2021-04-23 20:49:06 -04:00
Nick Sweeting caa1207f88 shorten most common options section even more 2021-04-23 20:44:12 -04:00
Nick Sweeting 5bdcd992bb shorten most common options list 2021-04-23 20:34:31 -04:00
Nick Sweeting 8e5983d434 add configuration section to readme 2021-04-23 20:20:52 -04:00
Nick Sweeting 55e93bcdc0 fix pricing and color 2021-04-23 19:48:01 -04:00
Nick Sweeting ac2a5f82d8 show cli usage example in server block 2021-04-23 19:44:53 -04:00
Nick Sweeting bbb6aaa5cb fix footer links formatting 2021-04-23 19:36:25 -04:00
Nick Sweeting 9edf8a0d52 fix cap 2021-04-23 19:29:13 -04:00
Nick Sweeting d9b1b4eaeb separate ubuntu setup blocks 2021-04-23 19:21:52 -04:00
Nick Sweeting 3a4ae18ebf rearrange paid options 2021-04-23 19:19:19 -04:00
Nick Sweeting 74f7e9ae1d fix closing tags 2021-04-23 19:11:25 -04:00
Nick Sweeting 02b85b63f2 fix missing closing tag 2021-04-23 19:04:49 -04:00
Nick Sweeting dbf9af0933 clearer server setup wording 2021-04-23 18:56:12 -04:00
Nick Sweeting 0e1f61eebc note server is completely optional 2021-04-23 18:49:24 -04:00
Nick Sweeting 0d84f921b7 use emoji arrow 2021-04-23 18:44:18 -04:00
Nick Sweeting d58e9ef2a0 cleaner clinks 2021-04-23 18:42:02 -04:00
Nick Sweeting ef5ab2d53f better list formatting 2021-04-23 18:37:57 -04:00
Nick Sweeting 58a6b48a67 try list format 2021-04-23 18:27:30 -04:00
Nick Sweeting 8839877e8b simpler server instructions 2021-04-23 18:18:24 -04:00
Nick Sweeting 04b2413f78 spacing 2021-04-23 18:10:49 -04:00
Nick Sweeting 2e2a07b649 tweak styling 2021-04-23 17:55:20 -04:00
Nick Sweeting b2d693451a simplify quickstart instructions 2021-04-23 17:50:14 -04:00
Nick Sweeting bc7e172f04 add docker-compose install instructions 2021-04-23 17:33:16 -04:00
Nick Sweeting fe90b88ed6 fix monadical link 2021-04-23 17:28:09 -04:00
Nick Sweeting 82cfef1985 css fixes 2021-04-23 17:25:25 -04:00
Nick Sweeting 76da9895a6 layout changes 2021-04-23 17:23:25 -04:00
Nick Sweeting a2d5feedd1 layout fixes 2021-04-23 17:20:58 -04:00
Nick Sweeting 5bccd04549 custom solution 2021-04-23 17:20:03 -04:00
Nick Sweeting 1ca3f9b48f add more info links 2021-04-23 17:10:56 -04:00
Nick Sweeting 72595e49e2 move note to top 2021-04-23 17:04:08 -04:00
Nick Sweeting b8ee7bb8a0 update referral links 2021-04-23 16:59:29 -04:00
Nick Sweeting 64d9cd568d change referral links 2021-04-23 16:54:49 -04:00
Nick Sweeting 0a09bdb2ca create archivebox dir in code dir manually 2021-04-23 16:12:25 -04:00
Nick Sweeting 145dda3e0f add some more env vars to deploy template 2021-04-23 16:04:18 -04:00
Nick Sweeting bbc9c49694 fix closing tags 2021-04-23 15:54:24 -04:00
Nick Sweeting 985740b0fb attempt to specify source dir for docker build 2021-04-23 15:53:50 -04:00
Nick Sweeting 4f91af0ce9 TODO create admin user from env vars 2021-04-23 15:50:34 -04:00
Nick Sweeting ac0f93ee10 section for paid providers in quickstart 2021-04-23 15:50:34 -04:00
Nick Sweeting 9999721659 Update deploy.template.yaml 2021-04-23 15:26:29 -04:00
Nick Sweeting 4dbad07a0b why isnt it working 2021-04-23 15:17:46 -04:00
Nick Sweeting f0bb48a9d2 fixes 2021-04-23 15:11:12 -04:00
Nick Sweeting c88e042445 deploy button fixes 2021-04-23 15:08:13 -04:00
Nick Sweeting f99fd6f511 add referral code 2021-04-23 15:03:09 -04:00
Nick Sweeting 764ed083b0 add one click deploy button 2021-04-23 15:02:26 -04:00
Nick Sweeting f473114e34 add digitalocean app deploy template 2021-04-23 15:02:26 -04:00
Nick Sweeting 9563e74f7c enforce minimum django version 2021-04-23 15:02:26 -04:00
Nick Sweeting 5a66e8a483 Merge pull request #708 from dryrain39/dryrain39-dev
fix bookmarklet script
2021-04-16 12:53:14 -04:00
Kunwoo Ahn b6ee7d291e fix bookmarklet script
Fix '&' bug in parameter
2021-04-16 21:24:51 +09:00
Nick Sweeting 687c34af3e ignore build dist dirs in pytest discovery 2021-04-14 04:49:16 -04:00
Nick Sweeting 6c06da5581 Merge branch 'dev' 2021-04-14 04:32:27 -04:00
root f7b732f889 bump deb_dist submodule 2021-04-14 04:30:23 -04:00
root f8d2e186e1 add v0.6.2 debian release 2021-04-14 04:26:35 -04:00
Nick Sweeting 5ce042fae0 Merge branch 'dev' 2021-04-14 04:16:09 -04:00
Nick Sweeting 413c89d5cf fix bottle issue 2021-04-14 04:15:44 -04:00
Nick Sweeting 3da6fb8ca7 add latest homebrew release 2021-04-14 04:13:14 -04:00
Nick Sweeting 23812f44af include v0.5.6 in pip_dist 2021-04-14 03:38:28 -04:00
Nick Sweeting 8c1da0e378 update pip_dist folder 2021-04-14 03:36:37 -04:00
Nick Sweeting c3ee1b836a Merge branch 'dev' 2021-04-14 03:33:27 -04:00
Nick Sweeting b45b3fa21c bump version number 2021-04-14 03:33:20 -04:00
Nick Sweeting a09211993c whoops 2021-04-14 03:33:20 -04:00
Nick Sweeting aaea5f3081 bump version number 2021-04-14 03:33:04 -04:00
Nick Sweeting 6811cb35e5 Update README.md 2021-04-14 02:37:46 -04:00
Nick Sweeting 0d670a09d5 Update README.md 2021-04-14 02:36:35 -04:00
Nick Sweeting 63552be0e3 whoops 2021-04-13 04:03:47 -04:00
Nick Sweeting 3af6f7c734 reorder 2021-04-13 03:59:44 -04:00
Nick Sweeting 7d1f8cb496 no longer recommend snap chromium, just use playwright 2021-04-13 03:59:17 -04:00
Nick Sweeting 2c6f0a96bf fix extra arg 2021-04-13 02:21:51 -04:00
Nick Sweeting 54cab113f3 allow static_index.json in dir root from old static exports 2021-04-12 17:06:32 -04:00
Nick Sweeting 50b341baab bail out if old index.json is found during init but doesnt contain links 2021-04-12 16:51:45 -04:00
Nick Sweeting 217952c8d0 sigh linters 2021-04-11 12:51:50 -04:00
Nick Sweeting bfefe042bf fix noqa 2021-04-11 03:57:04 -04:00
Nick Sweeting ff0b04890f Merge branch 'master' into dev 2021-04-11 00:49:43 -04:00
Nick Sweeting 4524129965 dont prevent search when backend throws an exception or times out 2021-04-11 00:49:35 -04:00
Nick Sweeting 335e4658c8 keep grid header on one line 2021-04-11 00:49:33 -04:00
Nick Sweeting 3fd711cd74 Update README.md 2021-04-10 17:27:09 -04:00
116 changed files with 6358 additions and 2896 deletions

No files matched your search

+12 -2
View File
@@ -5,17 +5,27 @@ __pycache__/
.mypy_cache/
.pytest_cache/
.github/
.pdm-build/
.pdm-python
.eggs/
.git/
!.git/HEAD
!.git/refs/heads/*
venv/
.venv/
.venv-old/
.docker-venv/
node_modules/
docs/
build/
dist/
pip_dist/
!pip_dist/archivebox.egg-info/requires.txt
brew_dist/
deb_dist/
pip_dist/
assets/
docker/
data/
output/
-6
View File
@@ -1,6 +0,0 @@
[flake8]
ignore = D100,D101,D102,D103,D104,D105,D202,D203,D205,D400,E131,E241,E252,E266,E272,E701,E731,W293,W503,W291,W391
select = F,E9,W
max-line-length = 130
max-complexity = 10
exclude = migrations,tests,node_modules,vendor,venv,.venv,.venv2,.docker-venv
+17 -30
View File
@@ -9,10 +9,10 @@ env:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 1
@@ -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
- 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 init
archivebox config --set SAVE_READABILITY=False
archivebox config --set SAVE_MERCURY=False
archivebox config --set SAVE_SINGLEFILE=False
archivebox --version
- 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: |
+21 -12
View File
@@ -11,27 +11,27 @@ on:
env:
DOCKER_IMAGE: archivebox-ci
jobs:
buildx:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
with:
version: latest
install: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: Builder instance name
run: echo ${{ steps.buildx.outputs.name }}
@@ -40,7 +40,7 @@ jobs:
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Cache Docker layers
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
@@ -48,7 +48,7 @@ jobs:
${{ runner.os }}-buildx-
- name: Docker Login
uses: docker/login-action@v1
uses: docker/login-action@v3
if: github.event_name != 'pull_request'
with:
username: ${{ secrets.DOCKER_USERNAME }}
@@ -56,20 +56,19 @@ jobs:
- name: Collect Docker tags
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v2
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
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
context: ./
file: ./Dockerfile
@@ -77,8 +76,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
+2 -1
View File
@@ -23,11 +23,12 @@ jobs:
cd brew_dist/
brew install --build-bottle ./archivebox.rb
# brew bottle archivebox
archivebox version
- name: Add some links to test
run: |
mkdir data && cd data
archivebox init
archivebox init --setup
archivebox add 'https://example.com'
archivebox version
archivebox status
+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
+3 -2
View File
@@ -28,7 +28,8 @@ jobs:
- name: Lint with flake8
run: |
cd archivebox
# one pass for show-stopper syntax errors or undefined names
flake8 archivebox --count --show-source --statistics
flake8 . --count --show-source --statistics
# one pass for small stylistic things
flake8 archivebox --count --max-line-length="$MAX_LINE_LENGTH" --statistics
flake8 . --count --max-line-length="$MAX_LINE_LENGTH" --statistics
+34 -17
View File
@@ -7,29 +7,42 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
permissions:
id-token: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
- name: Set up Python
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: '3.11'
architecture: x64
- name: Build Python Package
- 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: |
pip3 install --upgrade pip setuptools wheel
rm -Rf pip_dist/*.whl
python3 setup.py \
sdist --dist-dir=./pip_dist \
bdist_wheel --dist-dir=./pip_dist \
egg_info --egg-base=./pip_dist
pip install pip_dist/archivebox-*.whl
pdm build
- name: Install from build
run: pip install ./dist/archivebox-*.whl
- name: Add some links to test
run: |
@@ -39,6 +52,15 @@ jobs:
archivebox version
archivebox status
#- name: Publish package distributions to PyPI
# run: pdm publish --no-build
# - name: Push build to PyPI
# run: |
# cd pip_dist/
# python3 -m twine upload --repository testpypi pip_dist/*.{whl,tar.gz}
# python3 -m twine upload --repository pypi pip_dist/*.{whl,tar.gz}
# - name: Commit built package
# run: |
# cd pip_dist/
@@ -54,8 +76,3 @@ jobs:
# branch: ${{ github.ref }}
# directory: pip_dist
# - name: Push build to PyPI
# run: |
# cd pip_dist/
# python3 -m twine upload --repository testpypi pip_dist/*.{whl,tar.gz}
# python3 -m twine upload --repository pypi pip_dist/*.{whl,tar.gz}
+26 -14
View File
@@ -13,26 +13,33 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04, macos-latest, windows-latest]
python: [3.7]
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
### Setup Python & JS Languages
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
architecture: x64
- name: Set up Node JS 14.7.0
uses: actions/setup-node@v1
- name: Set up Node JS
uses: actions/setup-node@v4
with:
node-version: 14.7.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
@@ -41,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 }}
@@ -49,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
./bin/build_pip.sh
python -m pip install .
python -m pip install --upgrade pip setuptools wheel pytest bottle build
python -m pip install -r requirements.txt
python -m pip install -e .[sonic,ldap]
- name: Get npm cache dir
id: npm-cache
@@ -61,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 }}
@@ -90,13 +102,13 @@ jobs:
# TODO: remove this exception for windows once we get tests passing on that platform
if: ${{ !contains(matrix.os, 'windows') }}
run: |
python -m pytest -s --ignore=archivebox/vendor
python -m pytest -s --basetemp=tests/out --ignore=archivebox/vendor --ignore=deb_dist --ignore=pip_dist --ignore=brew_dist
docker_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 1
+5
View File
@@ -13,6 +13,8 @@ venv/
node_modules/
# Packaging artifacts
.pdm-python
.pdm-build
archivebox.egg-info
archivebox-*.tar.gz
build/
@@ -24,3 +26,6 @@ data1/
data2/
data3/
output/
# vim
*.sw?
+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
+246 -74
View File
@@ -1,125 +1,297 @@
# This is the Dockerfile for ArchiveBox, it bundles the following dependencies:
# python3, ArchiveBox, curl, wget, git, chromium, youtube-dl, single-file
# python3, ArchiveBox, curl, wget, git, chromium, youtube-dl, yt-dlp, single-file
# Usage:
# git submodule update --init --recursive
# git pull --recurse-submodules
# docker build . -t archivebox --no-cache
# docker run -v "$PWD/data":/data archivebox init
# docker run -v "$PWD/data":/data archivebox add 'https://example.com'
# docker run -v "$PWD/data":/data -it archivebox manage createsuperuser
# docker run -v "$PWD/data":/data -p 8000:8000 archivebox server
# Multi-arch build:
# docker buildx create --use
# docker buildx build . --platform=linux/amd64,linux/arm64,linux/arm/v7 --push -t archivebox/archivebox:latest -t archivebox/archivebox:dev
#
# Read more about [developing Archivebox](https://github.com/ArchiveBox/ArchiveBox#archivebox-development).
FROM python:3.9-slim-buster
# Use Debian 12 w/ faster package updates: https://packages.debian.org/bookworm-backports/
FROM python:3.11-slim-bookworm
LABEL name="archivebox" \
maintainer="Nick Sweeting <archivebox-docker@sweeting.me>" \
maintainer="Nick Sweeting <dockerfile@archivebox.io>" \
description="All-in-one personal internet archiving container" \
homepage="https://github.com/ArchiveBox/ArchiveBox" \
documentation="https://github.com/ArchiveBox/ArchiveBox/wiki/Docker#docker"
# System-level base config
ARG TARGETPLATFORM
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
######### Environment Variables #################################
# Global system-level config
ENV TZ=UTC \
LANGUAGE=en_US:en \
LC_ALL=C.UTF-8 \
LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \
APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 \
PYTHONIOENCODING=UTF-8 \
PYTHONUNBUFFERED=1 \
DEBIAN_FRONTEND=noninteractive \
APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1
PIP_DISABLE_PIP_VERSION_CHECK=1 \
npm_config_loglevel=error
# Application-level base config
# Version config
ENV PYTHON_VERSION=3.11 \
NODE_VERSION=20
# User config
ENV ARCHIVEBOX_USER="archivebox" \
DEFAULT_PUID=911 \
DEFAULT_PGID=911
# Global paths
ENV CODE_DIR=/app \
VENV_PATH=/venv \
DATA_DIR=/data \
NODE_DIR=/node \
ARCHIVEBOX_USER="archivebox"
GLOBAL_VENV=/venv \
PLAYWRIGHT_BROWSERS_PATH=/browsers
# Application-level paths
ENV APP_VENV=/app/.venv \
NODE_MODULES=/app/node_modules
# Build shell config
ENV PATH="$PATH:$GLOBAL_VENV/bin:$APP_VENV/bin:$NODE_MODULES/.bin"
SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-o", "errtrace", "-o", "nounset", "-c"]
######### System Environment ####################################
# Detect ArchiveBox version number by reading package.json
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 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..." \
&& echo "PLATFORM=${TARGETPLATFORM} ARCH=$(uname -m) ($(uname -s) ${TARGETARCH} ${TARGETVARIANT})" \
&& echo "BUILD_START_TIME=$(date +"%Y-%m-%d %H:%M:%S %s") TZ=${TZ} LANG=${LANG}" \
&& echo \
&& echo "GLOBAL_VENV=${GLOBAL_VENV} APP_VENV=${APP_VENV} NODE_MODULES=${NODE_MODULES}" \
&& echo "PYTHON=${PYTHON_VERSION} NODE=${NODE_VERSION} PATH=${PATH}" \
&& echo "CODE_DIR=${CODE_DIR} DATA_DIR=${DATA_DIR}" \
&& echo \
&& uname -a \
&& cat /etc/os-release | head -n7 \
&& which bash && bash --version | head -n1 \
&& which dpkg && dpkg --version | head -n1 \
&& echo -e '\n\n' && env && echo -e '\n\n' \
) | tee -a /VERSION.txt
# Create non-privileged user for archivebox and chrome
RUN groupadd --system $ARCHIVEBOX_USER \
&& useradd --system --create-home --gid $ARCHIVEBOX_USER --groups audio,video $ARCHIVEBOX_USER
RUN echo "[*] Setting up $ARCHIVEBOX_USER user uid=${DEFAULT_PUID}..." \
&& groupadd --system $ARCHIVEBOX_USER \
&& useradd --system --create-home --gid $ARCHIVEBOX_USER --groups audio,video $ARCHIVEBOX_USER \
&& usermod -u "$DEFAULT_PUID" "$ARCHIVEBOX_USER" \
&& groupmod -g "$DEFAULT_PGID" "$ARCHIVEBOX_USER" \
&& echo -e "\nARCHIVEBOX_USER=$ARCHIVEBOX_USER PUID=$(id -u $ARCHIVEBOX_USER) PGID=$(id -g $ARCHIVEBOX_USER)\n\n" \
| tee -a /VERSION.txt
# DEFAULT_PUID and DEFAULT_PID are overriden by PUID and PGID in /bin/docker_entrypoint.sh at runtime
# https://docs.linuxserver.io/general/understanding-puid-and-pgid
# Install system dependencies
RUN apt-get update -qq \
&& apt-get install -qq -y --no-install-recommends \
apt-transport-https ca-certificates gnupg2 zlib1g-dev \
dumb-init gosu cron unzip curl \
# Install system apt dependencies (adding backports to access more recent apt updates)
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH$TARGETVARIANT \
echo "[+] Installing APT base system dependencies for $TARGETPLATFORM..." \
&& echo 'deb https://deb.debian.org/debian bookworm-backports main contrib non-free' >> /etc/apt/sources.list.d/backports.list \
&& mkdir -p /etc/apt/keyrings \
&& apt-get update -qq \
&& apt-get install -qq -y -t bookworm-backports --no-install-recommends \
# 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 \
# 3. frivolous CLI helpers to make debugging failed archiving easier
# nano iputils-ping dnsutils htop procps jq yq
&& rm -rf /var/lib/apt/lists/*
# Install apt dependencies
RUN apt-get update -qq \
&& apt-get install -qq -y --no-install-recommends \
wget curl chromium git ffmpeg youtube-dl ripgrep \
fontconfig fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-symbola fonts-noto fonts-freefont-ttf \
&& rm -rf /var/lib/apt/lists/*
######### Language Environments ####################################
# Install Node environment
RUN curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
&& echo 'deb https://deb.nodesource.com/node_15.x buster main' >> /etc/apt/sources.list \
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 \
&& apt-get update -qq \
&& apt-get install -qq -y --no-install-recommends \
nodejs \
# && npm install -g npm \
&& rm -rf /var/lib/apt/lists/*
&& apt-get install -qq -y -t bookworm-backports --no-install-recommends \
nodejs libatomic1 python3-minimal \
&& rm -rf /var/lib/apt/lists/* \
# Update NPM to latest version
&& npm i -g npm --cache /root/.npm \
# Save version info
&& ( \
which node && node --version \
&& which npm && npm --version \
&& echo -e '\n\n' \
) | tee -a /VERSION.txt
# Install Python environment
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 "[+] Setting up Python $PYTHON_VERSION runtime..." \
# tell PDM to allow using global system python site packages
# && rm /usr/lib/python3*/EXTERNALLY-MANAGED \
# create global virtual environment GLOBAL_VENV to use (better than using pip install --global)
# && python3 -m venv --system-site-packages --symlinks $GLOBAL_VENV \
# && python3 -m venv --system-site-packages $GLOBAL_VENV \
# && python3 -m venv $GLOBAL_VENV \
# install global dependencies / python build dependencies in GLOBAL_VENV
# && pip install --upgrade pip setuptools wheel \
# Save version info
&& ( \
which python3 && python3 --version | grep " $PYTHON_VERSION" \
&& which pip && pip --version \
# && which pdm && pdm --version \
&& echo -e '\n\n' \
) | tee -a /VERSION.txt
######### Extractor Dependencies ##################################
# Install apt dependencies
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 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 \
# 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 \
&& rm -rf /var/lib/apt/lists/* \
# Save version info
&& ( \
which curl && curl --version | head -n1 \
&& which wget && wget --version 2>&1 | head -n1 \
&& which yt-dlp && yt-dlp --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
# Install chromium browser using playwright
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 --mount=type=cache,target=/root/.cache/ms-playwright,sharing=locked,id=browsers-$TARGETARCH$TARGETVARIANT \
echo "[+] Installing Browser binary dependencies to $PLAYWRIGHT_BROWSERS_PATH..." \
&& apt-get update -qq \
&& if [[ "$TARGETPLATFORM" == *amd64* || "$TARGETPLATFORM" == *arm64* ]]; then \
# install Chromium using playwright
pip install playwright \
&& 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)')"; \
else \
# fall back to installing Chromium via apt-get on platforms not supported by playwright (e.g. risc, ARMv7, etc.)
apt-get install -qq -y -t bookworm-backports --no-install-recommends \
chromium fontconfig fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-symbola fonts-noto fonts-freefont-ttf \
&& export CHROME_BINARY="$(which chromium)"; \
fi \
&& rm -rf /var/lib/apt/lists/* \
&& 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 || /usr/lib/chromium/chromium --version \
&& echo -e '\n\n' \
) | tee -a /VERSION.txt
# Install Node dependencies
WORKDIR "$NODE_DIR"
ENV PATH="${PATH}:$NODE_DIR/node_modules/.bin" \
npm_config_loglevel=error
ADD ./package.json ./package.json
ADD ./package-lock.json ./package-lock.json
RUN npm ci
# Install Python dependencies
WORKDIR "$CODE_DIR"
ENV PATH="${PATH}:$VENV_PATH/bin"
RUN python -m venv --clear --symlinks "$VENV_PATH" \
&& pip install --upgrade --quiet pip setuptools
ADD "./setup.py" "$CODE_DIR/"
ADD "./package.json" "$CODE_DIR/archivebox/"
RUN apt-get update -qq \
&& apt-get install -qq -y --no-install-recommends \
build-essential python-dev python3-dev \
&& echo 'empty placeholder for setup.py to use' > "$CODE_DIR/archivebox/README.md" \
&& 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"]))' > /tmp/requirements.txt \
&& pip install --quiet -r /tmp/requirements.txt \
&& apt-get purge -y build-essential python-dev python3-dev \
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 \
&& ( \
which node && node --version \
&& which npm && npm version \
&& echo -e '\n\n' \
) | tee -a /VERSION.txt
######### Build Dependencies ####################################
# Install ArchiveBox Python dependencies
WORKDIR "$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}..." \
&& apt-get update -qq \
&& apt-get install -qq -y -t bookworm-backports --no-install-recommends \
build-essential \
libssl-dev libldap2-dev libsasl2-dev \
python3-ldap python3-msgpack python3-mutagen python3-regex python3-pycryptodome procps \
# && ln -s "$GLOBAL_VENV" "$APP_VENV" \
# && pdm use --venv in-project \
# && pdm run python -m ensurepip \
# && pdm sync --fail-fast --no-editable --group :all --no-self \
# && pdm export -o requirements.txt --without-hashes \
# && source $GLOBAL_VENV/bin/activate \
&& pip install -r requirements.txt \
&& apt-get purge -y \
build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
# Install apt development dependencies
# RUN apt-get install -qq \
# && apt-get install -qq -y --no-install-recommends \
# python3 python3-dev python3-pip python3-venv python3-all \
# dh-python debhelper devscripts dput software-properties-common \
# python3-distutils python3-setuptools python3-wheel python3-stdeb
# RUN python3 -c 'from distutils.core import run_setup; result = run_setup("./setup.py", stop_after="init"); print("\n".join(result.extras_require["dev"]))' > /tmp/dev_requirements.txt \
# && pip install --quiet -r /tmp/dev_requirements.txt
# Install ArchiveBox Python package from source
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 \
# 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 \
# 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 \
&& rm -rf /var/lib/apt/lists/*
# Install ArchiveBox Python package and its dependencies
WORKDIR "$CODE_DIR"
ADD . "$CODE_DIR"
RUN pip install -e .
####################################################
# Setup ArchiveBox runtime config
WORKDIR "$DATA_DIR"
ENV IN_DOCKER=True \
CHROME_SANDBOX=False \
CHROME_BINARY="chromium" \
USE_SINGLEFILE=True \
SINGLEFILE_BINARY="$NODE_DIR/node_modules/.bin/single-file" \
USE_READABILITY=True \
READABILITY_BINARY="$NODE_DIR/node_modules/.bin/readability-extractor" \
USE_MERCURY=True \
MERCURY_BINARY="$NODE_DIR/node_modules/.bin/mercury-parser"
ENV IN_DOCKER=True
## No need to set explicitly, these values will be autodetected by archivebox in docker:
# CHROME_SANDBOX=False \
# WGET_BINARY="wget" \
# YOUTUBEDL_BINARY="yt-dlp" \
# CHROME_BINARY="/usr/bin/chromium-browser" \
# USE_SINGLEFILE=True \
# SINGLEFILE_BINARY="$NODE_MODULES/.bin/single-file" \
# USE_READABILITY=True \
# READABILITY_BINARY="$NODE_MODULES/.bin/readability-extractor" \
# USE_MERCURY=True \
# MERCURY_BINARY="$NODE_MODULES/.bin/postlight-parser"
# Print version for nice docker finish summary
# RUN archivebox version
RUN /app/bin/docker_entrypoint.sh archivebox version
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})\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
####################################################
# Open up the interfaces to the outside world
WORKDIR "$DATA_DIR"
VOLUME "$DATA_DIR"
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=20s --retries=15 \
CMD curl --silent 'http://localhost:8000/admin/login/' || exit 1
# Optional:
# HEALTHCHECK --interval=30s --timeout=20s --retries=15 \
# CMD curl --silent 'http://localhost:8000/admin/login/' || exit 1
ENTRYPOINT ["dumb-init", "--", "/app/bin/docker_entrypoint.sh"]
CMD ["archivebox", "server", "--quick-init", "0.0.0.0:8000"]
-6
View File
@@ -1,6 +0,0 @@
graft archivebox
global-exclude .DS_Store
global-exclude __pycache__
global-exclude *.pyc
prune tests/
-12
View File
@@ -1,12 +0,0 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[packages]
# see setup.py for package dependency list
"e1839a8" = {path = ".", editable = true}
[dev-packages]
# see setup.py for dev package dependency list
"e1839a8" = {path = ".", extras = ["dev"], editable = true}
+762 -335
View File
File diff suppressed because it is too large. Load diff
+34
View File
@@ -0,0 +1,34 @@
# Security Policy
---
## Security Information
Please see this wiki page for important notices about ArchiveBox security, publishing your archives securely, and the dangers of executing archived JS:
https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview
Also see this section of the README about important caveats when running ArchiveBox:
https://github.com/ArchiveBox/ArchiveBox?tab=readme-ov-file#caveats
You can also read these pages for more information about ArchiveBox's internals, development environment, DB schema, and more:
- https://github.com/ArchiveBox/ArchiveBox#archive-layout
- https://github.com/ArchiveBox/ArchiveBox#archivebox-development
- https://github.com/ArchiveBox/ArchiveBox/wiki/Upgrading-or-Merging-Archives
- https://github.com/ArchiveBox/ArchiveBox/wiki/Troubleshooting
---
## Reporting a Vulnerability
We use Github's built-in [Private Reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) feature to accept vulnerability reports.
1. Go to the Security tab on our Github repo: https://github.com/ArchiveBox/ArchiveBox/security
2. Click the ["Report a Vulnerability"](https://github.com/ArchiveBox/ArchiveBox/security/advisories/new) button
3. Fill out the form to submit the details of the report and it will be securely sent to the maintainers
You can also contact the maintainers via our public [Zulip Chat Server zulip.archivebox.io](https://zulip.archivebox.io) or [Twitter DMs @ArchiveBoxApp](https://twitter.com/ArchiveBoxApp).
-1
View File
@@ -1 +0,0 @@
theme: jekyll-theme-merlot
+1 -1
View File
@@ -3,4 +3,4 @@ ignore = D100,D101,D102,D103,D104,D105,D202,D203,D205,D400,E131,E241,E252,E266,E
select = F,E9,W
max-line-length = 130
max-complexity = 10
exclude = migrations,tests,node_modules,vendor,static,venv,.venv,.venv2,.docker-venv
exclude = migrations,tests,node_modules,vendor,venv,.venv,.venv2,.docker-venv
Regular → Executable
View File
File mode changed.
+8 -1
View File
@@ -30,11 +30,17 @@ def main(args: Optional[List[str]]=None, stdin: Optional[IO]=None, pwd: Optional
help="Tag the added URLs with the provided tags e.g. --tag=tag1,tag2,tag3",
)
parser.add_argument(
'--update-all', #'-n',
'--update', #'-u',
action='store_true',
default=not ONLY_NEW, # when ONLY_NEW=True we skip updating old links
help="Also retry previously skipped/failed links when adding new links",
)
parser.add_argument(
'--update-all', #'-n',
action='store_true',
default=False,
help="Also update ALL links in index when finished adding new links",
)
parser.add_argument(
'--index-only', #'-o',
action='store_true',
@@ -104,6 +110,7 @@ def main(args: Optional[List[str]]=None, stdin: Optional[IO]=None, pwd: Optional
urls=stdin_urls or urls,
depth=command.depth,
tag=command.tag,
update=command.update,
update_all=command.update_all,
index_only=command.index_only,
overwrite=command.overwrite,
+13
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,
@@ -51,6 +57,11 @@ def main(args: Optional[List[str]]=None, stdin: Optional[IO]=None, pwd: Optional
action='store_true',
help='Re-archive any URLs that have been previously archived, overwriting existing Snapshots',
)
parser.add_argument(
'--update',
action='store_true',
help='Re-pull any URLs that have been previously added, as needed to fill missing ArchiveResults',
)
group.add_argument(
'--clear', # '-c'
action='store_true',
@@ -92,8 +103,10 @@ 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,
import_path=command.import_path,
out_dir=pwd or OUTPUT_DIR,
)
+360 -82
View File
@@ -26,11 +26,13 @@ import io
import re
import sys
import json
import inspect
import getpass
import platform
import shutil
import sqlite3
import requests
import django
from sqlite3 import dbapi2 as sqlite3
from hashlib import md5
from pathlib import Path
@@ -39,6 +41,7 @@ from typing import Optional, Type, Tuple, Dict, Union, List
from subprocess import run, PIPE, DEVNULL
from configparser import ConfigParser
from collections import defaultdict
import importlib.metadata
from .config_stubs import (
SimpleConfigValueDict,
@@ -48,6 +51,7 @@ from .config_stubs import (
ConfigDefaultDict,
)
############################### Config Schema ##################################
CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
@@ -56,7 +60,9 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'USE_COLOR': {'type': bool, 'default': lambda c: c['IS_TTY']},
'SHOW_PROGRESS': {'type': bool, 'default': lambda c: (c['IS_TTY'] and platform.system() != 'Darwin')}, # progress bars are buggy on mac, disable for now
'IN_DOCKER': {'type': bool, 'default': False},
# TODO: 'SHOW_HINTS': {'type: bool, 'default': True},
'IN_QEMU': {'type': bool, 'default': False},
'PUID': {'type': int, 'default': os.getuid()},
'PGID': {'type': int, 'default': os.getgid()},
},
'GENERAL_CONFIG': {
@@ -65,23 +71,47 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'ONLY_NEW': {'type': bool, 'default': True},
'TIMEOUT': {'type': int, 'default': 60},
'MEDIA_TIMEOUT': {'type': int, 'default': 3600},
'OUTPUT_PERMISSIONS': {'type': str, 'default': '755'},
'OUTPUT_PERMISSIONS': {'type': str, 'default': '644'},
'RESTRICT_FILE_NAMES': {'type': str, 'default': 'windows'},
'URL_BLACKLIST': {'type': str, 'default': r'\.(css|js|otf|ttf|woff|woff2|gstatic\.com|googleapis\.com/css)(\?.*)?$'}, # to avoid downloading code assets as their own pages
'URL_DENYLIST': {'type': str, 'default': r'\.(css|js|otf|ttf|woff|woff2|gstatic\.com|googleapis\.com/css)(\?.*)?$', 'aliases': ('URL_BLACKLIST',)}, # to avoid downloading code assets as their own pages
'URL_ALLOWLIST': {'type': str, 'default': None, 'aliases': ('URL_WHITELIST',)},
'ADMIN_USERNAME': {'type': str, 'default': None},
'ADMIN_PASSWORD': {'type': str, 'default': None},
'ENFORCE_ATOMIC_WRITES': {'type': bool, 'default': True},
'TAG_SEPARATOR_PATTERN': {'type': str, 'default': r'[,]'},
},
'SERVER_CONFIG': {
'SECRET_KEY': {'type': str, 'default': None},
'BIND_ADDR': {'type': str, 'default': lambda c: ['127.0.0.1:8000', '0.0.0.0:8000'][c['IN_DOCKER']]},
'ALLOWED_HOSTS': {'type': str, 'default': '*'},
'DEBUG': {'type': bool, 'default': False},
'PUBLIC_INDEX': {'type': bool, 'default': True},
'PUBLIC_SNAPSHOTS': {'type': bool, 'default': True},
'PUBLIC_ADD_VIEW': {'type': bool, 'default': False},
'FOOTER_INFO': {'type': str, 'default': 'Content is hosted for personal archiving purposes only. Contact server owner for any takedown requests.'},
'SNAPSHOTS_PER_PAGE': {'type': int, 'default': 40},
'CUSTOM_TEMPLATES_DIR': {'type': str, 'default': None},
'TIME_ZONE': {'type': str, 'default': 'UTC'},
'SECRET_KEY': {'type': str, 'default': None},
'BIND_ADDR': {'type': str, 'default': lambda c: ['127.0.0.1:8000', '0.0.0.0:8000'][c['IN_DOCKER']]},
'ALLOWED_HOSTS': {'type': str, 'default': '*'},
'DEBUG': {'type': bool, 'default': False},
'PUBLIC_INDEX': {'type': bool, 'default': True},
'PUBLIC_SNAPSHOTS': {'type': bool, 'default': True},
'PUBLIC_ADD_VIEW': {'type': bool, 'default': False},
'FOOTER_INFO': {'type': str, 'default': 'Content is hosted for personal archiving purposes only. Contact server owner for any takedown requests.'},
'SNAPSHOTS_PER_PAGE': {'type': int, 'default': 40},
'CUSTOM_TEMPLATES_DIR': {'type': str, 'default': None},
'TIME_ZONE': {'type': str, 'default': 'UTC'},
'TIMEZONE': {'type': str, 'default': 'UTC'},
'REVERSE_PROXY_USER_HEADER': {'type': str, 'default': 'Remote-User'},
'REVERSE_PROXY_WHITELIST': {'type': str, 'default': ''},
'LOGOUT_REDIRECT_URL': {'type': str, 'default': '/'},
'PREVIEW_ORIGINALS': {'type': bool, 'default': True},
'LDAP': {'type': bool, 'default': False},
'LDAP_SERVER_URI': {'type': str, 'default': None},
'LDAP_BIND_DN': {'type': str, 'default': None},
'LDAP_BIND_PASSWORD': {'type': str, 'default': None},
'LDAP_USER_BASE': {'type': str, 'default': None},
'LDAP_USER_FILTER': {'type': str, 'default': None},
'LDAP_USERNAME_ATTR': {'type': str, 'default': None},
'LDAP_FIRSTNAME_ATTR': {'type': str, 'default': None},
'LDAP_LASTNAME_ATTR': {'type': str, 'default': None},
'LDAP_EMAIL_ATTR': {'type': str, 'default': None},
},
'ARCHIVE_METHOD_TOGGLES': {
@@ -92,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',)},
@@ -100,6 +131,8 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'SAVE_GIT': {'type': bool, 'default': True, 'aliases': ('FETCH_GIT',)},
'SAVE_MEDIA': {'type': bool, 'default': True, 'aliases': ('FETCH_MEDIA',)},
'SAVE_ARCHIVE_DOT_ORG': {'type': bool, 'default': True, 'aliases': ('SUBMIT_ARCHIVE_DOT_ORG',)},
'SAVE_ALLOWLIST': {'type': dict, 'default': {},},
'SAVE_DENYLIST': {'type': dict, 'default': {},},
},
'ARCHIVE_METHOD_OPTIONS': {
@@ -108,13 +141,14 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'CHECK_SSL_VALIDITY': {'type': bool, 'default': True},
'MEDIA_MAX_SIZE': {'type': str, 'default': '750m'},
'CURL_USER_AGENT': {'type': str, 'default': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.61 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/) curl/{CURL_VERSION}'},
'WGET_USER_AGENT': {'type': str, 'default': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.61 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/) wget/{WGET_VERSION}'},
'CHROME_USER_AGENT': {'type': str, 'default': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.61 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)'},
'CURL_USER_AGENT': {'type': str, 'default': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/) curl/{CURL_VERSION}'},
'WGET_USER_AGENT': {'type': str, 'default': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/) wget/{WGET_VERSION}'},
'CHROME_USER_AGENT': {'type': str, 'default': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)'},
'COOKIES_FILE': {'type': str, 'default': None},
'CHROME_USER_DATA_DIR': {'type': str, 'default': None},
'CHROME_TIMEOUT': {'type': int, 'default': 0},
'CHROME_HEADLESS': {'type': bool, 'default': True},
'CHROME_SANDBOX': {'type': bool, 'default': lambda c: not c['IN_DOCKER']},
'YOUTUBEDL_ARGS': {'type': list, 'default': lambda c: [
@@ -124,17 +158,22 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'--write-thumbnail',
'--no-call-home',
'--write-sub',
'--all-subs',
'--write-auto-sub',
'--write-auto-subs',
'--convert-subs=srt',
'--yes-playlist',
'--continue',
# This flag doesn't exist in youtube-dl
# only in yt-dlp
'--no-abort-on-error',
# --ignore-errors must come AFTER
# --no-abort-on-error
# https://github.com/yt-dlp/yt-dlp/issues/4914
'--ignore-errors',
'--geo-bypass',
'--add-metadata',
'--max-filesize={}'.format(c['MEDIA_MAX_SIZE']),
'--format=(bv*+ba/b)[filesize<={}][filesize_approx<=?{}]/(bv*+ba/b)'.format(c['MEDIA_MAX_SIZE'], c['MEDIA_MAX_SIZE']),
]},
'WGET_ARGS': {'type': list, 'default': ['--no-verbose',
'--adjust-extension',
@@ -150,6 +189,8 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'--compressed'
]},
'GIT_ARGS': {'type': list, 'default': ['--recursive']},
'SINGLEFILE_ARGS': {'type': list, 'default' : None},
'FAVICON_PROVIDER': {'type': str, 'default': 'https://www.google.com/s2/favicons?domain={}'},
},
'SEARCH_BACKEND_CONFIG' : {
@@ -159,10 +200,16 @@ 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'},
'SEARCH_BACKEND_TIMEOUT': {'type': int, 'default': 90},
# SQLite3 FTS5
'FTS_SEPARATE_DATABASE': {'type': bool, 'default': True},
'FTS_TOKENIZERS': {'type': str, 'default': 'porter unicode61 remove_diacritics 2'},
# Default from https://www.sqlite.org/limits.html#max_length
'FTS_SQLITE_MAX_LENGTH': {'type': int, 'default': int(1e9)},
},
'DEPENDENCY_CONFIG': {
@@ -176,20 +223,22 @@ CONFIG_SCHEMA: Dict[str, ConfigDefaultDict] = {
'USE_NODE': {'type': bool, 'default': True},
'USE_YOUTUBEDL': {'type': bool, 'default': True},
'USE_RIPGREP': {'type': bool, 'default': True},
'CURL_BINARY': {'type': str, 'default': 'curl'},
'GIT_BINARY': {'type': str, 'default': 'git'},
'WGET_BINARY': {'type': str, 'default': 'wget'},
'WGET_BINARY': {'type': str, 'default': 'wget'}, # also can accept wget2
'SINGLEFILE_BINARY': {'type': str, 'default': lambda c: bin_path('single-file')},
'READABILITY_BINARY': {'type': str, 'default': lambda c: bin_path('readability-extractor')},
'MERCURY_BINARY': {'type': str, 'default': lambda c: bin_path('mercury-parser')},
'YOUTUBEDL_BINARY': {'type': str, 'default': 'youtube-dl'},
'MERCURY_BINARY': {'type': str, 'default': lambda c: bin_path('postlight-parser')},
'YOUTUBEDL_BINARY': {'type': str, 'default': 'yt-dlp'}, # also can accept youtube-dl
'NODE_BINARY': {'type': str, 'default': 'node'},
'RIPGREP_BINARY': {'type': str, 'default': 'rg'},
'CHROME_BINARY': {'type': str, 'default': None},
'POCKET_CONSUMER_KEY': {'type': str, 'default': None},
'POCKET_ACCESS_TOKENS': {'type': dict, 'default': {}},
'READWISE_READER_TOKENS': {'type': dict, 'default': {}},
},
}
@@ -257,7 +306,7 @@ STATICFILE_EXTENSIONS = {
# that can be downloaded as-is, not html pages that need to be rendered
'gif', 'jpeg', 'jpg', 'png', 'tif', 'tiff', 'wbmp', 'ico', 'jng', 'bmp',
'svg', 'svgz', 'webp', 'ps', 'eps', 'ai',
'mp3', 'mp4', 'm4a', 'mpeg', 'mpg', 'mkv', 'mov', 'webm', 'm4v',
'mp3', 'mp4', 'm4a', 'mpeg', 'mpg', 'mkv', 'mov', 'webm', 'm4v',
'flv', 'wmv', 'avi', 'ogg', 'ts', 'm3u8',
'pdf', 'txt', 'rtf', 'rtfd', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx',
'atom', 'rss', 'css', 'js', 'json',
@@ -266,7 +315,7 @@ STATICFILE_EXTENSIONS = {
# Less common extensions to consider adding later
# jar, swf, bin, com, exe, dll, deb
# ear, hqx, eot, wmlc, kml, kmz, cco, jardiff, jnlp, run, msi, msp, msm,
# ear, hqx, eot, wmlc, kml, kmz, cco, jardiff, jnlp, run, msi, msp, msm,
# pl pm, prc pdb, rar, rpm, sea, sit, tcl tk, der, pem, crt, xpi, xspf,
# ra, mng, asx, asf, 3gpp, 3gp, mid, midi, kar, jad, wml, htc, mml
@@ -274,14 +323,160 @@ STATICFILE_EXTENSIONS = {
# html, htm, shtml, xhtml, xml, aspx, php, cgi
}
# When initializing archivebox in a new directory, we check to make sure the dir is
# actually empty so that we dont clobber someone's home directory or desktop by accident.
# These files are exceptions to the is_empty check when we're trying to init a new dir,
# as they could be from a previous archivebox version, system artifacts, dependencies, etc.
ALLOWED_IN_OUTPUT_DIR = {
'.gitignore',
'lost+found',
'.DS_Store',
'.venv',
'venv',
'virtualenv',
'.virtualenv',
'node_modules',
'package.json',
'package-lock.json',
'yarn.lock',
'static',
'sonic',
'search.sqlite3',
ARCHIVE_DIR_NAME,
SOURCES_DIR_NAME,
LOGS_DIR_NAME,
SQL_INDEX_FILENAME,
f'{SQL_INDEX_FILENAME}-wal',
f'{SQL_INDEX_FILENAME}-shm',
JSON_INDEX_FILENAME,
HTML_INDEX_FILENAME,
ROBOTS_TXT_FILENAME,
FAVICON_FILENAME,
CONFIG_FILENAME,
f'{CONFIG_FILENAME}.bak',
'static_index.json',
}
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: getpass.getuser() or os.getlogin()},
'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},
@@ -295,17 +490,30 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'CONFIG_FILE': {'default': lambda c: Path(c['CONFIG_FILE']).resolve() if c['CONFIG_FILE'] else c['OUTPUT_DIR'] / CONFIG_FILENAME},
'COOKIES_FILE': {'default': lambda c: c['COOKIES_FILE'] and Path(c['COOKIES_FILE']).resolve()},
'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_BLACKLIST_PTN': {'default': lambda c: c['URL_BLACKLIST'] and re.compile(c['URL_BLACKLIST'] or '', re.IGNORECASE | re.UNICODE | re.MULTILINE)},
'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')}, # exec is always needed to list directories
'ARCHIVEBOX_BINARY': {'default': lambda c: sys.argv[0] or bin_path('archivebox')},
'VERSION': {'default': lambda c: json.loads((Path(c['PACKAGE_DIR']) / 'package.json').read_text(encoding='utf-8').strip())['version']},
'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])},
'DJANGO_BINARY': {'default': lambda c: django.__file__.replace('__init__.py', 'bin/django-admin.py')},
'DJANGO_BINARY': {'default': lambda c: inspect.getfile(django)},
'DJANGO_VERSION': {'default': lambda c: '{}.{}.{} {} ({})'.format(*django.VERSION)},
'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 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'])},
'CURL_VERSION': {'default': lambda c: bin_version(c['CURL_BINARY']) if c['USE_CURL'] else None},
@@ -326,12 +534,13 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'USE_SINGLEFILE': {'default': lambda c: c['USE_SINGLEFILE'] and c['SAVE_SINGLEFILE']},
'SINGLEFILE_VERSION': {'default': lambda c: bin_version(c['SINGLEFILE_BINARY']) if c['USE_SINGLEFILE'] else None},
'SINGLEFILE_ARGS': {'default': lambda c: c['SINGLEFILE_ARGS'] or []},
'USE_READABILITY': {'default': lambda c: c['USE_READABILITY'] and c['SAVE_READABILITY']},
'READABILITY_VERSION': {'default': lambda c: bin_version(c['READABILITY_BINARY']) if c['USE_READABILITY'] else None},
'USE_MERCURY': {'default': lambda c: c['USE_MERCURY'] and c['SAVE_MERCURY']},
'MERCURY_VERSION': {'default': lambda c: '1.0.0' if shutil.which(str(bin_path(c['MERCURY_BINARY']))) else None}, # mercury is unversioned
'MERCURY_VERSION': {'default': lambda c: '1.0.0' if shutil.which(str(bin_path(c['MERCURY_BINARY']))) else None}, # mercury doesnt expose version info until this is merged https://github.com/postlight/parser/pull/750
'USE_GIT': {'default': lambda c: c['USE_GIT'] and c['SAVE_GIT']},
'GIT_VERSION': {'default': lambda c: bin_version(c['GIT_BINARY']) if c['USE_GIT'] else None},
@@ -345,14 +554,15 @@ 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']},
'SAVE_DOM': {'default': lambda c: c['USE_CHROME'] and c['SAVE_DOM']},
'SAVE_SINGLEFILE': {'default': lambda c: c['USE_CHROME'] and c['SAVE_SINGLEFILE'] and c['USE_NODE']},
'SAVE_READABILITY': {'default': lambda c: c['USE_READABILITY'] and c['USE_NODE']},
'SAVE_MERCURY': {'default': lambda c: c['USE_MERCURY'] and c['USE_NODE']},
'USE_NODE': {'default': lambda c: c['USE_NODE'] and (c['SAVE_READABILITY'] or c['SAVE_SINGLEFILE'] or c['SAVE_MERCURY'])},
'NODE_VERSION': {'default': lambda c: bin_version(c['NODE_BINARY']) if c['USE_NODE'] else None},
@@ -361,10 +571,11 @@ DYNAMIC_CONFIG_SCHEMA: ConfigDefaultDict = {
'EXTERNAL_LOCATIONS': {'default': lambda c: get_external_locations(c)},
'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()}},
}
################################### Helpers ####################################
@@ -377,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)')
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]]:
@@ -427,7 +651,7 @@ def load_config_file(out_dir: str=None) -> Optional[Dict[str, str]]:
config_path = Path(out_dir) / CONFIG_FILENAME
if config_path.exists():
config_file = ConfigParser()
config_file.optionxform = str
config_file.optionxform = str
config_file.read(config_path)
# flatten into one namespace
config_file_vars = {
@@ -451,7 +675,7 @@ def write_config_file(config: Dict[str, str], out_dir: str=None) -> ConfigDict:
#
# You can add options here manually in INI format, or automatically by running:
# archivebox config --set KEY=VALUE
#
#
# If you modify this file manually, make sure to update your archive after by running:
# archivebox init
#
@@ -462,7 +686,7 @@ def write_config_file(config: Dict[str, str], out_dir: str=None) -> ConfigDict:
out_dir = out_dir or Path(os.getenv('OUTPUT_DIR', '.')).resolve()
config_path = Path(out_dir) / CONFIG_FILENAME
if not config_path.exists():
atomic_write(config_path, CONFIG_HEADER)
@@ -500,7 +724,7 @@ def write_config_file(config: Dict[str, str], out_dir: str=None) -> ConfigDict:
with open(config_path, 'w+', encoding='utf-8') as new:
config_file.write(new)
try:
# validate the config by attempting to re-parse it
CONFIG = load_all_config()
@@ -513,20 +737,20 @@ def write_config_file(config: Dict[str, str], out_dir: str=None) -> ConfigDict:
if Path(f'{config_path}.bak').exists():
os.remove(f'{config_path}.bak')
return {
key.upper(): CONFIG.get(key.upper())
for key in config.keys()
}
def load_config(defaults: ConfigDefaultDict,
config: Optional[ConfigDict]=None,
out_dir: Optional[str]=None,
env_vars: Optional[os._Environ]=None,
config_file_vars: Optional[Dict[str, str]]=None) -> ConfigDict:
env_vars = env_vars or os.environ
config_file_vars = config_file_vars or load_config_file(out_dir=out_dir)
@@ -556,12 +780,14 @@ def load_config(defaults: ConfigDefaultDict,
stderr()
# raise
raise SystemExit(2)
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
@@ -605,7 +831,10 @@ def bin_version(binary: Optional[str]) -> Optional[str]:
return None
try:
version_str = run([abspath, "--version"], stdout=PIPE).stdout.strip().decode()
bin_env = os.environ | {'LANG': 'C'}
version_str = run([abspath, "--version"], stdout=PIPE, env=bin_env).stdout.strip().decode()
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])
except OSError:
@@ -639,7 +868,7 @@ def bin_hash(binary: Optional[str]) -> Optional[str]:
with io.open(abs_path, mode='rb') as f:
for chunk in iter(lambda: f.read(io.DEFAULT_BUFFER_SIZE), b''):
file_hash.update(chunk)
return f'md5:{file_hash.hexdigest()}'
def find_chrome_binary() -> Optional[str]:
@@ -647,6 +876,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',
@@ -664,7 +894,7 @@ def find_chrome_binary() -> Optional[str]:
full_path_exists = shutil.which(name)
if full_path_exists:
return name
return None
def find_chrome_data_dir() -> Optional[str]:
@@ -748,6 +978,7 @@ def get_data_locations(config: ConfigDict) -> ConfigValue:
'path': config['OUTPUT_DIR'].resolve(),
'enabled': True,
'is_valid': (config['OUTPUT_DIR'] / SQL_INDEX_FILENAME).exists(),
'is_mount': os.path.ismount(config['OUTPUT_DIR'].resolve()),
},
'SOURCES_DIR': {
'path': config['SOURCES_DIR'].resolve(),
@@ -763,6 +994,7 @@ def get_data_locations(config: ConfigDict) -> ConfigValue:
'path': config['ARCHIVE_DIR'].resolve(),
'enabled': True,
'is_valid': config['ARCHIVE_DIR'].exists(),
'is_mount': os.path.ismount(config['ARCHIVE_DIR'].resolve()),
},
'CONFIG_FILE': {
'path': config['CONFIG_FILE'].resolve(),
@@ -773,18 +1005,12 @@ def get_data_locations(config: ConfigDict) -> ConfigValue:
'path': (config['OUTPUT_DIR'] / SQL_INDEX_FILENAME).resolve(),
'enabled': True,
'is_valid': (config['OUTPUT_DIR'] / SQL_INDEX_FILENAME).exists(),
'is_mount': os.path.ismount((config['OUTPUT_DIR'] / SQL_INDEX_FILENAME).resolve()),
},
}
def get_dependency_info(config: ConfigDict) -> ConfigValue:
return {
'ARCHIVEBOX_BINARY': {
'path': bin_path(config['ARCHIVEBOX_BINARY']),
'version': config['VERSION'],
'hash': bin_hash(config['ARCHIVEBOX_BINARY']),
'enabled': True,
'is_valid': True,
},
'PYTHON_BINARY': {
'path': bin_path(config['PYTHON_BINARY']),
'version': config['PYTHON_VERSION'],
@@ -792,6 +1018,13 @@ def get_dependency_info(config: ConfigDict) -> ConfigValue:
'enabled': True,
'is_valid': bool(config['PYTHON_VERSION']),
},
'SQLITE_BINARY': {
'path': bin_path(config['SQLITE_BINARY']),
'version': config['SQLITE_VERSION'],
'hash': bin_hash(config['SQLITE_BINARY']),
'enabled': True,
'is_valid': bool(config['SQLITE_VERSION']),
},
'DJANGO_BINARY': {
'path': bin_path(config['DJANGO_BINARY']),
'version': config['DJANGO_VERSION'],
@@ -799,6 +1032,14 @@ def get_dependency_info(config: ConfigDict) -> ConfigValue:
'enabled': True,
'is_valid': bool(config['DJANGO_VERSION']),
},
'ARCHIVEBOX_BINARY': {
'path': bin_path(config['ARCHIVEBOX_BINARY']),
'version': config['VERSION'],
'hash': bin_hash(config['ARCHIVEBOX_BINARY']),
'enabled': True,
'is_valid': True,
},
'CURL_BINARY': {
'path': bin_path(config['CURL_BINARY']),
'version': config['CURL_VERSION'],
@@ -884,7 +1125,8 @@ def get_chrome_info(config: ConfigDict) -> ConfigValue:
'TIMEOUT': config['TIMEOUT'],
'RESOLUTION': config['RESOLUTION'],
'CHECK_SSL_VALIDITY': config['CHECK_SSL_VALIDITY'],
'CHROME_BINARY': config['CHROME_BINARY'],
'CHROME_BINARY': bin_path(config['CHROME_BINARY']),
'CHROME_TIMEOUT': config['CHROME_TIMEOUT'],
'CHROME_HEADLESS': config['CHROME_HEADLESS'],
'CHROME_SANDBOX': config['CHROME_SANDBOX'],
'CHROME_USER_AGENT': config['CHROME_USER_AGENT'],
@@ -925,38 +1167,69 @@ globals().update(CONFIG)
# Set timezone to UTC and umask to OUTPUT_PERMISSIONS
os.environ["TZ"] = 'UTC'
os.umask(0o777 - int(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'))
sys.path.append(NODE_BIN_PATH)
# OPTIONAL: also look around the host system for node modules to use
# avoid enabling this unless absolutely needed,
# having overlapping potential sources of libs is a big source of bugs/confusing to users
# DEV_NODE_BIN_PATH = str((Path(CONFIG["PACKAGE_DIR"]).absolute() / '..' / 'node_modules' / '.bin'))
# sys.path.append(DEV_NODE_BIN_PATH)
# USER_NODE_BIN_PATH = str(Path('~/.node_modules/.bin').resolve())
# sys.path.append(USER_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)
# get SQLite database version, compile options, and runtime options
# TODO: make this a less hacky proper assertion checker helper function in somewhere like setup_django
#cursor = sqlite3.connect(':memory:').cursor()
#DYNAMIC_CONFIG_SCHEMA['SQLITE_VERSION'] = lambda c: cursor.execute("SELECT sqlite_version();").fetchone()[0]
#DYNAMIC_CONFIG_SCHEMA['SQLITE_JOURNAL_MODE'] = lambda c: cursor.execute('PRAGMA journal_mode;').fetchone()[0]
#DYNAMIC_CONFIG_SCHEMA['SQLITE_OPTIONS'] = lambda c: [option[0] for option in cursor.execute('PRAGMA compile_options;').fetchall()]
#cursor.close()
########################### Config Validity Checkers ###########################
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)
if int(CONFIG['DJANGO_VERSION'].split('.')[0]) < 3:
stderr(f'[X] Django version is not new enough: {config["DJANGO_VERSION"]} (>3.0 is required)', color='red')
stderr(' Upgrade django using pip or your system package manager: pip3 install --upgrade django')
raise SystemExit(2)
if config['PYTHON_ENCODING'] not in ('UTF-8', 'UTF8'):
stderr(f'[X] Your system is running python3 scripts with a bad locale setting: {config["PYTHON_ENCODING"]} (it should be UTF-8).', color='red')
stderr(' To fix it, add the line "export PYTHONIOENCODING=UTF-8" to your ~/.bashrc file (without quotes)')
@@ -1023,12 +1296,13 @@ 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:')
stderr(' https://github.com/ArchiveBox/ArchiveBox/wiki/Configuration#save_media')
stderr()
def check_data_folder(out_dir: Union[str, Path, None]=None, config: ConfigDict=CONFIG) -> None:
output_dir = out_dir or config['OUTPUT_DIR']
@@ -1068,7 +1342,7 @@ def check_migrations(out_dir: Union[str, Path, None]=None, config: ConfigDict=CO
def setup_django(out_dir: Path=None, check_db=False, config: ConfigDict=CONFIG, in_memory_db=False) -> None:
check_system_config()
output_dir = out_dir or Path(config['OUTPUT_DIR'])
assert isinstance(output_dir, Path) and isinstance(config['PACKAGE_DIR'], Path)
@@ -1103,25 +1377,30 @@ def setup_django(out_dir: Path=None, check_db=False, config: ConfigDict=CONFIG,
# Otherwise use default sqlite3 file-based database and initialize django
# without running migrations automatically (user runs them manually by calling init)
django.setup()
from django.conf import settings
# log startup message to the error log
with open(settings.ERROR_LOG, "a+", encoding='utf-8') as f:
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
from django.db import connection
with connection.cursor() as cursor:
# Set Journal mode to WAL to allow for multiple writers
current_mode = cursor.execute("PRAGMA journal_mode")
if current_mode != 'wal':
cursor.execute("PRAGMA journal_mode=wal;")
# Set max blocking delay for concurrent writes and write sync mode
# https://litestream.io/tips/#busy-timeout
cursor.execute("PRAGMA busy_timeout = 5000;")
cursor.execute("PRAGMA synchronous = NORMAL;")
# Create cache table in DB if needed
try:
from django.core.cache import cache
@@ -1129,7 +1408,6 @@ def setup_django(out_dir: Path=None, check_db=False, config: ConfigDict=CONFIG,
except django.db.utils.OperationalError:
call_command("createcachetable", verbosity=0)
# if archivebox gets imported multiple times, we have to close
# the sqlite3 whenever we init from scratch to avoid multiple threads
# sharing the same connection by accident
+3 -1
View File
@@ -41,7 +41,7 @@ class ConfigDict(BaseConfig, total=False):
MEDIA_TIMEOUT: int
OUTPUT_PERMISSIONS: str
RESTRICT_FILE_NAMES: str
URL_BLACKLIST: str
URL_DENYLIST: str
SECRET_KEY: Optional[str]
BIND_ADDR: str
@@ -74,6 +74,7 @@ class ConfigDict(BaseConfig, total=False):
CHROME_USER_AGENT: str
COOKIES_FILE: Union[str, Path, None]
CHROME_USER_DATA_DIR: Union[str, Path, None]
CHROME_TIMEOUT: int
CHROME_HEADLESS: bool
CHROME_SANDBOX: bool
@@ -98,6 +99,7 @@ class ConfigDict(BaseConfig, total=False):
WGET_ARGS: List[str]
CURL_ARGS: List[str]
GIT_ARGS: List[str]
TAG_SEPARATOR_PATTERN: str
ConfigDefaultValueGetter = Callable[[ConfigDict], ConfigValue]
+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'
+1 -1
View File
@@ -41,7 +41,7 @@ class AddLinkForm(forms.Form):
# label="Exclude patterns",
# min_length='1',
# required=False,
# initial=URL_BLACKLIST,
# initial=URL_DENYLIST,
# )
# timeout = forms.IntegerField(
# initial=TIMEOUT,
+24 -1
View File
@@ -1,8 +1,11 @@
__package__ = 'archivebox.core'
import ipaddress
from django.utils import timezone
from django.contrib.auth.middleware import RemoteUserMiddleware
from django.core.exceptions import ImproperlyConfigured
from ..config import PUBLIC_SNAPSHOTS
from ..config import PUBLIC_SNAPSHOTS, REVERSE_PROXY_USER_HEADER, REVERSE_PROXY_WHITELIST
def detect_timezone(request, activate: bool=True):
@@ -35,3 +38,23 @@ def CacheControlMiddleware(get_response):
return response
return middleware
class ReverseProxyAuthMiddleware(RemoteUserMiddleware):
header = 'HTTP_{normalized}'.format(normalized=REVERSE_PROXY_USER_HEADER.replace('-', '_').upper())
def process_request(self, request):
if REVERSE_PROXY_WHITELIST == '':
return
ip = request.META.get('REMOTE_ADDR')
for cidr in REVERSE_PROXY_WHITELIST.split(','):
try:
network = ipaddress.ip_network(cidr)
except ValueError:
raise ImproperlyConfigured(
"The REVERSE_PROXY_WHITELIST config paramater is in invalid format, or "
"contains invalid CIDR. Correct format is a coma-separated list of IPv4/IPv6 CIDRs.")
if ipaddress.ip_address(ip) in network:
return super().process_request(request)
@@ -0,0 +1,18 @@
# Generated by Django 3.1.14 on 2022-09-14 09:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0020_auto_20210410_1031'),
]
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'), ('git', 'git'), ('media', 'media'), ('archive_org', 'archive_org')], max_length=32),
),
]
@@ -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),
),
]
+78 -6
View File
@@ -9,7 +9,7 @@ import tempfile
from pathlib import Path
from django.utils.crypto import get_random_string
from ..config import ( # noqa: F401
from ..config import (
DEBUG,
SECRET_KEY,
ALLOWED_HOSTS,
@@ -19,7 +19,18 @@ from ..config import (
SQL_INDEX_FILENAME,
OUTPUT_DIR,
LOGS_DIR,
TIME_ZONE,
TIMEZONE,
LDAP,
LDAP_SERVER_URI,
LDAP_BIND_DN,
LDAP_BIND_PASSWORD,
LDAP_USER_BASE,
LDAP_USER_FILTER,
LDAP_USERNAME_ATTR,
LDAP_FIRSTNAME_ATTR,
LDAP_LASTNAME_ATTR,
LDAP_EMAIL_ATTR,
)
IS_MIGRATING = 'makemigrations' in sys.argv[:3] or 'migrate' in sys.argv[:3]
@@ -34,7 +45,8 @@ WSGI_APPLICATION = 'core.wsgi.application'
ROOT_URLCONF = 'core.urls'
LOGIN_URL = '/accounts/login/'
LOGOUT_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = os.environ.get('LOGOUT_REDIRECT_URL', '/')
PASSWORD_RESET_URL = '/accounts/password_reset/'
APPEND_SLASH = True
@@ -54,6 +66,12 @@ INSTALLED_APPS = [
]
# For usage with https://www.jetadmin.io/integrations/django
# INSTALLED_APPS += ['jet_django']
# JET_PROJECT = 'archivebox'
# JET_TOKEN = 'some-api-token-here'
MIDDLEWARE = [
'core.middleware.TimezoneMiddleware',
'django.middleware.security.SecurityMiddleware',
@@ -61,14 +79,64 @@ MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'core.middleware.ReverseProxyAuthMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'core.middleware.CacheControlMiddleware',
]
################################################################################
### Authentication Settings
################################################################################
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.RemoteUserBackend',
'django.contrib.auth.backends.ModelBackend',
]
if LDAP:
try:
import ldap
from django_auth_ldap.config import LDAPSearch
global AUTH_LDAP_SERVER_URI
global AUTH_LDAP_BIND_DN
global AUTH_LDAP_BIND_PASSWORD
global AUTH_LDAP_USER_SEARCH
global AUTH_LDAP_USER_ATTR_MAP
AUTH_LDAP_SERVER_URI = LDAP_SERVER_URI
AUTH_LDAP_BIND_DN = LDAP_BIND_DN
AUTH_LDAP_BIND_PASSWORD = LDAP_BIND_PASSWORD
assert AUTH_LDAP_SERVER_URI and LDAP_USERNAME_ATTR and LDAP_USER_FILTER, 'LDAP_* config options must all be set if LDAP=True'
AUTH_LDAP_USER_SEARCH = LDAPSearch(
LDAP_USER_BASE,
ldap.SCOPE_SUBTREE,
'(&(' + LDAP_USERNAME_ATTR + '=%(user)s)' + LDAP_USER_FILTER + ')',
)
AUTH_LDAP_USER_ATTR_MAP = {
'username': LDAP_USERNAME_ATTR,
'first_name': LDAP_FIRSTNAME_ATTR,
'last_name': LDAP_LASTNAME_ATTR,
'email': LDAP_EMAIL_ATTR,
}
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'django_auth_ldap.backend.LDAPBackend',
]
except ModuleNotFoundError:
sys.stderr.write('[X] Error: Found LDAP=True config but LDAP packages not installed. You may need to run: pip install archivebox[ldap]\n\n')
# dont hard exit here. in case the user is just running "archivebox version" or "archivebox help", we still want those to work despite broken ldap
# sys.exit(1)
################################################################################
### Debug Settings
################################################################################
# only enable debug toolbar when in DEBUG mode with --nothreading (it doesnt work in multithreaded mode)
DEBUG_TOOLBAR = DEBUG and ('--nothreading' in sys.argv) and ('--reload' not in sys.argv)
if DEBUG_TOOLBAR:
@@ -154,7 +222,7 @@ DATABASES = {
'timeout': 60,
'check_same_thread': False,
},
'TIME_ZONE': 'UTC',
'TIME_ZONE': TIMEZONE,
# DB setup is sometimes modified at runtime by setup_django() in config.py
}
}
@@ -201,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
@@ -224,6 +294,8 @@ USE_L10N = True
USE_TZ = True
DATETIME_FORMAT = 'Y-m-d g:iA'
SHORT_DATETIME_FORMAT = 'Y-m-d h:iA'
TIME_ZONE = TIMEZONE # django convention is TIME_ZONE, archivebox config uses TIMEZONE, they are equivalent
from django.conf.locale.en import formats as en_formats
@@ -262,8 +334,8 @@ class NoisyRequestsFilter(logging.Filter):
if LOGS_DIR.exists():
ERROR_LOG = (LOGS_DIR / 'errors.log')
else:
# meh too many edge cases here around creating log dir w/ correct permissions
# cant be bothered, just trash the log and let them figure it out via stdout/stderr
# historically too many edge cases here around creating log dir w/ correct permissions early on
# if there's an issue on startup, we trash the log and let user figure it out via stdout/stderr
ERROR_LOG = tempfile.NamedTemporaryFile().name
LOGGING = {
+14 -2
View File
@@ -6,7 +6,11 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.conf import settings
from django.views.generic.base import RedirectView
from core.views import HomepageView, SnapshotView, PublicIndexView, AddView
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)
@@ -24,7 +28,7 @@ urlpatterns = [
path('admin/core/snapshot/add/', RedirectView.as_view(url='/add/')),
path('add/', AddView.as_view(), name='add'),
path('accounts/login/', RedirectView.as_view(url='/admin/login/')),
path('accounts/logout/', RedirectView.as_view(url='/admin/logout/')),
@@ -32,6 +36,14 @@ 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),
# path('jet_api/', include('jet_django.urls')), Enable to use https://www.jetadmin.io/integrations/django
path('index.html', RedirectView.as_view(url='/')),
path('index.json', static.serve, {'document_root': settings.OUTPUT_DIR, 'path': 'index.json'}),
path('', HomepageView.as_view(), name='Home'),
+24 -5
View File
@@ -11,6 +11,8 @@ from django.views.generic.list import ListView
from django.views.generic import FormView
from django.db.models import Q
from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from core.models import Snapshot
from core.forms import AddLinkForm
@@ -21,6 +23,7 @@ from ..config import (
PUBLIC_SNAPSHOTS,
PUBLIC_ADD_VIEW,
VERSION,
COMMIT_HASH,
FOOTER_INFO,
SNAPSHOTS_PER_PAGE,
)
@@ -36,7 +39,7 @@ class HomepageView(View):
if PUBLIC_INDEX:
return redirect('/public')
return redirect(f'/admin/login/?next={request.path}')
@@ -203,7 +206,7 @@ class SnapshotView(View):
content_type="text/html",
status=404,
)
class PublicIndexView(ListView):
template_name = 'public_index.html'
@@ -215,10 +218,11 @@ class PublicIndexView(ListView):
return {
**super().get_context_data(**kwargs),
'VERSION': VERSION,
'COMMIT_HASH': COMMIT_HASH,
'FOOTER_INFO': FOOTER_INFO,
}
def get_queryset(self, **kwargs):
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs)
query = self.request.GET.get('q')
if query and query.strip():
@@ -236,7 +240,7 @@ class PublicIndexView(ListView):
else:
return redirect(f'/admin/login/?next={self.request.path}')
@method_decorator(csrf_exempt, name='dispatch')
class AddView(UserPassesTestMixin, FormView):
template_name = "add.html"
form_class = AddLinkForm
@@ -247,7 +251,7 @@ class AddView(UserPassesTestMixin, FormView):
url = self.request.GET.get('url', None)
if url:
return {'url': url if '://' in url else f'https://{url}'}
return super().get_initial()
def test_func(self):
@@ -293,3 +297,18 @@ class AddView(UserPassesTestMixin, FormView):
"form": AddLinkForm()
})
return render(template_name=self.template_name, request=self.request, context=context)
class HealthCheckView(View):
"""
A Django view that renders plain text "OK" for service discovery tools
"""
def get(self, request):
"""
Handle a GET request
"""
return HttpResponse(
'OK',
content_type='text/plain',
status=200
)
+70 -15
View File
@@ -1,13 +1,19 @@
__package__ = 'archivebox.extractors'
import os
import sys
from pathlib import Path
from typing import Optional, List, Iterable, Union
from typing import Callable, Optional, List, Iterable, Union
from datetime import datetime, timezone
from django.db.models import QuerySet
from ..index.schema import Link
from ..config import (
SAVE_ALLOWLIST_PTN,
SAVE_DENYLIST_PTN,
)
from ..core.settings import ERROR_LOG
from ..index.schema import ArchiveResult, Link
from ..index.sql import write_link_to_sql_index
from ..index import (
load_link_details,
@@ -31,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
@@ -40,9 +47,12 @@ from .archive_org import should_save_archive_dot_org, save_archive_dot_org
from .headers import should_save_headers, save_headers
def get_default_archive_methods():
ShouldSaveFunction = Callable[[Link, Optional[Path], Optional[bool]], bool]
SaveFunction = Callable[[Link, Optional[Path], int], ArchiveResult]
ArchiveMethodEntry = tuple[str, ShouldSaveFunction, SaveFunction]
def get_default_archive_methods() -> List[ArchiveMethodEntry]:
return [
('title', should_save_title, save_title),
('favicon', should_save_favicon, save_favicon),
('headers', should_save_headers, save_headers),
('singlefile', should_save_singlefile, save_singlefile),
@@ -50,21 +60,49 @@ def get_default_archive_methods():
('screenshot', should_save_screenshot, save_screenshot),
('dom', should_save_dom, save_dom),
('wget', should_save_wget, save_wget),
('readability', should_save_readability, save_readability), # keep 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), ('singlefile', 2), ('dom', 3), ('wget', 4)]
ARCHIVE_METHODS_INDEXING_PRECEDENCE = [
('readability', 1),
('mercury', 2),
('htmltotext', 3),
('singlefile', 4),
('dom', 5),
('wget', 6)
]
@enforce_types
def ignore_methods(to_ignore: List[str]):
def get_archive_methods_for_link(link: Link) -> Iterable[ArchiveMethodEntry]:
DEFAULT_METHODS = get_default_archive_methods()
allowed_methods = {
m for pat, methods in
SAVE_ALLOWLIST_PTN.items()
if pat.search(link.url)
for m in methods
} or { m[0] for m in DEFAULT_METHODS }
denied_methods = {
m for pat, methods in
SAVE_DENYLIST_PTN.items()
if pat.search(link.url)
for m in methods
}
allowed_methods -= denied_methods
return (m for m in DEFAULT_METHODS if m[0] in allowed_methods)
@enforce_types
def ignore_methods(to_ignore: List[str]) -> Iterable[str]:
ARCHIVE_METHODS = get_default_archive_methods()
methods = filter(lambda x: x[0] not in to_ignore, ARCHIVE_METHODS)
methods = map(lambda x: x[0], methods)
return list(methods)
return [x[0] for x in ARCHIVE_METHODS if x[0] not in to_ignore]
@enforce_types
def archive_link(link: Link, overwrite: bool=False, methods: Optional[Iterable[str]]=None, out_dir: Optional[Path]=None) -> Link:
@@ -77,11 +115,11 @@ def archive_link(link: Link, overwrite: bool=False, methods: Optional[Iterable[s
except Snapshot.DoesNotExist:
snapshot = write_link_to_sql_index(link)
ARCHIVE_METHODS = get_default_archive_methods()
active_methods = get_archive_methods_for_link(link)
if methods:
ARCHIVE_METHODS = [
method for method in ARCHIVE_METHODS
active_methods = [
method for method in active_methods
if method[0] in methods
]
@@ -98,7 +136,7 @@ def archive_link(link: Link, overwrite: bool=False, methods: Optional[Iterable[s
stats = {'skipped': 0, 'succeeded': 0, 'failed': 0}
start_ts = datetime.now(timezone.utc)
for method_name, should_run, method_function in ARCHIVE_METHODS:
for method_name, should_run, method_function in active_methods:
try:
if method_name not in link.history:
link.history[method_name] = []
@@ -127,10 +165,27 @@ def archive_link(link: Link, overwrite: bool=False, methods: Optional[Iterable[s
# print('{black} X {}{reset}'.format(method_name, **ANSI))
stats['skipped'] += 1
except Exception as e:
# Disabled until https://github.com/ArchiveBox/ArchiveBox/issues/984
# and https://github.com/ArchiveBox/ArchiveBox/issues/1014
# are fixed.
"""
raise Exception('Exception in archive_methods.save_{}(Link(url={}))'.format(
method_name,
link.url,
)) from e
"""
# Instead, use the kludgy workaround from
# https://github.com/ArchiveBox/ArchiveBox/issues/984#issuecomment-1150541627
with open(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(("\n" + 'Exception in archive_methods.save_{}(Link(url={})) command={}; ts={}'.format(
method_name,
link.url,
command,
ts
) + "\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)
@@ -143,7 +198,7 @@ def archive_link(link: Link, overwrite: bool=False, methods: Optional[Iterable[s
write_link_details(link, out_dir=out_dir, skip_sql_index=False)
log_link_archiving_finished(link, link.link_dir, is_new, stats, start_ts)
log_link_archiving_finished(link, out_dir, is_new, stats, start_ts)
except KeyboardInterrupt:
try:
+5 -2
View File
@@ -9,6 +9,7 @@ from ..util import (
enforce_types,
is_static_file,
chrome_args,
chrome_cleanup,
)
from ..config import (
TIMEOUT,
@@ -26,7 +27,8 @@ def should_save_dom(link: Link, out_dir: Optional[Path]=None, overwrite: Optiona
out_dir = out_dir or Path(link.link_dir)
if not overwrite and (out_dir / 'output.html').exists():
return False
if (out_dir / 'output.html').stat().st_size > 1:
return False
return SAVE_DOM
@@ -38,7 +40,7 @@ def save_dom(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEOUT) ->
output: ArchiveOutput = 'output.html'
output_path = out_dir / output
cmd = [
*chrome_args(TIMEOUT=timeout),
*chrome_args(),
'--dump-dom',
link.url
]
@@ -56,6 +58,7 @@ def save_dom(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEOUT) ->
except Exception as err:
status = 'failed'
output = err
chrome_cleanup()
finally:
timer.end()
+2 -1
View File
@@ -10,6 +10,7 @@ from ..util import enforce_types, domain
from ..config import (
TIMEOUT,
SAVE_FAVICON,
FAVICON_PROVIDER,
CURL_BINARY,
CURL_ARGS,
CURL_VERSION,
@@ -40,7 +41,7 @@ def save_favicon(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEOUT)
'--output', str(output),
*(['--user-agent', '{}'.format(CURL_USER_AGENT)] if CURL_USER_AGENT else []),
*([] if CHECK_SSL_VALIDITY else ['--insecure']),
'https://www.google.com/s2/favicons?domain={}'.format(domain(link.url)),
FAVICON_PROVIDER.format(domain(link.url)),
]
status = 'failed'
timer = TimedProgress(timeout, prefix=' ')
+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,
)
+14 -3
View File
@@ -33,7 +33,7 @@ def should_save_media(link: Link, out_dir: Optional[Path]=None, overwrite: Optio
@enforce_types
def save_media(link: Link, out_dir: Optional[Path]=None, timeout: int=MEDIA_TIMEOUT) -> ArchiveResult:
"""Download playlists or individual video, audio, and subtitles using youtube-dl"""
"""Download playlists or individual video, audio, and subtitles using youtube-dl or yt-dlp"""
out_dir = out_dir or Path(link.link_dir)
output: ArchiveOutput = 'media'
@@ -43,6 +43,7 @@ def save_media(link: Link, out_dir: Optional[Path]=None, timeout: int=MEDIA_TIME
YOUTUBEDL_BINARY,
*YOUTUBEDL_ARGS,
*([] if CHECK_SSL_VALIDITY else ['--no-check-certificate']),
# TODO: add --cookies-from-browser={CHROME_USER_DATA_DIR}
link.url,
]
status = 'succeeded'
@@ -60,7 +61,7 @@ def save_media(link: Link, out_dir: Optional[Path]=None, timeout: int=MEDIA_TIME
pass
else:
hints = (
'Got youtube-dl response code: {}.'.format(result.returncode),
'Got youtube-dl (or yt-dlp) response code: {}.'.format(result.returncode),
*result.stderr.decode().split('\n'),
)
raise ArchiveError('Failed to save media', hints)
@@ -71,8 +72,18 @@ def save_media(link: Link, out_dir: Optional[Path]=None, timeout: int=MEDIA_TIME
timer.end()
# add video description and subtitles to full-text index
# Let's try a few different
index_texts = [
text_file.read_text(encoding='utf-8').strip()
# errors:
# * 'strict' to raise a ValueError exception if there is an
# encoding error. The default value of None has the same effect.
# * 'ignore' ignores errors. Note that ignoring encoding errors
# can lead to data loss.
# * 'xmlcharrefreplace' is only supported when writing to a
# file. Characters not supported by the encoding are replaced with
# the appropriate XML character reference &#nnn;.
# There are a few more options described in https://docs.python.org/3/library/functions.html#open
text_file.read_text(encoding='utf-8', errors='xmlcharrefreplace').strip()
for text_file in (
*output_path.glob('*.description'),
*output_path.glob('*.srt'),
+3 -1
View File
@@ -9,6 +9,7 @@ from ..util import (
enforce_types,
is_static_file,
chrome_args,
chrome_cleanup,
)
from ..config import (
TIMEOUT,
@@ -37,7 +38,7 @@ def save_pdf(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEOUT) ->
out_dir = out_dir or Path(link.link_dir)
output: ArchiveOutput = 'output.pdf'
cmd = [
*chrome_args(TIMEOUT=timeout),
*chrome_args(),
'--print-to-pdf',
link.url,
]
@@ -54,6 +55,7 @@ def save_pdf(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEOUT) ->
except Exception as err:
status = 'failed'
output = err
chrome_cleanup()
finally:
timer.end()
+9 -31
View File
@@ -10,9 +10,7 @@ from ..index.schema import Link, ArchiveResult, ArchiveError
from ..system import run, atomic_write
from ..util import (
enforce_types,
download_url,
is_static_file,
)
from ..config import (
TIMEOUT,
@@ -22,28 +20,8 @@ from ..config import (
READABILITY_VERSION,
)
from ..logging_util import TimedProgress
from .title import get_html
@enforce_types
def get_html(link: Link, path: Path) -> str:
"""
Try to find wget, singlefile and then dom files.
If none is found, download the url again.
"""
canonical = link.canonical_outputs()
abs_path = path.absolute()
sources = [canonical["singlefile_path"], canonical["wget_path"], canonical["dom_path"]]
document = None
for source in sources:
try:
with open(abs_path / source, "r", encoding="utf-8") as f:
document = f.read()
break
except (FileNotFoundError, TypeError):
continue
if document is None:
return download_url(link.url)
else:
return document
@enforce_types
def should_save_readability(link: Link, out_dir: Optional[str]=None, overwrite: Optional[bool]=False) -> bool:
@@ -87,14 +65,14 @@ def save_readability(link: Link, out_dir: Optional[str]=None, timeout: int=TIMEO
cmd = [
DEPENDENCIES['READABILITY_BINARY']['path'],
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
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")
@@ -102,11 +80,9 @@ 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', 3)[-3:]
for line in (result.stdout + result.stderr).decode().rsplit('\n', 5)[-5:]
if line.strip()
]
hints = (
@@ -116,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
@@ -9,6 +9,7 @@ from ..util import (
enforce_types,
is_static_file,
chrome_args,
chrome_cleanup,
)
from ..config import (
TIMEOUT,
@@ -37,7 +38,7 @@ def save_screenshot(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEO
out_dir = out_dir or Path(link.link_dir)
output: ArchiveOutput = 'screenshot.png'
cmd = [
*chrome_args(TIMEOUT=timeout),
*chrome_args(),
'--screenshot',
link.url,
]
@@ -54,6 +55,7 @@ def save_screenshot(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEO
except Exception as err:
status = 'failed'
output = err
chrome_cleanup()
finally:
timer.end()
+25 -3
View File
@@ -17,6 +17,7 @@ from ..config import (
SAVE_SINGLEFILE,
DEPENDENCIES,
SINGLEFILE_VERSION,
SINGLEFILE_ARGS,
CHROME_BINARY,
)
from ..logging_util import TimedProgress
@@ -41,14 +42,35 @@ def save_singlefile(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEO
out_dir = out_dir or Path(link.link_dir)
output = "singlefile.html"
browser_args = chrome_args(TIMEOUT=0)
browser_args = chrome_args(CHROME_TIMEOUT=0)
# SingleFile CLI Docs: https://github.com/gildas-lormeau/SingleFile/tree/master/cli
browser_args = '--browser-args={}'.format(json.dumps(browser_args[1:]))
cmd = [
DEPENDENCIES['SINGLEFILE_BINARY']['path'],
options = [
*SINGLEFILE_ARGS,
'--browser-executable-path={}'.format(CHROME_BINARY),
browser_args,
]
# Deduplicate options (single-file doesn't like when you use the same option two times)
#
# NOTE: Options names that come first clobber conflicting names that come later
# My logic is SINGLEFILE_ARGS is the option that affects the singlefile command with most
# specificity, therefore the user sets it with a lot intent, therefore it should take precedence
# kind of like the ergonomic principle of lexical scope in programming languages.
seen_option_names = []
def test_seen(argument):
option_name = argument.split("=")[0]
if option_name in seen_option_names:
return False
else:
seen_option_names.append(option_name)
return True
deduped_options = list(filter(test_seen, options))
cmd = [
DEPENDENCIES['SINGLEFILE_BINARY']['path'],
*deduped_options,
link.url,
output,
]
+25 -2
View File
@@ -26,7 +26,7 @@ from ..logging_util import TimedProgress
HTML_TITLE_REGEX = re.compile(
r'<title.*?>' # start matching text after <title> tag
r'(.[^<>]+)', # get everything up to these symbols
r'([^<>]+)', # get everything up to these symbols
re.IGNORECASE | re.MULTILINE | re.DOTALL | re.UNICODE,
)
@@ -58,6 +58,29 @@ class TitleParser(HTMLParser):
if tag.lower() == "title":
self.inside_title_tag = False
@enforce_types
def get_html(link: Link, path: Path, timeout: int=TIMEOUT) -> str:
"""
Try to find wget, singlefile and then dom files.
If none is found, download the url again.
"""
canonical = link.canonical_outputs()
abs_path = path.absolute()
# 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:
with open(abs_path / source, "r", encoding="utf-8") as f:
document = f.read()
break
except (FileNotFoundError, TypeError):
continue
if document is None:
return download_url(link.url, timeout=timeout)
else:
return document
@enforce_types
def should_save_title(link: Link, out_dir: Optional[str]=None, overwrite: Optional[bool]=False) -> bool:
@@ -90,7 +113,7 @@ def save_title(link: Link, out_dir: Optional[Path]=None, timeout: int=TIMEOUT) -
status = 'succeeded'
timer = TimedProgress(timeout, prefix=' ')
try:
html = download_url(link.url, timeout=timeout)
html = get_html(link, out_dir, timeout=timeout)
try:
# try using relatively strict html parser first
parser = TitleParser()
+5 -2
View File
@@ -22,7 +22,8 @@ from ..config import (
JSON_INDEX_FILENAME,
OUTPUT_DIR,
TIMEOUT,
URL_BLACKLIST_PTN,
URL_DENYLIST_PTN,
URL_ALLOWLIST_PTN,
stderr,
OUTPUT_PERMISSIONS
)
@@ -141,7 +142,9 @@ def archivable_links(links: Iterable[Link]) -> Iterable[Link]:
continue
if scheme(link.url) not in ('http', 'https', 'ftp'):
continue
if URL_BLACKLIST_PTN and URL_BLACKLIST_PTN.search(link.url):
if URL_DENYLIST_PTN and URL_DENYLIST_PTN.search(link.url):
continue
if URL_ALLOWLIST_PTN and (not URL_ALLOWLIST_PTN.search(link.url)):
continue
yield link
+4 -2
View File
@@ -24,6 +24,7 @@ from ..config import (
FOOTER_INFO,
HTML_INDEX_FILENAME,
SAVE_ARCHIVE_DOT_ORG,
PREVIEW_ORIGINALS,
)
MAIN_INDEX_TEMPLATE = 'static_index.html'
@@ -105,6 +106,7 @@ def link_details_template(link: Link) -> str:
'status_color': 'success' if link.is_archived else 'danger',
'oldest_archive_date': ts_to_date_str(link.oldest_archive_date),
'SAVE_ARCHIVE_DOT_ORG': SAVE_ARCHIVE_DOT_ORG,
'PREVIEW_ORIGINALS': PREVIEW_ORIGINALS,
})
@enforce_types
@@ -141,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)
@@ -175,7 +177,7 @@ def snapshot_icons(snapshot) -> str:
# The check for archive_org is different, so it has to be handled separately
# get from db (faster)
exists = extractor_outputs[extractor] and extractor_outputs[extractor].status == 'succeeded' and extractor_outputs[extractor].output
exists = extractor in extractor_outputs and extractor_outputs[extractor] and extractor_outputs[extractor].status == 'succeeded' and extractor_outputs[extractor].output
# get from filesystem (slower)
# target_path = Path(path) / "archive.org.txt"
# exists = target_path.exists()
+12 -1
View File
@@ -60,7 +60,18 @@ def parse_json_main_index(out_dir: Path=OUTPUT_DIR) -> Iterator[Link]:
index_path = Path(out_dir) / JSON_INDEX_FILENAME
if index_path.exists():
with open(index_path, 'r', encoding='utf-8') as f:
links = pyjson.load(f)['links']
try:
links = pyjson.load(f)['links']
if links:
Link.from_json(links[0])
except Exception as err:
print(" {lightyellow}! Found an index.json in the project root but couldn't load links from it: {} {}".format(
err.__class__.__name__,
err,
**ANSI,
))
return ()
for link_json in links:
try:
yield Link.from_json(link_json)
+4 -2
View File
@@ -20,7 +20,7 @@ from django.utils.functional import cached_property
from ..system import get_dir_size
from ..util import ts_to_date_str, parse_date
from ..config import OUTPUT_DIR, ARCHIVE_DIR_NAME
from ..config import OUTPUT_DIR, ARCHIVE_DIR_NAME, FAVICON_PROVIDER
class ArchiveError(Exception):
def __init__(self, message, hints=None):
@@ -423,12 +423,13 @@ class Link:
canonical = {
'index_path': 'index.html',
'favicon_path': 'favicon.ico',
'google_favicon_path': 'https://www.google.com/s2/favicons?domain={}'.format(self.domain),
'google_favicon_path': FAVICON_PROVIDER.format(self.domain),
'wget_path': wget_output_path(self),
'warc_path': 'warc/',
'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
+16 -8
View File
@@ -1,5 +1,7 @@
__package__ = 'archivebox.index'
import re
from io import StringIO
from pathlib import Path
from typing import List, Tuple, Iterator
@@ -8,7 +10,10 @@ from django.db import transaction
from .schema import Link
from ..util import enforce_types, parse_date
from ..config import OUTPUT_DIR
from ..config import (
OUTPUT_DIR,
TAG_SEPARATOR_PATTERN,
)
### Main Links Index
@@ -33,9 +38,11 @@ def remove_from_sql_main_index(snapshots: QuerySet, atomic: bool=False, out_dir:
def write_link_to_sql_index(link: Link):
from core.models import Snapshot, ArchiveResult
info = {k: v for k, v in link._asdict().items() if k in Snapshot.keys}
tags = info.pop("tags")
if tags is None:
tags = []
tag_list = list(dict.fromkeys(
tag.strip() for tag in re.split(TAG_SEPARATOR_PATTERN, link.tags or '')
))
info.pop('tags')
try:
info["timestamp"] = Snapshot.objects.get(url=link.url).timestamp
@@ -44,7 +51,7 @@ def write_link_to_sql_index(link: Link):
info["timestamp"] = str(float(info["timestamp"]) + 1.0)
snapshot, _ = Snapshot.objects.update_or_create(url=link.url, defaults=info)
snapshot.save_tags(tags)
snapshot.save_tags(tag_list)
for extractor, entries in link.history.items():
for entry in entries:
@@ -102,12 +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_set = (
set(tag.strip() for tag in (link.tags or '').split(','))
tag_list = list(
{tag.strip() for tag in re.split(TAG_SEPARATOR_PATTERN, link.tags or '')}
| set(snap.tags.values_list('name', flat=True))
)
tag_list = list(tag_set) or []
snap.save()
snap.save_tags(tag_list)
+53 -24
View File
@@ -108,12 +108,12 @@ def reject_stdin(caller: str, stdin: Optional[IO]=sys.stdin) -> None:
if not stdin.isatty():
# stderr('READING STDIN TO REJECT...')
stdin_raw_text = stdin.read()
if stdin_raw_text:
if stdin_raw_text.strip():
# stderr('GOT STDIN!', len(stdin_str))
stderr(f'[X] The "{caller}" command does not accept stdin.', color='red')
stderr(f'[!] The "{caller}" command does not accept stdin (ignoring).', color='red')
stderr(f' Run archivebox "{caller} --help" to see usage and examples.')
stderr()
raise SystemExit(1)
# raise SystemExit(1)
return None
@@ -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
)
@@ -432,18 +436,30 @@ def log_archive_method_finished(result: "ArchiveResult"):
# Prettify error output hints string and limit to five lines
hints = getattr(result.output, 'hints', None) or ()
if hints:
hints = hints if isinstance(hints, (list, tuple)) else hints.split('\n')
if isinstance(hints, (list, tuple, type(_ for _ in ()))):
hints = [hint.decode() for hint in hints if isinstance(hint, bytes)]
else:
if isinstance(hints, bytes):
hints = hints.decode()
hints = hints.split('\n')
hints = (
' {}{}{}'.format(ANSI['lightyellow'], line.strip(), ANSI['reset'])
for line in hints[:5] if line.strip()
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),
]
@@ -511,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')
@@ -527,11 +543,27 @@ def log_shell_welcome_msg():
### Helpers
@enforce_types
def pretty_path(path: Union[Path, str]) -> str:
def pretty_path(path: Union[Path, str], pwd: Union[Path, str]=OUTPUT_DIR) -> str:
"""convert paths like .../ArchiveBox/archivebox/../output/abc into output/abc"""
pwd = Path('.').resolve()
# parent = os.path.abspath(os.path.join(pwd, os.path.pardir))
return str(path).replace(str(pwd) + '/', './')
pwd = str(Path(pwd)) # .resolve()
path = str(path)
if not path:
return path
# replace long absolute paths with ./ relative ones to save on terminal output width
if path.startswith(pwd) and (pwd != '/'):
path = path.replace(pwd, '.', 1)
# quote paths containing spaces
if ' ' in path:
path = f'"{path}"'
# if path is just a plain dot, replace it back with the absolute path for clarity
if path == '.':
path = pwd
return path
@enforce_types
@@ -566,12 +598,13 @@ def printable_config(config: ConfigDict, prefix: str='') -> str:
def printable_folder_status(name: str, folder: Dict) -> str:
if folder['enabled']:
if folder['is_valid']:
color, symbol, note = 'green', '√', 'valid'
color, symbol, note, num_files = 'green', '√', 'valid', ''
else:
color, symbol, note, num_files = 'red', 'X', 'invalid', '?'
else:
color, symbol, note, num_files = 'lightyellow', '-', 'disabled', '-'
if folder['path']:
if Path(folder['path']).exists():
num_files = (
@@ -581,14 +614,12 @@ def printable_folder_status(name: str, folder: Dict) -> str:
)
else:
num_files = 'missing'
if folder.get('is_mount'):
# add symbol @ next to filecount if path is a remote filesystem mount
num_files = f'{num_files} @' if num_files else '@'
path = str(folder['path']).replace(str(OUTPUT_DIR), '.') if folder['path'] else ''
if path and ' ' in path:
path = f'"{path}"'
# if path is just a plain dot, replace it back with the full path for clarity
if path == '.':
path = str(OUTPUT_DIR)
path = pretty_path(folder['path'])
return ' '.join((
ANSI[color],
@@ -619,9 +650,7 @@ def printable_dependency_version(name: str, dependency: Dict) -> str:
else:
color, symbol, note, version = 'lightyellow', '-', 'disabled', '-'
path = str(dependency["path"]).replace(str(OUTPUT_DIR), '.') if dependency["path"] else ''
if path and ' ' in path:
path = f'"{path}"'
path = pretty_path(dependency['path'])
return ' '.join((
ANSI[color],
Regular → Executable
+183 -148
View File
@@ -4,8 +4,9 @@ import os
import sys
import shutil
import platform
from django.utils import timezone
from pathlib import Path
from datetime import date
from datetime import date, datetime
from typing import Dict, List, Optional, Iterable, IO, Union
from crontab import CronTab, CronSlices
@@ -70,7 +71,13 @@ from .config import (
IS_TTY,
DEBUG,
IN_DOCKER,
IN_QEMU,
PUID,
PGID,
USER,
TIMEZONE,
ENFORCE_ATOMIC_WRITES,
OUTPUT_PERMISSIONS,
PYTHON_BINARY,
ARCHIVEBOX_BINARY,
ONLY_NEW,
@@ -80,25 +87,26 @@ from .config import (
LOGS_DIR,
PACKAGE_DIR,
CONFIG_FILE,
CONFIG_FILENAME,
ARCHIVE_DIR_NAME,
SOURCES_DIR_NAME,
LOGS_DIR_NAME,
JSON_INDEX_FILENAME,
HTML_INDEX_FILENAME,
SQL_INDEX_FILENAME,
ROBOTS_TXT_FILENAME,
FAVICON_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,
DEPENDENCIES,
USE_CHROME,
CHROME_BINARY,
CHROME_VERSION,
YOUTUBEDL_BINARY,
@@ -106,12 +114,12 @@ from .config import (
SINGLEFILE_VERSION,
READABILITY_VERSION,
MERCURY_VERSION,
USE_YOUTUBEDL,
USE_NODE,
NODE_VERSION,
load_all_config,
CONFIG,
USER_CONFIG,
ADMIN_USERNAME,
ADMIN_PASSWORD,
get_real_name,
setup_django,
)
@@ -133,33 +141,7 @@ from .logging_util import (
from .search import flush_search_index, index_links
ALLOWED_IN_OUTPUT_DIR = {
'.gitignore',
'lost+found',
'.DS_Store',
'.venv',
'venv',
'virtualenv',
'.virtualenv',
'node_modules',
'package.json',
'package-lock.json',
'yarn.lock',
'static',
'sonic',
ARCHIVE_DIR_NAME,
SOURCES_DIR_NAME,
LOGS_DIR_NAME,
SQL_INDEX_FILENAME,
f'{SQL_INDEX_FILENAME}-wal',
f'{SQL_INDEX_FILENAME}-shm',
JSON_INDEX_FILENAME,
HTML_INDEX_FILENAME,
ROBOTS_TXT_FILENAME,
FAVICON_FILENAME,
CONFIG_FILENAME,
f'{CONFIG_FILENAME}.bak',
}
@enforce_types
def help(out_dir: Path=OUTPUT_DIR) -> None:
@@ -236,51 +218,73 @@ def help(out_dir: Path=OUTPUT_DIR) -> None:
def version(quiet: bool=False,
out_dir: Path=OUTPUT_DIR) -> None:
"""Print the ArchiveBox version and dependency information"""
if quiet:
print(VERSION)
else:
# ArchiveBox v0.5.6
# Cpython Linux Linux-4.19.121-linuxkit-x86_64-with-glibc2.28 x86_64 (in Docker) (in TTY)
print('ArchiveBox v{}'.format(VERSION))
print(VERSION)
if not quiet:
# 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(
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'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={os.environ.get("TZ", "UTC")}',
f'SEARCH_BACKEND_ENGINE={SEARCH_BACKEND_ENGINE}',
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()
print('{white}[i] Dependency versions:{reset}'.format(**ANSI))
for name, dependency in DEPENDENCIES.items():
print(printable_dependency_version(name, dependency))
# add a newline between core dependencies and extractor dependencies for easier reading
if name == 'ARCHIVEBOX_BINARY':
print()
print()
print('{white}[i] Source-code locations:{reset}'.format(**ANSI))
for name, folder in CODE_LOCATIONS.items():
print(printable_folder_status(name, folder))
for name, path in CODE_LOCATIONS.items():
print(printable_folder_status(name, path))
print()
print('{white}[i] Secrets locations:{reset}'.format(**ANSI))
for name, folder in EXTERNAL_LOCATIONS.items():
print(printable_folder_status(name, folder))
for name, path in EXTERNAL_LOCATIONS.items():
print(printable_folder_status(name, path))
print()
if DATA_LOCATIONS['OUTPUT_DIR']['is_valid']:
print('{white}[i] Data locations:{reset}'.format(**ANSI))
for name, folder in DATA_LOCATIONS.items():
print(printable_folder_status(name, folder))
for name, path in DATA_LOCATIONS.items():
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()
@@ -311,7 +315,7 @@ def init(force: bool=False, quick: bool=False, setup: bool=False, out_dir: Path=
if (out_dir / JSON_INDEX_FILENAME).exists():
stderr("[!] This folder contains a JSON index. It is deprecated, and will no longer be kept up to date automatically.", color="lightyellow")
stderr(" You can run `archivebox list --json --with-headers > index.json` to manually generate it.", color="lightyellow")
stderr(" You can run `archivebox list --json --with-headers > static_index.json` to manually generate it.", color="lightyellow")
existing_index = (out_dir / SQL_INDEX_FILENAME).exists()
@@ -433,6 +437,13 @@ def init(force: bool=False, quick: bool=False, setup: bool=False, out_dir: Path=
write_main_index(list(pending_links.values()), out_dir=out_dir)
print('\n{green}----------------------------------------------------------------------{reset}'.format(**ANSI))
from django.contrib.auth.models import User
if (ADMIN_USERNAME and ADMIN_PASSWORD) and not User.objects.filter(username=ADMIN_USERNAME).exists():
print('{green}[+] Found ADMIN_USERNAME and ADMIN_PASSWORD configuration options, creating new admin user.{reset}'.format(**ANSI))
User.objects.create_superuser(username=ADMIN_USERNAME, password=ADMIN_PASSWORD)
if existing_index:
print('{green}[√] Done. Verified and updated the existing ArchiveBox collection.{reset}'.format(**ANSI))
else:
@@ -455,7 +466,7 @@ def init(force: bool=False, quick: bool=False, setup: bool=False, out_dir: Path=
print(' archivebox server # then visit http://127.0.0.1:8000')
print()
print(' To add new links, you can run:')
print(" archivebox add ~/some/path/or/url/to/list_of_links.txt")
print(" archivebox add < ~/some/path/to/list_of_links.txt")
print()
print(' For more usage and examples, run:')
print(' archivebox help')
@@ -582,7 +593,8 @@ def oneshot(url: str, extractors: str="", out_dir: Path=OUTPUT_DIR):
def add(urls: Union[str, List[str]],
tag: str='',
depth: int=0,
update_all: bool=not ONLY_NEW,
update: bool=not ONLY_NEW,
update_all: bool=False,
index_only: bool=False,
overwrite: bool=False,
# duplicate: bool=False, # TODO: reuse the logic from admin.py resnapshot to allow adding multiple snapshots by appending timestamp automatically
@@ -592,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)'
@@ -615,6 +627,7 @@ def add(urls: Union[str, List[str]],
# save verbatim args to sources
write_ahead_log = save_text_as_source('\n'.join(urls), filename='{ts}-import.txt', out_dir=out_dir)
new_links += parse_links_from_source(write_ahead_log, root_url=None, parser=parser)
# If we're going one level deeper, download each link and look for more links
@@ -622,8 +635,11 @@ def add(urls: Union[str, List[str]],
if new_links and depth == 1:
log_crawl_started(new_links)
for new_link in new_links:
downloaded_file = save_file_as_source(new_link.url, filename=f'{new_link.timestamp}-crawl-{new_link.domain}.txt', out_dir=out_dir)
new_links_depth += parse_links_from_source(downloaded_file, root_url=new_link.url)
try:
downloaded_file = save_file_as_source(new_link.url, filename=f'{new_link.timestamp}-crawl-{new_link.domain}.txt', out_dir=out_dir)
new_links_depth += parse_links_from_source(downloaded_file, root_url=new_link.url)
except Exception as err:
stderr('[!] Failed to get contents of URL {new_link.url}', err, color='red')
imported_links = list({link.url: link for link in (new_links + new_links_depth)}.values())
@@ -632,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:
@@ -646,28 +675,25 @@ def add(urls: Union[str, List[str]],
if extractors:
archive_kwargs["methods"] = extractors
if update_all:
stderr()
ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
if update:
stderr(f'[*] [{ts}] Archiving + updating {len(imported_links)}/{len(all_links)}', len(imported_links), 'URLs from added set...', color='green')
archive_links(imported_links, overwrite=overwrite, **archive_kwargs)
elif update_all:
stderr(f'[*] [{ts}] Archiving + updating {len(all_links)}/{len(all_links)}', len(all_links), 'URLs from entire library...', color='green')
archive_links(all_links, overwrite=overwrite, **archive_kwargs)
elif overwrite:
stderr(f'[*] [{ts}] Archiving + overwriting {len(imported_links)}/{len(all_links)}', len(imported_links), 'URLs from added set...', color='green')
archive_links(imported_links, overwrite=True, **archive_kwargs)
elif new_links:
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
@@ -940,34 +966,35 @@ def setup(out_dir: Path=OUTPUT_DIR) -> None:
stderr('\n[+] Installing enabled ArchiveBox dependencies automatically...', color='green')
stderr('\n Installing YOUTUBEDL_BINARY automatically using pip...')
if USE_YOUTUBEDL:
if YOUTUBEDL_VERSION:
print(f'{YOUTUBEDL_VERSION} is already installed', YOUTUBEDL_BINARY)
else:
try:
run_shell([
PYTHON_BINARY, '-m', 'pip',
'install',
'--upgrade',
'--no-cache-dir',
'--no-warn-script-location',
'youtube_dl',
], capture_output=False, cwd=out_dir)
pkg_path = run_shell([
PYTHON_BINARY, '-m', 'pip',
'show',
'youtube_dl',
], capture_output=True, text=True, cwd=out_dir).stdout.split('Location: ')[-1].split('\n', 1)[0]
NEW_YOUTUBEDL_BINARY = Path(pkg_path) / 'youtube_dl' / '__main__.py'
os.chmod(NEW_YOUTUBEDL_BINARY, 0o777)
assert NEW_YOUTUBEDL_BINARY.exists(), f'youtube_dl must exist inside {pkg_path}'
config(f'YOUTUBEDL_BINARY={NEW_YOUTUBEDL_BINARY}', set=True, out_dir=out_dir)
except BaseException as e:
stderr(f'[X] Failed to install python packages: {e}', color='red')
raise SystemExit(1)
if YOUTUBEDL_VERSION:
print(f'{YOUTUBEDL_VERSION} is already installed', YOUTUBEDL_BINARY)
else:
try:
run_shell([
PYTHON_BINARY, '-m', 'pip',
'install',
'--upgrade',
'--no-cache-dir',
'--no-warn-script-location',
'youtube_dl',
], capture_output=False, cwd=out_dir)
pkg_path = run_shell([
PYTHON_BINARY, '-m', 'pip',
'show',
'youtube_dl',
], capture_output=True, text=True, cwd=out_dir).stdout.decode().split('Location: ')[-1].split('\n', 1)[0]
NEW_YOUTUBEDL_BINARY = Path(pkg_path) / 'youtube_dl' / '__main__.py'
os.chmod(NEW_YOUTUBEDL_BINARY, 0o777)
assert NEW_YOUTUBEDL_BINARY.exists(), f'youtube_dl must exist inside {pkg_path}'
config(f'YOUTUBEDL_BINARY={NEW_YOUTUBEDL_BINARY}', set=True, out_dir=out_dir)
except BaseException as e: # lgtm [py/catch-base-exception]
stderr(f'[X] Failed to install python packages: {e}', color='red')
raise SystemExit(1)
stderr('\n Installing CHROME_BINARY automatically using playwright...')
if USE_CHROME:
if platform.machine() == 'armv7l':
stderr('\n Skip the automatic installation of CHROME_BINARY because playwright is not available on armv7.')
else:
stderr('\n Installing CHROME_BINARY automatically using playwright...')
if CHROME_VERSION:
print(f'{CHROME_VERSION} is already installed', CHROME_BINARY)
else:
@@ -982,59 +1009,60 @@ def setup(out_dir: Path=OUTPUT_DIR) -> None:
], capture_output=False, cwd=out_dir)
run_shell([PYTHON_BINARY, '-m', 'playwright', 'install', 'chromium'], capture_output=False, cwd=out_dir)
proc = run_shell([PYTHON_BINARY, '-c', 'from playwright.sync_api import sync_playwright; print(sync_playwright().start().chromium.executable_path)'], capture_output=True, text=True, cwd=out_dir)
NEW_CHROME_BINARY = proc.stdout.strip()
NEW_CHROME_BINARY = proc.stdout.decode().strip() if isinstance(proc.stdout, bytes) else proc.stdout.strip()
assert NEW_CHROME_BINARY and len(NEW_CHROME_BINARY), 'CHROME_BINARY must contain a path'
config(f'CHROME_BINARY={NEW_CHROME_BINARY}', set=True, out_dir=out_dir)
except BaseException as e:
except BaseException as e: # lgtm [py/catch-base-exception]
stderr(f'[X] Failed to install chromium using playwright: {e.__class__.__name__} {e}', color='red')
raise SystemExit(1)
stderr('\n Installing SINGLEFILE_BINARY, READABILITY_BINARY, MERCURY_BINARY automatically using npm...')
if USE_NODE:
if not NODE_VERSION:
stderr('[X] You must first install node using your system package manager', color='red')
hint([
'curl -sL https://deb.nodesource.com/setup_15.x | sudo -E bash -',
'or to disable all node-based modules run: archivebox config --set USE_NODE=False',
])
if not NODE_VERSION:
stderr('[X] You must first install node & npm using your system package manager', color='red')
hint([
'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)
if all((SINGLEFILE_VERSION, READABILITY_VERSION, MERCURY_VERSION)):
print('SINGLEFILE_BINARY, READABILITY_BINARY, and MERCURURY_BINARY are already installed')
else:
try:
# clear out old npm package locations
paths = (
out_dir / 'package.json',
out_dir / 'package_lock.json',
out_dir / 'node_modules',
)
for path in paths:
if path.is_dir():
shutil.rmtree(path, ignore_errors=True)
elif path.is_file():
os.remove(path)
shutil.copyfile(PACKAGE_DIR / 'package.json', out_dir / 'package.json') # copy the js requirements list from the source install into the data dir
# lets blindly assume that calling out to npm via shell works reliably cross-platform 🤡 (until proven otherwise via support tickets)
run_shell([
'npm',
'install',
'--prefix', str(out_dir), # force it to put the node_modules dir in this folder
'--force', # overwrite any existing node_modules
'--no-save', # don't bother saving updating the package.json or package-lock.json file
'--no-audit', # don't bother checking for newer versions with security vuln fixes
'--no-fund', # hide "please fund our project" messages
'--loglevel', 'error', # only show erros (hide warn/info/debug) during installation
# these args are written in blood, change with caution
], capture_output=False, cwd=out_dir)
os.remove(out_dir / 'package.json')
except BaseException as e: # lgtm [py/catch-base-exception]
stderr(f'[X] Failed to install npm packages: {e}', color='red')
hint(f'Try deleting {out_dir}/node_modules and running it again')
raise SystemExit(1)
if all((SINGLEFILE_VERSION, READABILITY_VERSION, MERCURY_VERSION)):
print('SINGLEFILE_BINARY, READABILITY_BINARY, and MERCURURY_BINARY are already installed')
else:
try:
# clear out old npm package locations
paths = (
out_dir / 'package.json',
out_dir / 'package_lock.json',
out_dir / 'node_modules',
)
for path in paths:
if path.is_dir():
shutil.rmtree(path, ignore_errors=True)
elif path.is_file():
os.remove(path)
shutil.copyfile(PACKAGE_DIR / 'package.json', out_dir / 'package.json')
run_shell([
'npm',
'install',
'--prefix', str(out_dir),
'--force',
'--no-save',
'--no-audit',
'--no-fund',
'--loglevel', 'error',
], capture_output=False, cwd=out_dir)
os.remove(out_dir / 'package.json')
except BaseException as e:
stderr(f'[X] Failed to install npm packages: {e}', color='red')
hint(f'Try deleting {out_dir}/node_modules and running it again')
raise SystemExit(1)
stderr('\n[√] Set up ArchiveBox and its dependencies successfully.', color='green')
run_shell([ARCHIVEBOX_BINARY, '--version'], capture_output=False, cwd=out_dir)
run_shell([PYTHON_BINARY, ARCHIVEBOX_BINARY, '--version'], capture_output=False, cwd=out_dir)
@enforce_types
def config(config_options_str: Optional[str]=None,
@@ -1140,8 +1168,10 @@ 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,
import_path: Optional[str]=None,
out_dir: Path=OUTPUT_DIR):
"""Set ArchiveBox to regularly import URLs at specific times using cron"""
@@ -1171,6 +1201,8 @@ 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']),
@@ -1253,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,
-1
View File
@@ -1 +0,0 @@
../package.json
+13
View File
@@ -0,0 +1,13 @@
{
"name": "archivebox",
"version": "0.7.2",
"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": "github:ArchiveBox/readability-extractor",
"single-file-cli": "^1.1.46"
}
}
+18 -2
View File
@@ -34,6 +34,7 @@ from ..index.schema import Link
from ..logging_util import TimedProgress, log_source_saved
from . import pocket_api
from . import readwise_reader_api
from . import wallabag_atom
from . import pocket_html
from . import pinboard_rss
@@ -51,6 +52,7 @@ from . import url_list
PARSERS = {
# Specialized parsers
pocket_api.KEY: (pocket_api.NAME, pocket_api.PARSER),
readwise_reader_api.KEY: (readwise_reader_api.NAME, readwise_reader_api.PARSER),
wallabag_atom.KEY: (wallabag_atom.NAME, wallabag_atom.PARSER),
pocket_html.KEY: (pocket_html.NAME, pocket_html.PARSER),
pinboard_rss.KEY: (pinboard_rss.NAME, pinboard_rss.PARSER),
@@ -149,7 +151,17 @@ def run_parser_functions(to_parse: IO[str], timer, root_url: Optional[str]=None,
def save_text_as_source(raw_text: str, filename: str='{ts}-stdin.txt', out_dir: Path=OUTPUT_DIR) -> str:
ts = str(datetime.now(timezone.utc).timestamp()).split('.', 1)[0]
source_path = str(out_dir / SOURCES_DIR_NAME / filename.format(ts=ts))
atomic_write(source_path, raw_text)
referenced_texts = ''
for entry in raw_text.split():
try:
if Path(entry).exists():
referenced_texts += Path(entry).read_text()
except Exception as err:
print(err)
atomic_write(source_path, raw_text + '\n' + referenced_texts)
log_source_saved(source_file=source_path)
return source_path
@@ -176,7 +188,7 @@ def save_file_as_source(path: str, timeout: int=TIMEOUT, filename: str='{ts}-{ba
ANSI['reset'],
))
print(' ', e)
raise SystemExit(1)
raise e
else:
# Source is a path to a local file on the filesystem
@@ -223,6 +235,10 @@ _test_url_strs = {
'https://example.com/?what=1#how-about-this=1&2%20baf': 1,
'https://example.com?what=1#how-about-this=1&2%20baf': 1,
'<test>http://example7.com</test>': 1,
'https://<test>': 0,
'https://[test]': 0,
'http://"test"': 0,
'http://\'test\'': 0,
'[https://example8.com/what/is/this.php?what=1]': 1,
'[and http://example9.com?what=1&other=3#and-thing=2]': 1,
'<what>https://example10.com#and-thing=2 "</about>': 1,
+4 -1
View File
@@ -17,7 +17,10 @@ def parse_generic_json_export(json_file: IO[str], **_kwargs) -> Iterable[Link]:
"""Parse JSON-format bookmarks export files (produced by pinboard.in/export/, or wallabag)"""
json_file.seek(0)
links = json.load(json_file)
# sometimes the first line is a comment or filepath, so we get everything after the first {
json_file_json_str = '{' + json_file.read().split('{', 1)[-1]
links = json.loads(json_file_json_str)
json_date = lambda s: datetime.strptime(s, '%Y-%m-%dT%H:%M:%S%z')
for link in links:
+6 -1
View File
@@ -21,13 +21,18 @@ def parse_pinboard_rss_export(rss_file: IO[str], **_kwargs) -> Iterable[Link]:
root = ElementTree.parse(rss_file).getroot()
items = root.findall("{http://purl.org/rss/1.0/}item")
for item in items:
find = lambda p: item.find(p).text.strip() if item.find(p) else None # type: ignore
find = lambda p: item.find(p).text.strip() if item.find(p) is not None else None # type: ignore
url = find("{http://purl.org/rss/1.0/}link")
tags = find("{http://purl.org/dc/elements/1.1/}subject")
title = find("{http://purl.org/rss/1.0/}title")
ts_str = find("{http://purl.org/dc/elements/1.1/}date")
if url is None:
# Yielding a Link with no URL will
# crash on a URL validation assertion
continue
# Pinboard includes a colon in its date stamp timezone offsets, which
# Python can't parse. Remove it:
if ts_str and ts_str[-3:-2] == ":":
+2 -2
View File
@@ -47,11 +47,11 @@ def get_pocket_articles(api: Pocket, since=None, page=0):
def link_from_article(article: dict, sources: list):
url: str = article['resolved_url'] or article['given_url']
url: str = article.get('resolved_url') or article['given_url']
broken_protocol = _BROKEN_PROTOCOL_RE.match(url)
if broken_protocol:
url = url.replace(f'{broken_protocol.group(1)}:/', f'{broken_protocol.group(1)}://')
title = article['resolved_title'] or article['given_title'] or url
title = article.get('resolved_title') or article.get('given_title') or url
return Link(
url=url,
+123
View File
@@ -0,0 +1,123 @@
__package__ = "archivebox.parsers"
import re
import requests
from datetime import datetime
from typing import IO, Iterable, Optional
from configparser import ConfigParser
from pathlib import Path
from ..index.schema import Link
from ..util import enforce_types
from ..system import atomic_write
from ..config import (
SOURCES_DIR,
READWISE_READER_TOKENS,
)
API_DB_PATH = Path(SOURCES_DIR) / "readwise_reader_api.db"
class ReadwiseReaderAPI:
cursor: Optional[str]
def __init__(self, api_token, cursor=None) -> None:
self.api_token = api_token
self.cursor = cursor
def get_archive(self):
response = requests.get(
url="https://readwise.io/api/v3/list/",
headers={"Authorization": f"Token {self.api_token}"},
params={
"location": "archive",
"pageCursor": self.cursor,
}
)
response.raise_for_status()
return response
def get_readwise_reader_articles(api: ReadwiseReaderAPI):
response = api.get_archive()
body = response.json()
articles = body["results"]
yield from articles
if body['nextPageCursor']:
api.cursor = body["nextPageCursor"]
yield from get_readwise_reader_articles(api)
def link_from_article(article: dict, sources: list):
url: str = article['source_url']
title = article["title"] or url
timestamp = datetime.fromisoformat(article['updated_at']).timestamp()
return Link(
url=url,
timestamp=str(timestamp),
title=title,
tags="",
sources=sources,
)
def write_cursor(username: str, since: str):
if not API_DB_PATH.exists():
atomic_write(API_DB_PATH, "")
since_file = ConfigParser()
since_file.optionxform = str
since_file.read(API_DB_PATH)
since_file[username] = {"since": since}
with open(API_DB_PATH, "w+") as new:
since_file.write(new)
def read_cursor(username: str) -> Optional[str]:
if not API_DB_PATH.exists():
atomic_write(API_DB_PATH, "")
config_file = ConfigParser()
config_file.optionxform = str
config_file.read(API_DB_PATH)
return config_file.get(username, "since", fallback=None)
@enforce_types
def should_parse_as_readwise_reader_api(text: str) -> bool:
return text.startswith("readwise-reader://")
@enforce_types
def parse_readwise_reader_api_export(input_buffer: IO[str], **_kwargs) -> Iterable[Link]:
"""Parse bookmarks from the Readwise Reader API"""
input_buffer.seek(0)
pattern = re.compile(r"^readwise-reader:\/\/(\w+)")
for line in input_buffer:
if should_parse_as_readwise_reader_api(line):
username = pattern.search(line).group(1)
api = ReadwiseReaderAPI(READWISE_READER_TOKENS[username], cursor=read_cursor(username))
for article in get_readwise_reader_articles(api):
yield link_from_article(article, sources=[line])
if api.cursor:
write_cursor(username, api.cursor)
KEY = "readwise_reader_api"
NAME = "Readwise Reader API"
PARSER = parse_readwise_reader_api_export
+11 -5
View File
@@ -34,13 +34,19 @@ def parse_wallabag_atom_export(rss_file: IO[str], **_kwargs) -> Iterable[Link]:
trailing_removed = entry.split('</entry>', 1)[0]
leading_removed = trailing_removed.strip()
rows = leading_removed.split('\n')
splits_fixed = leading_removed.replace('"\n href="', '" href="')
rows = splits_fixed.split('\n')
def get_row(key):
return [r.strip() for r in rows if r.strip().startswith('<{}'.format(key))][0]
def get_row(prefix):
return [
row.strip()
for row in rows
if row.strip().startswith('<{}'.format(prefix))
][0]
title = str_between(get_row('title'), '<title><![CDATA[', ']]></title>').strip()
url = str_between(get_row('link rel="via"'), '<link rel="via">', '</link>')
url_inside_link = str_between(get_row('link rel="via"'), '<link rel="via">', '</link>')
url_inside_attr = str_between(get_row('link rel="via"'), 'href="', '"/>')
ts_str = str_between(get_row('published'), '<published>', '</published>')
time = datetime.strptime(ts_str, "%Y-%m-%dT%H:%M:%S%z")
try:
@@ -49,7 +55,7 @@ def parse_wallabag_atom_export(rss_file: IO[str], **_kwargs) -> Iterable[Link]:
tags = None
yield Link(
url=htmldecode(url),
url=htmldecode(url_inside_attr or url_inside_link),
timestamp=str(time.timestamp()),
title=htmldecode(title) or None,
tags=tags or '',
+195
View File
@@ -0,0 +1,195 @@
import codecs
from typing import List, Generator
import sqlite3
from archivebox.util import enforce_types
from archivebox.config import (
FTS_SEPARATE_DATABASE,
FTS_TOKENIZERS,
FTS_SQLITE_MAX_LENGTH
)
FTS_TABLE = "snapshot_fts"
FTS_ID_TABLE = "snapshot_id_fts"
FTS_COLUMN = "texts"
if FTS_SEPARATE_DATABASE:
database = sqlite3.connect("search.sqlite3")
# Make get_connection callable, because `django.db.connection.cursor()`
# has to be called to get a context manager, but sqlite3.Connection
# is a context manager without being called.
def get_connection():
return database
SQLITE_BIND = "?"
else:
from django.db import connection as database # type: ignore[no-redef, assignment]
get_connection = database.cursor
SQLITE_BIND = "%s"
# Only Python >= 3.11 supports sqlite3.Connection.getlimit(),
# so fall back to the default if the API to get the real value isn't present
try:
limit_id = sqlite3.SQLITE_LIMIT_LENGTH
try:
with database.temporary_connection() as cursor: # type: ignore[attr-defined]
SQLITE_LIMIT_LENGTH = cursor.connection.getlimit(limit_id)
except AttributeError:
SQLITE_LIMIT_LENGTH = database.getlimit(limit_id)
except AttributeError:
SQLITE_LIMIT_LENGTH = FTS_SQLITE_MAX_LENGTH
def _escape_sqlite3(value: str, *, quote: str, errors='strict') -> str:
assert isinstance(quote, str), "quote is not a str"
assert len(quote) == 1, "quote must be a single character"
encodable = value.encode('utf-8', errors).decode('utf-8')
nul_index = encodable.find("\x00")
if nul_index >= 0:
error = UnicodeEncodeError("NUL-terminated utf-8", encodable,
nul_index, nul_index + 1, "NUL not allowed")
error_handler = codecs.lookup_error(errors)
replacement, _ = error_handler(error)
assert isinstance(replacement, str), "handling a UnicodeEncodeError should return a str replacement"
encodable = encodable.replace("\x00", replacement)
return quote + encodable.replace(quote, quote * 2) + quote
def _escape_sqlite3_value(value: str, errors='strict') -> str:
return _escape_sqlite3(value, quote="'", errors=errors)
def _escape_sqlite3_identifier(value: str) -> str:
return _escape_sqlite3(value, quote='"', errors='strict')
@enforce_types
def _create_tables():
table = _escape_sqlite3_identifier(FTS_TABLE)
# Escape as value, because fts5() expects
# string literal column names
column = _escape_sqlite3_value(FTS_COLUMN)
id_table = _escape_sqlite3_identifier(FTS_ID_TABLE)
tokenizers = _escape_sqlite3_value(FTS_TOKENIZERS)
trigger_name = _escape_sqlite3_identifier(f"{FTS_ID_TABLE}_ad")
with get_connection() as cursor:
# Create a contentless-delete FTS5 table that indexes
# but does not store the texts of snapshots
try:
cursor.execute(
f"CREATE VIRTUAL TABLE {table}"
f" USING fts5({column},"
f" tokenize={tokenizers},"
" content='', contentless_delete=1);"
)
except Exception as e:
msg = str(e)
if 'unrecognized option: "contentlessdelete"' in msg:
sqlite_version = getattr(sqlite3, "sqlite_version", "Unknown")
raise RuntimeError(
"SQLite full-text search requires SQLite >= 3.43.0;"
f" the running version is {sqlite_version}"
) from e
else:
raise
# Create a one-to-one mapping between ArchiveBox snapshot_id
# and FTS5 rowid, because the column type of rowid can't be
# customized.
cursor.execute(
f"CREATE TABLE {id_table}("
" rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
" snapshot_id char(32) NOT NULL UNIQUE"
");"
)
# Create a trigger to delete items from the FTS5 index when
# the snapshot_id is deleted from the mapping, to maintain
# consistency and make the `flush()` query simpler.
cursor.execute(
f"CREATE TRIGGER {trigger_name}"
f" AFTER DELETE ON {id_table} BEGIN"
f" DELETE FROM {table} WHERE rowid=old.rowid;"
" END;"
)
def _handle_query_exception(exc: Exception):
message = str(exc)
if message.startswith("no such table:"):
raise RuntimeError(
"SQLite full-text search index has not yet"
" been created; run `archivebox update --index-only`."
)
else:
raise exc
@enforce_types
def index(snapshot_id: str, texts: List[str]):
text = ' '.join(texts)[:SQLITE_LIMIT_LENGTH]
table = _escape_sqlite3_identifier(FTS_TABLE)
column = _escape_sqlite3_identifier(FTS_COLUMN)
id_table = _escape_sqlite3_identifier(FTS_ID_TABLE)
with get_connection() as cursor:
retries = 2
while retries > 0:
retries -= 1
try:
# If there is already an FTS index rowid to snapshot_id mapping,
# then don't insert a new one, silently ignoring the operation.
# {id_table}.rowid is AUTOINCREMENT, so will generate an unused
# rowid for the index if it is an unindexed snapshot_id.
cursor.execute(
f"INSERT OR IGNORE INTO {id_table}(snapshot_id) VALUES({SQLITE_BIND})",
[snapshot_id])
# Fetch the FTS index rowid for the given snapshot_id
id_res = cursor.execute(
f"SELECT rowid FROM {id_table} WHERE snapshot_id = {SQLITE_BIND}",
[snapshot_id])
rowid = id_res.fetchone()[0]
# (Re-)index the content
cursor.execute(
"INSERT OR REPLACE INTO"
f" {table}(rowid, {column}) VALUES ({SQLITE_BIND}, {SQLITE_BIND})",
[rowid, text])
# All statements succeeded; return
return
except Exception as e:
if str(e).startswith("no such table:") and retries > 0:
_create_tables()
else:
raise
raise RuntimeError("Failed to create tables for SQLite FTS5 search")
@enforce_types
def search(text: str) -> List[str]:
table = _escape_sqlite3_identifier(FTS_TABLE)
id_table = _escape_sqlite3_identifier(FTS_ID_TABLE)
with get_connection() as cursor:
try:
res = cursor.execute(
f"SELECT snapshot_id FROM {table}"
f" INNER JOIN {id_table}"
f" ON {id_table}.rowid = {table}.rowid"
f" WHERE {table} MATCH {SQLITE_BIND}",
[text])
except Exception as e:
_handle_query_exception(e)
snap_ids = [row[0] for row in res.fetchall()]
return snap_ids
@enforce_types
def flush(snapshot_ids: Generator[str, None, None]):
snapshot_ids = list(snapshot_ids) # type: ignore[assignment]
id_table = _escape_sqlite3_identifier(FTS_ID_TABLE)
with get_connection() as cursor:
try:
cursor.executemany(
f"DELETE FROM {id_table} WHERE snapshot_id={SQLITE_BIND}",
[snapshot_ids])
except Exception as e:
_handle_query_exception(e)
+1
View File
@@ -0,0 +1 @@
templates/static
+32 -12
View File
@@ -14,11 +14,11 @@ from crontab import CronTab
from .vendor.atomicwrites import atomic_write as lib_atomic_write
from .util import enforce_types, ExtendedEncoder
from .config import OUTPUT_PERMISSIONS
from .config import PYTHON_BINARY, OUTPUT_PERMISSIONS, DIR_OUTPUT_PERMISSIONS, ENFORCE_ATOMIC_WRITES
def run(*args, input=None, capture_output=True, timeout=None, check=False, text=False, start_new_session=True, **kwargs):
def run(cmd, *args, input=None, capture_output=True, timeout=None, check=False, text=False, start_new_session=True, **kwargs):
"""Patched of subprocess.run to kill forked child subprocesses and fix blocking io making timeout=innefective
Mostly copied from https://github.com/python/cpython/blob/master/Lib/subprocess.py
"""
@@ -37,7 +37,10 @@ def run(*args, input=None, capture_output=True, timeout=None, check=False, text=
pgid = None
try:
with Popen(*args, start_new_session=start_new_session, **kwargs) as process:
if isinstance(cmd, (list, tuple)) and cmd[0].endswith('.py'):
cmd = (PYTHON_BINARY, *cmd)
with Popen(cmd, *args, start_new_session=start_new_session, **kwargs) as process:
pgid = os.getpgid(process.pid)
try:
stdout, stderr = process.communicate(input, timeout=timeout)
@@ -89,14 +92,24 @@ def atomic_write(path: Union[Path, str], contents: Union[dict, str, bytes], over
elif isinstance(contents, (bytes, str)):
f.write(contents)
except OSError as e:
print(f"[X] OSError: Failed to write {path} with fcntl.F_FULLFSYNC. ({e})")
print(" You can store the archive/ subfolder on a hard drive or network share that doesn't support support syncronous writes,")
print(" but the main folder containing the index.sqlite3 and ArchiveBox.conf files must be on a filesystem that supports FSYNC.")
raise SystemExit(1)
if ENFORCE_ATOMIC_WRITES:
print(f"[X] OSError: Failed to write {path} with fcntl.F_FULLFSYNC. ({e})")
print(" You can store the archive/ subfolder on a hard drive or network share that doesn't support support syncronous writes,")
print(" but the main folder containing the index.sqlite3 and ArchiveBox.conf files must be on a filesystem that supports FSYNC.")
raise SystemExit(1)
# retry the write without forcing FSYNC (aka atomic mode)
with open(path, mode=mode, encoding=encoding) as f:
if isinstance(contents, dict):
dump(contents, f, indent=4, sort_keys=True, cls=ExtendedEncoder)
elif isinstance(contents, (bytes, str)):
f.write(contents)
# set file permissions
os.chmod(path, int(OUTPUT_PERMISSIONS, base=8))
@enforce_types
def chmod_file(path: str, cwd: str='.', permissions: str=OUTPUT_PERMISSIONS) -> None:
def chmod_file(path: str, cwd: str='.') -> None:
"""chmod -R <permissions> <cwd>/<path>"""
root = Path(cwd) / path
@@ -104,10 +117,15 @@ def chmod_file(path: str, cwd: str='.', permissions: str=OUTPUT_PERMISSIONS) ->
raise Exception('Failed to chmod: {} does not exist (did the previous step fail?)'.format(path))
if not root.is_dir():
# path is just a plain file
os.chmod(root, int(OUTPUT_PERMISSIONS, base=8))
else:
for subpath in Path(path).glob('**/*'):
os.chmod(subpath, int(OUTPUT_PERMISSIONS, base=8))
if subpath.is_dir():
# directories need execute permissions to be able to list contents
os.chmod(subpath, int(DIR_OUTPUT_PERMISSIONS, base=8))
else:
os.chmod(subpath, int(OUTPUT_PERMISSIONS, base=8))
@enforce_types
@@ -167,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.
+58 -2
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;
@@ -197,7 +253,7 @@
// select the action button from the dropdown
container.find('select[name=action]')
.find('op:selected').removeAttr('selected').end()
.find('[selected]').removeAttr('selected').end()
.find('[value=' + action_type + ']').attr('selected', 'selected').click()
// click submit & replace the archivebox logo with a spinner
@@ -1,62 +1,3 @@
{% extends "base.html" %}
{% load static %}
{% block body %}
<div id="toolbar">
<form id="changelist-search" action="{% url 'public-index' %}" method="get">
<div>
<label for="searchbar"><img src="/static/admin/img/search.svg" alt="Search"></label>
<input type="text" size="40" name="q" value="" id="searchbar" autofocus placeholder="Title, URL, tags, timestamp, or content...".>
<input type="submit" value="Search" style="height: 36px; padding-top: 6px; margin: 8px"/>
<input type="button"
value="♺"
title="Refresh..."
onclick="location.href='{% url 'public-index' %}'"
style="background-color: rgba(121, 174, 200, 0.8); height: 30px; font-size: 0.8em; margin-top: 12px; padding-top: 6px; float:right">
</input>
</div>
</form>
</div>
<table id="table-bookmarks">
<thead>
<tr>
<th style="width: 100px;">Bookmarked</th>
<th style="width: 26vw;">Snapshot ({{object_list|length}})</th>
<th style="width: 140px">Files</th>
<th style="width: 16vw;whitespace:nowrap;overflow-x:hidden;">Original URL</th>
</tr>
</thead>
<tbody>
{% for link in object_list %}
{% include 'main_index_row.html' with link=link %}
{% endfor %}
</tbody>
</table>
<center>
<span class="step-links">
{% if page_obj.has_previous %}
<a href="{% url 'public-index' %}?page=1">&laquo; first</a>
<a href="{% url 'public-index' %}?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{% url 'public-index' %}?page={{ page_obj.next_page_number }}">next </a>
<a href="{% url 'public-index' %}?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
{% if page_obj.has_next %}
<a href="{% url 'public-index' %}?page={{ page_obj.next_page_number }}">next </a>
<a href="{% url 'public-index' %}?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
<br>
</center>
{% endblock %}
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_list %}
{% load core_tags %}
@@ -150,7 +150,6 @@
<a href="{% url 'admin:core_snapshot_change' obj.id %}">
<span class="timestamp">{{obj.added}}</span>
</a>
&nbsp; &nbsp;
<label>
<span class="num_outputs">📄 &nbsp; {{obj.num_outputs}}</span> &nbsp; &nbsp;
<span>🗄&nbsp; {{ obj.archive_size | file_size }}</span>
+17 -7
View File
@@ -28,6 +28,14 @@
<a href="/add" id="submit">&nbsp; Add more URLs ➕</a>
</center>
{% else %}
<div id="in-progress" style="display: none;">
<center><h3>Adding URLs to index and running archive methods...</h3>
<br/>
<div class="loader"></div>
<br/>
Check the server log or the <a href="/admin/core/archiveresult/?o=-1">Log</a> page for detailed progress...
</center>
</div>
<form id="add-form" method="POST" class="p-form">{% csrf_token %}
<h1>Add new URLs to your archive</h1>
<br/>
@@ -38,20 +46,22 @@
</form>
<br/><br/><br/>
<center id="delay-warning" style="display: none">
<small>(it's safe to leave this page, adding will continue in the background)</small>
<small>(you will be redirected to your <a href="/">Snapshot list</a> momentarily, its safe to close this page at any time)</small>
</center>
{% if absolute_add_path %}
<center id="bookmarklet">
<!-- <center id="bookmarklet">
<p>Bookmark this link to quickly add to your archive:
<a href="javascript:void(window.open('{{ absolute_add_path }}?url='+document.location.href));">Add to ArchiveBox</a></p>
</center>
<a href="javascript:void(window.open('{{ absolute_add_path }}?url='+encodeURIComponent(document.location.href)));">Add to ArchiveBox</a></p>
</center> -->
{% endif %}
<script>
document.getElementById('add-form').addEventListener('submit', function(event) {
document.getElementById('in-progress').style.display = 'block'
document.getElementById('add-form').style.display = 'none'
document.getElementById('delay-warning').style.display = 'block'
setTimeout(function() {
document.getElementById('add-form').innerHTML = '<center><h3>Adding URLs to index and running archive methods...<h3><br/><div class="loader"></div><br/>Check the server log or the <a href="/admin/core/archiveresult/?o=-1">Log</a> page for progress...</center>'
document.getElementById('delay-warning').style.display = 'block'
}, 200)
window.location = '/'
}, 2000)
return true
})
</script>
+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>
+1 -1
View File
@@ -36,7 +36,7 @@
{% endif %}
</span>
</td>
<td style="text-align:left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; title="{{link.url}}">
<td style="text-align:left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="{{link.url}}">
<a href="{{link.url}}">
{{link.url}}
</a>
+7 -3
View File
@@ -414,17 +414,21 @@
</div>
</div>
{% endif %}
{% if PREVIEW_ORIGINALS %}
<div class="col-lg-2">
<div class="card">
<iframe class="card-img-top" src="{{url}}" sandbox="allow-same-origin allow-top-navigation-by-user-activation allow-scripts allow-forms" scrolling="no" loading="lazy"></iframe>
<iframe class="card-img-top" src="{{url}}" sandbox="allow-same-origin allow-top-navigation-by-user-activation allow-scripts allow-forms" scrolling="no" loading="lazy" referrerpolicy="no-referrer"></iframe>
<div class="card-body">
<a href="{{url}}" title="Open in new tab..." target="_blank" rel="noopener">
<a href="{{url}}" title="Open in new tab..." target="_blank" rel="noopener" referrerpolicy="no-referrer">
<p class="card-text"><code>🌐 {{domain}}</code></p>
</a>
<a href="{{url}}" target="preview" id="original-btn"><h4 class="card-title">Original</h4></a>
<a href="{{url}}" target="preview" id="original-btn" referrerpolicy="no-referrer">
<h4 class="card-title">Original</h4>
</a>
</div>
</div>
</div>
{% endif %}
<div class="col-lg-2">
<div class="card">
<iframe class="card-img-top" src="{{headers_path}}" sandbox="allow-same-origin allow-top-navigation-by-user-activation allow-scripts allow-forms" scrolling="no" loading="lazy"></iframe>
+35 -13
View File
@@ -17,6 +17,8 @@ from requests.exceptions import RequestException, ReadTimeout
from .vendor.base32_crockford import encode as base32_encode # type: ignore
from w3lib.encoding import html_body_declared_encoding, http_content_type_encoding
from os.path import lexists
from os import remove as remove_file
try:
import chardet
@@ -59,7 +61,7 @@ URL_REGEX = re.compile(
r'(?=('
r'http[s]?://' # start matching from allowed schemes
r'(?:[a-zA-Z]|[0-9]' # followed by allowed alphanum characters
r'|[$-_@.&+]|[!*\(\),]' # or allowed symbols
r'|[-_$@.&+!*\(\),]' # or allowed symbols (keep hyphen first to match literal hyphen)
r'|(?:%[0-9a-fA-F][0-9a-fA-F]))' # or allowed unicode bytes
r'[^\]\[\(\)<>"\'\s]+' # stop parsing at these symbols
r'))',
@@ -219,7 +221,7 @@ def get_headers(url: str, timeout: int=None) -> str:
def chrome_args(**options) -> List[str]:
"""helper to build up a chrome shell command with arguments"""
from .config import CHROME_OPTIONS
from .config import CHROME_OPTIONS, CHROME_VERSION
options = {**CHROME_OPTIONS, **options}
@@ -229,19 +231,29 @@ def chrome_args(**options) -> List[str]:
cmd_args = [options['CHROME_BINARY']]
if options['CHROME_HEADLESS']:
cmd_args += ('--headless',)
chrome_major_version = int(re.search(r'\s(\d+)\.\d', CHROME_VERSION)[1])
if chrome_major_version >= 111:
cmd_args += ("--headless=new",)
else:
cmd_args += ('--headless',)
if not options['CHROME_SANDBOX']:
# assume this means we are running inside a docker container
# in docker, GPU support is limited, sandboxing is unecessary,
# in docker, GPU support is limited, sandboxing is unecessary,
# and SHM is limited to 64MB by default (which is too low to be usable).
cmd_args += (
'--no-sandbox',
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-software-rasterizer',
'--run-all-compositor-stages-before-draw',
'--hide-scrollbars',
"--no-sandbox",
"--no-zygote",
"--disable-dev-shm-usage",
"--disable-software-rasterizer",
"--run-all-compositor-stages-before-draw",
"--hide-scrollbars",
"--window-size=1440,2000",
"--autoplay-policy=no-user-gesture-required",
"--no-first-run",
"--use-fake-ui-for-media-stream",
"--use-fake-device-for-media-stream",
"--disable-sync",
)
@@ -254,14 +266,24 @@ def chrome_args(**options) -> List[str]:
if options['RESOLUTION']:
cmd_args += ('--window-size={}'.format(options['RESOLUTION']),)
if options['TIMEOUT']:
cmd_args += ('--timeout={}'.format(options['TIMEOUT'] * 1000),)
if options['CHROME_TIMEOUT']:
cmd_args += ('--timeout={}'.format(options['CHROME_TIMEOUT'] * 1000),)
if options['CHROME_USER_DATA_DIR']:
cmd_args.append('--user-data-dir={}'.format(options['CHROME_USER_DATA_DIR']))
return cmd_args
def chrome_cleanup():
"""
Cleans up any state or runtime files that chrome leaves behind when killed by
a timeout or other error
"""
from .config import IN_DOCKER
if IN_DOCKER and lexists("/home/archivebox/.config/chromium/SingletonLock"):
remove_file("/home/archivebox/.config/chromium/SingletonLock")
def ansi_to_html(text):
"""
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../../package-lock.json
+1
View File
@@ -0,0 +1 @@
../../package.json
+6 -4
View File
@@ -1,11 +1,13 @@
#!/bin/bash
#!/usr/bin/env bash
# DEPRECATED: this helper exists for backwards compatibility with <v0.4 only
# Do not use this to call archivebox, instead use the archivebox binary directly.
if python3 -m django --version >/dev/null 2>&1; then
python3 -m archivebox "$*"
else
echo '[X] ArchiveBox must be installed before using:'
echo " pip install archivebox"
echo '[X] ArchiveBox not found, is it installed and present in your $PATH?'
echo ' pip3 install archivebox'
echo
echo "Hint: Did you forget to activate a virtuenv or set your $$PATH?"
echo 'Hint: Did you forget to activate a virtualenv?'
exit 2
fi
+19 -2
View File
@@ -23,7 +23,24 @@ fi
cd "$REPO_DIR/brew_dist"
# make sure archivebox.rb is up-to-date with the dependencies
git pull
git status | grep 'up to date'
echo "[+] Building Homebrew bottle"
brew install --build-bottle ./archivebox.rb
echo
echo "[+] Uninstalling any exisitng archivebox versions..."
brew uninstall archivebox || true
brew untap archivebox/archivebox || true
# echo "[*] Running Formula linters and test build..."
# brew test-bot --tap=ArchiveBox/homebrew-archivebox archivebox/archivebox/archivebox || true
# brew uninstall archivebox || true
# brew untap archivebox/archivebox || true
echo
echo "[+] Installing and building hombrew bottle from https://Github.com/ArchiveBox/homebrew-archivebox#main"
brew tap archivebox/archivebox
brew install --build-bottle archivebox
brew bottle archivebox
echo
echo "[√] Finished. Make sure to commit the outputted .tar.gz and bottle files!"
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# ./bin/build_docker.sh dev 'linux/arm/v7'
### 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 errexit
set -o errtrace
set -o nounset
set -o pipefail
IFS=$'\n'
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
cd "$REPO_DIR"
which docker > /dev/null || exit 1
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')"
REQUIRED_PLATFORMS="${2:-"linux/arm64,linux/amd64,linux/arm/v7"}"
echo "[+] Building Docker image: tag=$TAG_NAME version=$SHORT_VERSION arch=$REQUIRED_PLATFORMS"
echo "[+] Building archivebox:$VERSION docker image..."
# docker builder prune
docker build . --no-cache -t archivebox-dev --load
# docker buildx build --platform "$REQUIRED_PLATFORMS" --load . \
# -t archivebox \
# -t archivebox:$TAG_NAME \
# -t archivebox:$VERSION \
# -t archivebox:$SHORT_VERSION
+87 -16
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
# ./bin/build_docker.sh dev 'linux/arm/v7'
### Bash Environment Setup
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
@@ -11,23 +12,93 @@ set -o pipefail
IFS=$'\n'
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
VERSION="$(jq -r '.version' < "$REPO_DIR/package.json")"
SHORT_VERSION="$(echo "$VERSION" | perl -pe 's/(\d+)\.(\d+)\.(\d+)/$1.$2/g')"
cd "$REPO_DIR"
which docker > /dev/null
which docker > /dev/null || exit 1
which jq > /dev/null || exit 1
# which pdm > /dev/null || exit 1
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"
function check_platforms() {
INSTALLED_PLATFORMS="$(docker buildx inspect | grep 'Platforms:' )"
for REQUIRED_PLATFORM in ${SELECTED_PLATFORMS//,/$IFS}; do
echo "[+] Checking for: $REQUIRED_PLATFORM..."
if ! (echo "$INSTALLED_PLATFORMS" | grep -q "$REQUIRED_PLATFORM"); then
return 1
fi
done
echo
return 0
}
function remove_builder() {
# remove existing xbuilder
docker buildx stop xbuilder || true
docker buildx rm xbuilder || true
}
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
docker buildx inspect --bootstrap || true
}
function recreate_builder() {
# Install QEMU binaries for cross-platform building if not installed
docker run --privileged --rm 'tonistiigi/binfmt' --install all
remove_builder
create_builder
}
# Check if docker is ready for cross-plaform builds, if not, recreate builder
docker buildx use xbuilder 2>&1 >/dev/null || create_builder
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="inherit_metadata" --production
pdm export --group=':all' --production --without-hashes -o requirements.txt
echo "[+] Building archivebox:$VERSION docker image..."
docker build . -t archivebox \
-t archivebox:latest \
-t archivebox:$VERSION \
-t archivebox:$SHORT_VERSION \
-t docker.io/nikisweeting/archivebox:latest \
-t docker.io/nikisweeting/archivebox:$VERSION \
-t docker.io/nikisweeting/archivebox:$SHORT_VERSION \
-t docker.io/archivebox/archivebox:latest \
-t docker.io/archivebox/archivebox:$VERSION \
-t docker.io/archivebox/archivebox:$SHORT_VERSION \
-t docker.pkg.github.com/archivebox/archivebox/archivebox:latest \
-t docker.pkg.github.com/archivebox/archivebox/archivebox:$VERSION \
-t docker.pkg.github.com/archivebox/archivebox/archivebox:$SHORT_VERSION
# docker builder prune
# docker build . --no-cache -t archivebox-dev \
# replace --load with --push to deploy
docker buildx build --platform "$SELECTED_PLATFORMS" --load . \
-t archivebox/archivebox \
-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:$GIT_SHA \
-t ghcr.io/archivebox/archivebox/archivebox:latest \
-t ghcr.io/archivebox/archivebox/archivebox:stable
+14 -6
View File
@@ -15,17 +15,25 @@ REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && p
if [[ -f "$REPO_DIR/.venv/bin/activate" ]]; then
source "$REPO_DIR/.venv/bin/activate"
else
echo "[!] Warning: No virtualenv presesnt in $REPO_DIR.venv"
echo "[!] Warning: No virtualenv presesnt in $REPO_DIR/.venv, creating one now..."
python3 -m venv --system-site-packages --symlinks $REPO_DIR/.venv
fi
cd "$REPO_DIR"
echo "[*] Cleaning up build dirs"
cd "$REPO_DIR"
rm -Rf build dist
echo "[+] Building sdist, bdist_wheel, and egg_info"
python3 setup.py \
sdist --dist-dir=./pip_dist \
bdist_wheel --dist-dir=./pip_dist \
egg_info --egg-base=./pip_dist
rm -f archivebox/package.json
cp package.json archivebox/package.json
pdm self update
pdm install
pdm build
pdm export --without-hashes -o ./pip_dist/requirements.txt
cp dist/* ./pip_dist/
echo
echo "[√] Finished. Don't forget to commit the new sdist and wheel files in ./pip_dist/"
+140 -28
View File
@@ -1,45 +1,157 @@
#!/usr/bin/env bash
#!/bin/bash
DATA_DIR="${DATA_DIR:-/data}"
ARCHIVEBOX_USER="${ARCHIVEBOX_USER:-archivebox}"
# 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.
# Set the archivebox user UID & GID
if [[ -n "$PUID" && "$PUID" != 0 ]]; then
usermod -u "$PUID" "$ARCHIVEBOX_USER" > /dev/null 2>&1
fi
if [[ -n "$PGID" && "$PGID" != 0 ]]; then
groupmod -g "$PGID" "$ARCHIVEBOX_USER" > /dev/null 2>&1
# 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}"
# 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 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
# 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")"
# Set the permissions of the data dir to match the archivebox user
# 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
export PUID="$(id -u archivebox)"
export PGID="$(id -g archivebox)"
# Check the permissions of the data dir (or create if it doesn't exist)
if [[ -d "$DATA_DIR/archive" ]]; then
# check data directory permissions
if [[ ! "$(stat -c %u $DATA_DIR/archive)" = "$(id -u archivebox)" ]]; then
echo "Change in ownership detected, please be patient while we chown existing files"
echo "This could take some time..."
chown $ARCHIVEBOX_USER:$ARCHIVEBOX_USER -R "$DATA_DIR"
if touch "$DATA_DIR/archive/.permissions_test_safe_to_delete" 2>/dev/null; then
# It's fine, we are able to write to the data directory (as root inside the container)
rm -f "$DATA_DIR/archive/.permissions_test_safe_to_delete"
# 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 (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
mkdir -p "$DATA_DIR/logs"
chown -R $ARCHIVEBOX_USER:$ARCHIVEBOX_USER "$DATA_DIR"
fi
chown $ARCHIVEBOX_USER:$ARCHIVEBOX_USER "$DATA_DIR"
# force set the ownership of the data dir contents to the archivebox user and group
# this is needed because Docker Desktop often does not map user permissions from the host properly
chown $PUID:$PGID "$DATA_DIR"
chown $PUID:$PGID "$DATA_DIR"/*
# 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"/*
rm -Rf "$PLAYWRIGHT_BROWSERS_PATH/permissions_test_safe_to_delete"
# (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" == "echo" || "$1" == "archivebox" ]]; then
# arg 1 is a binary, execute it verbatim
# e.g. "archivebox init"
# "/bin/bash"
# "echo"
exec gosu "$ARCHIVEBOX_USER" bash -c "$*"
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 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
# no command given, assume args were meant to be passed to archivebox cmd
# e.g. "add https://example.com"
# "manage createsupseruser"
# "server 0.0.0.0:8000"
exec gosu "$ARCHIVEBOX_USER" bash -c "archivebox $*"
# 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 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" "$ARCHIVEBOX_BIN_PATH" "$@"
fi
+37 -9
View File
@@ -1,4 +1,15 @@
#!/bin/bash
#!/usr/bin/env bash
# Helper script to export browser history and bookmarks to a format ArchiveBox can ingest.
# Usage:
# curl -O 'https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/bin/export_browser_history.sh'
# bash export_browser_history.sh --chrome
# bash export_browser_history.sh --firefox
# bash export_browser_history.sh --safari
# ls
# chrome_history.json
# firefox_history.json
# firefox_bookmarks.json
# safari_history.json
OUTPUT_DIR="$(pwd)"
@@ -16,9 +27,9 @@ if [[ "$1" == "--chrome" ]]; then
sqlite3 "$OUTPUT_DIR/chrome_history.db.tmp" "SELECT \"[\" || group_concat(json_object('timestamp', last_visit_time, 'description', title, 'href', url)) || \"]\" FROM urls;" > "$OUTPUT_DIR/chrome_history.json"
jq < "$(dirname "${2:-$default}")"/Bookmarks '.roots.other.children[] | {href: .url, description: .name, timestamp: .date_added}' > "$OUTPUT_DIR/chrome_bookmarks.json"
rm "$DATA_DIR"/output/sources/chrome_history.db.*
rm "$OUTPUT_DIR"/chrome_history.db.*
echo "Chrome history exported to:"
echo " output/sources/chrome_history.json"
echo " $OUTPUT_DIR/chrome_history.json"
fi
if [[ "$1" == "--firefox" ]]; then
@@ -33,12 +44,29 @@ if [[ "$1" == "--firefox" ]]; then
fi
sqlite3 "$OUTPUT_DIR/firefox_history.db.tmp" "SELECT \"[\" || group_concat(json_object('timestamp', last_visit_date, 'description', title, 'href', url)) || \"]\" FROM moz_places;" > "$OUTPUT_DIR/firefox_history.json"
sqlite3 "$OUTPUT_DIR/firefox_history.db.tmp" "SELECT \"[\" || group_concat(json_object('timestamp', b.dateAdded, 'description', b.title, 'href', f.url)) || \"]\" FROM moz_bookmarks AS b JOIN moz_places AS f ON f.id = b.fk" > "$OUTPUT_DIR/firefox_bookmarks.json"
sqlite3 "$OUTPUT_DIR/firefox_history.db.tmp" "
with recursive tags AS (
select id, title, '' AS tags
FROM moz_bookmarks
where parent == 0
UNION ALL
select c.id, p.title, c.title || ',' || tags AS tags
from moz_bookmarks AS c
JOIN tags AS p
ON c.parent = p.id
)
SELECT '[' || group_concat(json_object('timestamp', b.dateAdded, 'description', b.title, 'href', f.url, 'tags', tags.tags)) || ']'
FROM moz_bookmarks AS b
JOIN moz_places AS f ON f.id = b.fk
JOIN tags ON tags.id = b.parent
WHERE f.url LIKE '%://%';" > "$OUTPUT_DIR/firefox_bookmarks.json"
rm "$DATA_DIR"/output/sources/firefox_history.db.*
rm "$OUTPUT_DIR"/firefox_history.db.*
echo "Firefox history exported to:"
echo " output/sources/firefox_history.json"
echo " output/sources/firefox_bookmarks.json"
echo " $OUTPUT_DIR/firefox_history.json"
echo " $OUTPUT_DIR/firefox_bookmarks.json"
fi
if [[ "$1" == "--safari" ]]; then
@@ -54,7 +82,7 @@ if [[ "$1" == "--safari" ]]; then
sqlite3 "$OUTPUT_DIR/safari_history.db.tmp" "select url from history_items" > "$OUTPUT_DIR/safari_history.json"
rm "$DATA_DIR"/output/sources/safari_history.db.*
rm "$OUTPUT_DIR"/safari_history.db.*
echo "Safari history exported to:"
echo " output/sources/safari_history.json"
echo " $OUTPUT_DIR/safari_history.json"
fi
+2 -1
View File
@@ -15,7 +15,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
source "$DIR/.venv/bin/activate"
echo "[*] Running flake8..."
flake8 archivebox && echo "√ No errors found."
cd archivebox
flake8 . && echo "√ No errors found."
echo
+15 -4
View File
@@ -11,9 +11,20 @@ set -o pipefail
IFS=$'\n'
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
VERSION="$(jq -r '.version' < "$REPO_DIR/package.json")"
SHORT_VERSION="$(echo "$VERSION" | perl -pe 's/(\d+)\.(\d+)\.(\d+)/$1.$2/g')"
CURRENT_PLAFORM="$(uname)"
REQUIRED_PLATFORM="Darwin"
if [[ "$CURRENT_PLAFORM" != "$REQUIRED_PLATFORM" ]]; then
echo "[!] Skipping the Homebrew package build on $CURRENT_PLAFORM (it can only be run on $REQUIRED_PLATFORM)."
exit 0
fi
cd "$REPO_DIR"
# TODO
exit 0
./bin/build_brew.sh
git add '*.bottle.tar.gz'
git commit -m "add new release bottle"
git pull
git push
+37 -8
View File
@@ -11,15 +11,44 @@ set -o pipefail
IFS=$'\n'
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
VERSION="$(jq -r '.version' < "$REPO_DIR/package.json")"
SHORT_VERSION="$(echo "$VERSION" | perl -pe 's/(\d+)\.(\d+)\.(\d+)/$1.$2/g')"
cd "$REPO_DIR"
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 "[*] Logging in to Docker Hub & Github Container Registry"
# docker login --username=nikisweeting
# docker login ghcr.io --username=pirate
# echo "[^] Building docker image"
# ./bin/build_docker.sh "$TAG_NAME" "$SELECTED_PLATFORMS"
echo "[^] Uploading docker image"
# docker login --username=nikisweeting
# docker login docker.pkg.github.com --username=pirate
docker push archivebox/archivebox:$VERSION archivebox/archivebox:$SHORT_VERSION archivebox/archivebox:latest
docker push docker.io/nikisweeting/archivebox
docker push docker.io/archivebox/archivebox
docker push docker.pkg.github.com/archivebox/archivebox/archivebox
docker buildx build --platform "$SELECTED_PLATFORMS" --push . \
-t archivebox/archivebox \
-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:$GIT_SHA \
-t ghcr.io/archivebox/archivebox/archivebox:latest \
-t ghcr.io/archivebox/archivebox/archivebox:stable
+4 -10
View File
@@ -11,17 +11,11 @@ set -o pipefail
IFS=$'\n'
REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
VERSION="$(jq -r '.version' < "$REPO_DIR/package.json")"
cd "$REPO_DIR"
source "$REPO_DIR/.venv/bin/activate"
echo "[^] Publishing to Test PyPI..."
pdm publish --repository testpypi
# apt install python3 python3-all python3-dev
# pip install '.[dev]'
echo "[^] Uploading to test.pypi.org"
python3 -m twine upload --repository testpypi pip_dist/archivebox-${VERSION}*.{whl,tar.gz}
echo "[^] Uploading to pypi.org"
python3 -m twine upload --repository pypi pip_dist/archivebox-${VERSION}*.{whl,tar.gz}
echo "[^] Publishing to PyPI..."
pdm publish --no-build
+177 -97
View File
@@ -1,120 +1,200 @@
#!/usr/bin/env bash
# ArchiveBox Setup Script
# https://github.com/ArchiveBox/ArchiveBox
#!/usr/bin/env sh
# ArchiveBox Setup Script: https://github.com/ArchiveBox/ArchiveBox
# Supported Platforms: Ubuntu/Debian/FreeBSD/macOS
# Usage:
# curl -sSL 'https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/bin/setup.sh' | sh
clear
if [ $(id -u) -eq 0 ]; then
echo ""
echo "[X] You cannot run this script as root. You must run it as a non-root user with sudo ability."
echo " Create a new non-privileged user 'archivebox' if necessary."
echo " adduser archivebox && usermod -a archivebox -G sudo && su archivebox"
echo " https://www.digitalocean.com/community/tutorials/how-to-create-a-new-sudo-enabled-user-on-ubuntu-20-04-quickstart"
echo " https://www.vultr.com/docs/create-a-sudo-user-on-freebsd"
echo " Then re-run this script as the non-root user."
echo ""
exit 2
fi
if (which docker-compose > /dev/null && docker pull archivebox/archivebox:latest); then
echo "[+] Initializing an ArchiveBox data folder at ~/archivebox/data using Docker Compose..."
mkdir -p ~/archivebox
cd ~/archivebox
mkdir -p data
if [ -f "./index.sqlite3" ]; then
mv ~/archivebox/* ~/archivebox/data/
fi
curl -O 'https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/master/docker-compose.yml'
docker-compose run --rm archivebox init --setup
echo
echo "[+] Starting ArchiveBox server using: docker-compose up -d..."
docker-compose up -d
sleep 7
open http://127.0.0.1:8000 || true
echo
echo "[√] Server started on http://0.0.0.0:8000 and data directory initialized in ~/archivebox/data. Usage:"
echo " cd ~/archivebox"
echo " docker-compose ps"
echo " docker-compose down"
echo " docker-compose pull"
echo " docker-compose up"
echo " docker-compose run archivebox manage createsuperuser"
echo " docker-compose run archivebox add 'https://example.com'"
echo " docker-compose run archivebox list"
echo " docker-compose run archivebox help"
exit 0
elif (which docker > /dev/null && docker pull archivebox/archivebox:latest); then
echo "[+] Initializing an ArchiveBox data folder at ~/archivebox using Docker..."
mkdir -p ~/archivebox
cd ~/archivebox
if [ -f "./data/index.sqlite3" ]; then
cd ./data
fi
docker run -v "$PWD":/data -it --rm archivebox/archivebox:latest init --setup
echo
echo "[+] Starting ArchiveBox server using: docker run -d archivebox/archivebox..."
docker run -v "$PWD":/data -it -d -p 8000:8000 --name=archivebox archivebox/archivebox:latest
sleep 7
open http://127.0.0.1:8000 || true
echo
echo "[√] Server started on http://0.0.0.0:8000 and data directory initialized in ~/archivebox. Usage:"
echo " cd ~/archivebox"
echo " docker ps --filter name=archivebox"
echo " docker kill archivebox"
echo " docker pull archivebox/archivebox"
echo " docker run -v $PWD:/data -d -p 8000:8000 --name=archivebox archivebox/archivebox"
echo " docker run -v $PWD:/data -it archivebox/archivebox manage createsuperuser"
echo " docker run -v $PWD:/data -it archivebox/archivebox add 'https://example.com'"
echo " docker run -v $PWD:/data -it archivebox/archivebox list"
echo " docker run -v $PWD:/data -it archivebox/archivebox help"
exit 0
fi
echo ""
echo "[!] It's highly recommended to use ArchiveBox with Docker, but Docker wasn't found."
echo ""
echo " ⚠️ If you want to use Docker, press [Ctrl-C] to cancel now. ⚠️"
echo " Get Docker: https://docs.docker.com/get-docker/"
echo " After you've installed Docker, run this script again."
echo ""
echo "Otherwise, install will continue with apt/brew/pip in 12s... (press [Ctrl+C] to cancel)"
echo ""
sleep 12 || exit 1
echo "Proceeding with system package manager..."
echo ""
echo "[i] ArchiveBox Setup Script 📦"
echo ""
echo " This is a helper script which installs the ArchiveBox dependencies on your system using homebrew/aptitude."
echo " You may be prompted for a password in order to install the following:"
echo " This is a helper script which installs the ArchiveBox dependencies on your system using brew/apt/pip3."
echo " You may be prompted for a sudo password in order to install the following:"
echo ""
echo " - python3, python3-pip, python3-distutils"
echo " - curl"
echo " - wget"
echo " - git"
echo " - youtube-dl"
echo " - chromium-browser (skip this if Chrome/Chromium is already installed)"
echo " - nodejs (used for singlefile, readability, mercury, and more)"
echo " - archivebox"
echo " - python3, pip, nodejs, npm (languages used by ArchiveBox, and its extractor modules)"
echo " - curl, wget, git, youtube-dl, yt-dlp (used for extracting title, favicon, git, media, and more)"
echo " - chromium (skips this if any Chrome/Chromium version is already installed)"
echo ""
echo " If you'd rather install these manually, you can find documentation here:"
echo " If you'd rather install these manually as-needed, you can find detailed documentation here:"
echo " https://github.com/ArchiveBox/ArchiveBox/wiki/Install"
echo ""
read -p "Press [enter] to continue with the automatic install, or Ctrl+C to cancel..." REPLY
echo "Continuing in 12s... (press [Ctrl+C] to cancel)"
echo ""
sleep 12 || exit 1
echo "Proceeding to install dependencies..."
echo ""
# On Linux:
if which apt-get > /dev/null; then
echo "[+] Adding ArchiveBox apt repo to sources..."
sudo apt install software-properties-common
sudo add-apt-repository -u ppa:archivebox/archivebox
echo "[+] Installing python3, wget, curl..."
sudo apt install -y git python3 python3-pip python3-distutils wget curl youtube-dl nodejs npm ripgrep
# sudo apt install archivebox
if which google-chrome; then
echo "[i] You already have google-chrome installed, if you would like to download chromium instead (they work pretty much the same), follow the Manual Setup instructions"
google-chrome --version
elif which chromium-browser; then
echo "[i] chromium-browser already installed, using existing installation."
chromium-browser --version
elif which chromium; then
echo "[i] chromium already installed, using existing installation."
chromium --version
else
echo "[+] Installing chromium..."
sudo apt install chromium || sudo apt install chromium-browser
echo "[+] Adding ArchiveBox apt repo and signing key to sources..."
if ! (sudo apt install -y software-properties-common && sudo add-apt-repository -u ppa:archivebox/archivebox); then
echo "deb http://ppa.launchpad.net/archivebox/archivebox/ubuntu focal main" | sudo tee /etc/apt/sources.list.d/archivebox.list
echo "deb-src http://ppa.launchpad.net/archivebox/archivebox/ubuntu focal main" | sudo tee -a /etc/apt/sources.list.d/archivebox.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C258F79DCC02E369
sudo apt-get update -qq
fi
echo
echo "[+] Installing ArchiveBox system dependencies using apt..."
sudo apt-get install -y git python3 python3-pip python3-distutils wget curl youtube-dl yt-dlp ffmpeg git nodejs npm ripgrep
sudo apt-get install -y libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb libgbm-dev || sudo apt-get install -y chromium || sudo apt-get install -y chromium-browser || true
sudo apt-get install -y archivebox
sudo apt-get --only-upgrade install -y archivebox
echo ""
echo "[+] Installing ArchiveBox python dependencies using pip3..."
sudo python3 -m pip install --upgrade --ignore-installed archivebox
# On Mac:
elif which brew > /dev/null; then # 🐍 eye of newt
echo "[+] Installing python3, wget, curl (ignore 'already installed' warnings)..."
brew install git wget curl youtube-dl ripgrep node
if which python3; then
if python3 -c 'import sys; raise SystemExit(sys.version_info < (3,5,0))'; then
echo "[√] Using existing $(which python3)..."
else
echo "[+] Installing python3..."
brew install python3
fi
else
echo "[+] Installing python3..."
brew install python3
fi
if ls /Applications/Google\ Chrome*.app > /dev/null; then
echo "[√] Using existing /Applications/Google Chrome.app"
elif ls /Applications/Chromium.app; then
echo "[√] Using existing /Applications/Chromium.app"
elif which chromium-browser; then
echo "[√] Using existing $(which chromium-browser)"
elif which chromium; then
echo "[√] Using existing $(which chromium)"
else
echo "[+] Installing chromium..."
brew cask install chromium
fi
elif which brew > /dev/null; then
echo "[+] Installing ArchiveBox system dependencies using brew..."
brew tap archivebox/archivebox
brew update
brew install --fetch-HEAD -f archivebox
echo ""
echo "[+] Installing ArchiveBox python dependencies using pip3..."
python3 -m pip install --upgrade --ignore-installed archivebox
elif which pkg > /dev/null; then
echo "[+] Installing ArchiveBox system dependencies using pkg and pip (python3.9)..."
sudo pkg install -y python3 py39-pip py39-sqlite3 npm wget curl youtube_dl ffmpeg git ripgrep
sudo pkg install -y chromium
echo ""
echo "[+] Installing ArchiveBox python dependencies using pip..."
# don't use sudo here so that pip installs in $HOME/.local instead of into /usr/local
python3 -m pip install --upgrade --ignore-installed archivebox
else
echo "[X] Could not find aptitude or homebrew! ‼️"
echo "[!] Warning: Could not find aptitude/homebrew/pkg! May not be able to install all dependencies automatically."
echo ""
echo " If you're on macOS, make sure you have homebrew installed: https://brew.sh/"
echo " If you're on Ubuntu/Debian, make sure you have apt installed: https://help.ubuntu.com/lts/serverguide/apt.html"
echo " (those are the only currently supported systems for the automatic setup script)"
echo " If you're on Linux, only Ubuntu/Debian/BSD systems are officially supported with this script."
echo " If you're on Windows, this script is not officially supported (Docker is recommeded instead)."
echo ""
echo "See the README.md for Manual Setup & Troubleshooting instructions."
echo "See the README.md for Manual Setup & Troubleshooting instructions if you you're unable to run ArchiveBox after this script completes."
fi
echo ""
if ! (python3 --version && python3 -m pip --version && python3 -m django --version); then
echo "[X] Python 3 pip was not found on your system!"
echo " You must first install Python >= 3.7 (and pip3):"
echo " https://www.python.org/downloads/"
echo " https://wiki.python.org/moin/BeginnersGuide/Download"
echo " After installing, run this script again."
exit 1
fi
npm i -g npm
pip3 install --upgrade pip setuptools
if ! (python3 -m django --version && python3 -m archivebox version --quiet); then
echo "[X] Django and ArchiveBox were not found after installing!"
echo " Check to see if a previous step failed."
echo ""
exit 1
fi
pip3 install --upgrade archivebox
npm install -g 'git+https://github.com/ArchiveBox/ArchiveBox.git'
# echo ""
# echo "[+] Upgrading npm and pip..."
# sudo npm i -g npm || true
# sudo python3 -m pip install --upgrade pip setuptools || true
# Check:
echo ""
echo "[*] Checking installed versions:"
echo "---------------------------------------------------"
which python3 &&
python3 --version | head -n 1 &&
echo "" &&
which git &&
git --version | head -n 1 &&
echo "" &&
which wget &&
wget --version | head -n 1 &&
echo "" &&
which curl &&
curl --version | head -n 1 &&
echo "" &&
which youtube-dl &&
youtube-dl --version | head -n 1 &&
echo "---------------------------------------------------" &&
archivebox version &&
echo "[√] All dependencies installed. ✅" &&
exit 0
echo
echo "[+] Initializing ArchiveBox data folder at ~/archivebox..."
mkdir -p ~/archivebox
cd ~/archivebox
if [ -f "./data/index.sqlite3" ]; then
cd ./data
fi
: | python3 -m archivebox init --setup || true # pipe in empty command to make sure stdin is closed
echo "---------------------------------------------------"
echo "[X] Failed to install some dependencies! ‼️"
echo " - Try the Manual Setup instructions in the README.md"
echo " - Try the Troubleshooting: Dependencies instructions in the README.md"
echo " - Open an issue on github to get help: https://github.com/ArchiveBox/ArchiveBox/issues"
exit 1
echo
echo "[+] Starting ArchiveBox server using: nohup archivebox server &..."
nohup python3 -m archivebox server 0.0.0.0:8000 > ./logs/server.log 2>&1 &
sleep 7
which open > /dev/null && open http://127.0.0.1:8000 || true
echo
echo "[√] Server started on http://0.0.0.0:8000 and data directory initialized in ~/archivebox. Usage:"
echo " cd ~/archivebox"
echo " ps aux | grep archivebox"
echo " pkill -f archivebox"
echo " python3 -m pip install --upgrade archivebox"
echo " archivebox server --quick-init 0.0.0.0:8000"
echo " archivebox manage createsuperuser"
echo " archivebox add 'https://example.com'"
echo " archivebox list"
echo " archivebox help"
+1 -1
View File
@@ -14,4 +14,4 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && cd .. && pwd )"
source "$DIR/.venv/bin/activate"
pytest -s --basetemp=tests/out --ignore=archivebox/vendor
pytest -s --basetemp=tests/out --ignore=archivebox/vendor --ignore=deb_dist --ignore=pip_dist --ignore=brew_dist
Submodule deb_dist updated: 16776b076a...92f8fe8f34.
+1 -1
Submodule docker updated: 236f7881e3...1fc6dd7f0f.
+106 -38
View File
@@ -1,72 +1,128 @@
# Usage:
# docker-compose run archivebox init --setup
# docker-compose up
# echo "https://example.com" | docker-compose run archivebox archivebox add
# docker-compose run archivebox add --depth=1 https://example.com/some/feed.rss
# docker-compose run archivebox config --set PUBLIC_INDEX=True
# docker-compose run archivebox help
# docker compose run archivebox init --setup
# docker compose up
# echo "https://example.com" | docker compose run archivebox archivebox add
# docker compose run archivebox add --depth=1 https://example.com/some/feed.rss
# docker compose run archivebox config --set MEDIA_MAX_SIZE=750m
# docker compose run archivebox help
# Documentation:
# https://github.com/ArchiveBox/ArchiveBox/wiki/Docker#docker-compose
version: '2.4'
services:
archivebox:
# build: . # for developers working on archivebox
image: ${DOCKER_IMAGE:-archivebox/archivebox:latest}
#image: ${DOCKER_IMAGE:-archivebox/archivebox:dev}
image: archivebox/archivebox:stable
command: server --quick-init 0.0.0.0:8000
ports:
- 8000:8000
environment:
- ALLOWED_HOSTS=* # add any config options you want as env vars
- MEDIA_MAX_SIZE=750m
# - SEARCH_BACKEND_ENGINE=sonic # uncomment these if you enable sonic below
# - SEARCH_BACKEND_HOST_NAME=sonic
# - SEARCH_BACKEND_PASSWORD=SecretPassword
volumes:
- ./data:/data
# - ./archivebox:/app/archivebox # for developers working on archivebox
# - ./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
# - PUBLIC_SNAPSHOTS=True # set to False to prevent anonymous users from viewing snapshot content
# - PUBLIC_ADD_VIEW=False # set to True to allow anonymous users to submit new URLs to archive
# - ADMIN_USERNAME=admin # create an admin user on first run with the given user/pass combo
# - ADMIN_PASSWORD=SomeSecretPassword
# - PUID=911 # set to your host user's UID & GID if you encounter permissions issues
# - PGID=911
# - SEARCH_BACKEND_ENGINE=sonic # uncomment these and sonic container below for better full-text search
# - SEARCH_BACKEND_HOST_NAME=sonic
# - SEARCH_BACKEND_PASSWORD=SomeSecretPassword
# - MEDIA_MAX_SIZE=750m # increase this filesize limit to allow archiving larger audio/video files
# - TIMEOUT=60 # increase this number to 120+ seconds if you see many slow downloads timing out
# - CHECK_SSL_VALIDITY=True # set to False to disable strict SSL checking (allows saving URLs w/ broken certs)
# - SAVE_ARCHIVE_DOT_ORG=True # set to False to disable submitting all URLs to Archive.org when archiving
# ...
# 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
# dns:
# - 172.20.0.53
######## Optional Addons: tweak examples below as needed for your specific use case ########
### Example: To run the Sonic full-text search backend, uncomment this block and the sonic config above
# 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 index: docker-compose run archivebox update --index-only
# sonic:
# image: valeriansaliou/sonic:v1.3.0
# image: archivebox/sonic:latest
# expose:
# - 1491
# environment:
# - SEARCH_BACKEND_PASSWORD=SecretPassword
# - SEARCH_BACKEND_PASSWORD=SomeSecretPassword
# volumes:
# - ./sonic.cfg:/etc/sonic.cfg:ro
# - ./data/sonic:/var/lib/sonic/store
### Optional Addons: tweak these examples as needed for your specific use case
### Example: To run pihole in order to block ad/tracker requests during archiving,
# uncomment this block and set up pihole using its admin interface
# Example: Run scheduled imports in a docker instead of using cron on the
# host machine, add tasks and see more info with archivebox schedule --help
# scheduler:
# image: archivebox/archivebox:latest
# command: schedule --foreground --every=day --depth=1 'https://getpocket.com/users/USERNAME/feed/all'
# pihole:
# image: pihole/pihole:latest
# ports:
# - 127.0.0.1:8090:80 # uncomment to access the admin HTTP interface on http://localhost:8090
# environment:
# - WEBPASSWORD=SET_THIS_TO_SOME_SECRET_PASSWORD_FOR_ADMIN_DASHBOARD
# - DNSMASQ_LISTENING=all
# dns:
# - 127.0.0.1
# - 1.1.1.1
# networks:
# dns:
# ipv4_address: 172.20.0.53
# volumes:
# - ./etc/pihole:/etc/pihole
# - ./etc/dnsmasq:/etc/dnsmasq.d
### Example: Enable ability to run regularly scheduled archiving tasks by uncommenting this container
# $ docker compose run archivebox schedule --every=day --depth=1 'https://example.com/some/rss/feed.xml'
# then restart the scheduler container to apply the changes to the schedule
# $ docker compose restart archivebox_scheduler
# archivebox_scheduler:
# image: ${DOCKER_IMAGE:-archivebox/archivebox:dev}
# command: schedule --foreground
# environment:
# - USE_COLOR=True
# - SHOW_PROGRESS=False
# - MEDIA_MAX_SIZE=750m # increase this number to allow archiving larger audio/video files
# # - TIMEOUT=60 # increase if you see timeouts often during archiving / on slow networks
# # - ONLY_NEW=True # set to False to retry previously failed URLs when re-adding instead of skipping them
# # - CHECK_SSL_VALIDITY=True # set to False to allow saving URLs w/ broken SSL certs
# # - SAVE_ARCHIVE_DOT_ORG=True # set to False to disable submitting URLs to Archive.org when archiving
# # - PUID=502 # set to your host user's UID & GID if you encounter permissions issues
# # - PGID=20
# volumes:
# - ./data:/data
# - ./etc/crontabs:/var/spool/cron/crontabs
# # cpus: 2 # uncomment / edit these values to limit container resource consumption
# # mem_limit: 2048m
# # shm_size: 1024m
### Example: Put Nginx in front of the ArchiveBox server for SSL termination
# Example: Put Nginx in front of the ArchiveBox server for SSL termination
# nginx:
# image: nginx:alpine
# ports:
# - 443:443
# - 80:80
# volumes:
# - ./etc/nginx/nginx.conf:/etc/nginx/nginx.conf
# - ./etc/nginx.conf:/etc/nginx/nginx.conf
# - ./data:/var/www
# Example: run all your ArchiveBox traffic through a WireGuard VPN tunnel
### Example: run all your ArchiveBox traffic through a WireGuard VPN tunnel
# wireguard:
# image: linuxserver/wireguard
# image: linuxserver/wireguard:latest
# network_mode: 'service:archivebox'
# cap_add:
# - NET_ADMIN
@@ -78,14 +134,26 @@ services:
# - /lib/modules:/lib/modules
# - ./wireguard.conf:/config/wg0.conf:ro
# Example: Run PYWB in parallel and auto-import WARCs from ArchiveBox
### Example: Run PYWB in parallel and auto-import WARCs from ArchiveBox
# pywb:
# image: webrecorder/pywb:latest
# entrypoint: /bin/sh 'wb-manager add default /archivebox/archive/*/warc/*.warc.gz; wayback --proxy;'
# entrypoint: /bin/sh -c '(wb-manager init default || test $$? -eq 2) && wb-manager add default /archivebox/archive/*/warc/*.warc.gz; wayback;'
# environment:
# - INIT_COLLECTION=archivebox
# ports:
# - 8080:8080
# volumes:
# ./data:/archivebox
# ./data/wayback:/webarchive
# - ./data:/archivebox
# - ./data/wayback:/webarchive
networks:
# network needed for pihole container to offer :53 dns resolving on fixed ip for archivebox container
dns:
ipam:
driver: default
config:
- subnet: 172.20.0.0/24
+1 -1
Submodule docs updated: bfc5f76a61...a1b69c51ba.
+1 -1
View File
@@ -55,7 +55,7 @@
# CURL_BINARY = curl
# GIT_BINARY = git
# WGET_BINARY = wget
# YOUTUBEDL_BINARY = youtube-dl
# YOUTUBEDL_BINARY = yt-dlp
# CHROME_BINARY = chromium
# CHROME_USER_DATA_DIR="~/.config/google-chrome/Default"
+1 -1
View File
@@ -2,7 +2,7 @@
In this folder are some example config files you can use for setting up ArchiveBox on your machine.
E.g. see `etc/nginx` for an example nginx config to serve your archive, or `etc/cron` for an example cron job that crawls a feed every 24 hours.
E.g. see `nginx.conf` for an example nginx config to serve your archive with SSL, or `fly.toml` for an example deployment to the Fly.io hosting platform.
Please contribute your etc files here! Example contributions
+29
View File
@@ -0,0 +1,29 @@
# This is an example systemd service config definition for ArchiveBox.
#
# Link it into place on your system to use systemd to auto-start the ArchiveBox server on boot:
# https://unix.stackexchange.com/questions/224992/where-do-i-put-my-systemd-unit-file
#
# Review and change these lines as-needed for your specific environment and needs:
# WorkingDirectory, ExecStart, User, Group
[Unit]
Description=Open source self-hosted web archiving
Documentation=https://github.com/ArchiveBox/ArchiveBox/wiki
[Service]
Type=simple
WorkingDirectory=/home/archivebox/archivebox/
ExecStart=/usr/local/bin/archivebox server 0.0.0.0:8000
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=always
RestartSec=2
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=archivebox
User=archivebox
Group=archivebox
[Install]
WantedBy=multi-user.target
-1
View File
@@ -1 +0,0 @@
0 24 * * * www-data /opt/ArchiveBox/bin/archive "https://getpocket.com/users/example/feed/all" >> /var/log/ArchiveBox.log
+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
+40
View File
@@ -0,0 +1,40 @@
# fly.toml file generated for archivebox on 2021-04-23T16:35:11-04:00
app = "archivebox"
kill_signal = "SIGINT"
kill_timeout = 5
[env]
[mounts]
source="archivebox_data"
destination="/data"
[experimental]
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8000
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 6
timeout = "2s"
+5 -3
View File
@@ -34,12 +34,14 @@ http {
server {
listen 80 default_server;
server_name _;
root /var/www;
index index.html;
autoindex on;
try_files $uri $uri/ $uri.html =404;
location /archive {
root /var/www/archive;
}
}
}
View File
File renamed without changes.
+1716 -1527
View File
File diff suppressed because it is too large. Load diff
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff. Show more