Compare commits

...
Author SHA1 Message Date
Nick Sweeting 803388ef42 Format Chrome recovery test 2026-08-15 21:36:08 -07:00
Nick Sweeting d9381db20c Fix stale hook and setup pin checks 2026-08-15 21:12:57 -07:00
ArchiveBox Release Bot 50cb7cab33 Bump for abx-dl 1.12.167 2026-08-16 03:34:35 +00:00
Nick Sweeting 71fc3f8c62 Refresh documentation example inventory 2026-08-15 17:15:11 -07:00
ArchiveBox Release Bot a42df9e289 Bump for abx-dl 1.12.163 2026-08-16 00:00:32 +00:00
Nick Sweeting 728f3da055 Guide public setup through the first-run wizard 2026-08-15 16:32:52 -07:00
ArchiveBox Release Bot 69f7e8f606 Bump for abx-dl 1.12.162 2026-08-15 23:16:33 +00:00
Nick Sweeting 7d1c7870ab Keep setup abxpkg pin aligned with lockfile 2026-08-15 16:07:23 -07:00
ArchiveBox Release Bot 04f3dba4b2 Bump for abx-dl 1.12.161 2026-08-15 22:28:48 +00:00
ArchiveBox Release Bot fe3aee8392 Bump for abx-dl 1.12.160 2026-08-15 21:16:21 +00:00
Nick Sweeting 0d37c0d380 Show Docker admin bootstrap on login page 2026-08-15 14:04:06 -07:00
ArchiveBox Release Bot 3f266a3eb5 Bump for abx-dl 1.12.159 2026-08-15 20:28:58 +00:00
Nick Sweeting 4d54241b1c Clarify sudo setup ownership 2026-08-15 13:15:17 -07:00
Nick Sweeting 29aa07ba87 Align released runtime installer pins 2026-08-15 12:49:58 -07:00
ArchiveBox Release Bot 89e204e0e7 Bump for abx-dl 1.12.158 2026-08-15 19:26:12 +00:00
ArchiveBox Release Bot 8760b0d020 Bump for abx-dl 1.12.157 2026-08-15 18:29:17 +00:00
ArchiveBox Release Bot 9852b01f41 Bump for abx-dl 1.12.156 2026-08-15 17:27:12 +00:00
Nick Sweeting 6eea1c470d fix: advance released runtime defaults 2026-08-15 10:25:01 -07:00
ArchiveBox Release Bot aca67773df Bump for abx-dl 1.12.155 2026-08-15 16:24:19 +00:00
Nick Sweeting f0e53508a5 fix: align installer defaults with downloader release 2026-08-15 09:19:21 -07:00
ArchiveBox Release Bot cad9768b5e Bump for abx-dl 1.12.154 2026-08-15 14:14:43 +00:00
Nick Sweeting 4b6b097a87 Remove one-shot Docker containers 2026-08-15 06:25:02 -07:00
ArchiveBox Release Bot d692077433 Bump for abx-dl 1.12.153 2026-08-15 12:56:29 +00:00
Nick Sweeting d020f58445 Build source image from locked downloader 2026-08-15 05:25:19 -07:00
Nick Sweeting d6f076de42 Resolve image binaries as archivebox user 2026-08-15 05:10:41 -07:00
Nick Sweeting 23d4ef8f18 Keep setup bootstrap on abxpkg 1.12.83 2026-08-15 04:55:08 -07:00
ArchiveBox Release Bot 02691f21bd Bump for abx-dl 1.12.152 2026-08-15 11:40:36 +00:00
Nick Sweeting 81e178bd01 Document setup script Docker fallback 2026-08-15 03:57:59 -07:00
Nick Sweeting 013221a54d Keep setup bootstrap on the locked abxpkg 2026-08-15 03:51:56 -07:00
Nick Sweeting 5ec6478b12 Require ArchiveBox health in setup probes 2026-08-15 03:50:13 -07:00
ArchiveBox Release Bot b1dd33a541 Bump for abx-dl 1.12.151 2026-08-15 10:26:34 +00:00
ArchiveBox Release Bot 82403e78db Bump for abx-dl 1.12.150 2026-08-15 09:27:36 +00:00
ArchiveBox Release Bot ceb056263c Bump for abx-dl 1.12.149 2026-08-15 08:49:07 +00:00
Nick Sweeting 79d47de611 Keep setup bootstrap aligned with locked abxpkg 2026-08-15 01:32:13 -07:00
ArchiveBox Release Bot f2467197fa Bump for abx-dl 1.12.147 2026-08-15 08:13:44 +00:00
ArchiveBox Release Bot f10a7084ba Bump for abx-dl 1.12.146 2026-08-15 07:21:06 +00:00
ArchiveBox Release Bot 754db79b04 Bump for abx-dl 1.12.145 2026-08-15 06:55:30 +00:00
ArchiveBox Release Bot b38102e23a Bump for abx-dl 1.12.144 2026-08-15 06:23:35 +00:00
ArchiveBox Release Bot 19a2eb0846 Bump for abx-dl 1.12.143 2026-08-15 05:47:04 +00:00
Nick Sweeting 01101f6b78 Align release checks with current package baseline 2026-08-14 22:22:50 -07:00
ArchiveBox Release Bot 63b539253f Bump for abx-dl 1.12.142 2026-08-15 03:45:53 +00:00
ArchiveBox Release Bot 91c02bb8c3 Bump for abx-dl 1.12.141 2026-08-15 03:25:25 +00:00
Nick Sweeting f1984cd87a Bootstrap setup with abxpkg 1.12.79 2026-08-14 20:10:49 -07:00
ArchiveBox Release Bot f7822088f8 Bump for abx-dl 1.12.140 2026-08-15 02:59:41 +00:00
Nick Sweeting b9c563eb5b Download Compose setup atomically 2026-08-14 19:39:09 -07:00
ArchiveBox Release Bot c7b356aa73 Bump for abx-dl 1.12.139 2026-08-15 02:35:02 +00:00
Nick Sweeting 4c4cb309b3 Use builder find for native library cleanup 2026-08-14 19:19:07 -07:00
ArchiveBox Release Bot 169d991dbc Bump for abx-dl 1.12.138 2026-08-15 02:02:47 +00:00
Nick Sweeting 2b194c48cd Use configured abxpkg library for queued binaries 2026-08-14 18:56:25 -07:00
Nick Sweeting 99e863f997 Remove stale downloader binary state coupling 2026-08-14 18:51:16 -07:00
Nick Sweeting 78469f388a Fall back from unavailable default Sonic search 2026-08-14 18:46:43 -07:00
ArchiveBox Release Bot 38ceec1e35 Bump for abx-dl 1.12.137 2026-08-15 01:41:18 +00:00
Nick Sweeting 100420446e Bootstrap setup with abxpkg 1.12.77 2026-08-14 18:16:48 -07:00
Nick Sweeting 4d28c21a55 Bootstrap setup with abxpkg 1.12.76 2026-08-14 17:28:53 -07:00
Nick Sweeting dd736356d3 Bootstrap setup with abxpkg 1.12.74 2026-08-14 16:33:55 -07:00
Nick Sweeting 8f766ace6f Align release checks with binary request cache 2026-08-14 15:39:38 -07:00
Nick Sweeting c10579bad1 Keep binary overrides native to abxpkg 2026-08-14 14:17:34 -07:00
ArchiveBox Release Bot d2f3b3a65b Bump for abx-dl 1.12.132 2026-08-14 21:16:04 +00:00
Nick Sweeting defcb91642 Align novice install and upgrade commands 2026-08-14 14:03:23 -07:00
ArchiveBox Release Bot 03bd92f07a Bump for abx-dl 1.12.131 2026-08-14 20:37:58 +00:00
Nick Sweeting 3dc4804899 Bootstrap abxpkg 1.12.72 2026-08-14 13:02:12 -07:00
Nick Sweeting f76091d5cb Refresh documentation snippet inventory 2026-08-14 11:48:59 -07:00
Nick Sweeting 6586dade7a Keep default setup on current abxpkg 2026-08-14 11:43:48 -07:00
Nick Sweeting 28bdc5902d Exercise filesystem hops through migrate-only update 2026-08-14 10:43:48 -07:00
ArchiveBox Release Bot 1014badebc Bump for abx-dl 1.12.129 2026-08-14 17:13:36 +00:00
Nick Sweeting 7d8cc42cb1 Fix admin creation command shown on login page 2026-08-14 08:58:23 -07:00
ArchiveBox Release Bot ec1fa05ed5 Bump for abx-dl 1.12.128 2026-08-14 15:21:26 +00:00
ArchiveBox Release Bot 248246a49e Bump for abx-dl 1.12.127 2026-08-14 15:09:32 +00:00
ArchiveBox Release Bot cf796bbe26 Bump for abx-dl 1.12.126 2026-08-14 14:34:24 +00:00
Nick Sweeting a473debb87 Use running Compose service in setup guidance 2026-08-14 07:10:24 -07:00
Nick Sweeting e16092b564 Use running Compose service for CLI examples 2026-08-14 07:00:36 -07:00
ArchiveBox Release Bot dd2f6d6742 Bump for abx-dl 1.12.125 2026-08-14 13:57:10 +00:00
Nick Sweeting d4a5e9ab32 Refresh Docker documentation inventory 2026-08-14 06:28:48 -07:00
Nick Sweeting f222dc2cb5 Refresh setup bootstrap guidance 2026-08-14 06:23:59 -07:00
Nick Sweeting 370753e672 Simplify fresh Docker setup 2026-08-14 06:16:51 -07:00
Nick Sweeting a3671a707e Allow compiled Python bytecode in image budget 2026-08-14 06:07:18 -07:00
ArchiveBox Release Bot eeb5d88e15 Bump for abx-dl 1.12.124 2026-08-14 10:10:18 +00:00
ArchiveBox Release Bot b702384e79 Bump for abx-dl 1.12.123 2026-08-14 09:10:17 +00:00
Nick Sweeting 24f4aaa4f8 Keep root tool traversal on service group 2026-08-14 02:01:53 -07:00
Nick Sweeting c8f7a77430 Harden root collection ownership handoff 2026-08-14 01:53:10 -07:00
ArchiveBox Release Bot 34ab9e66dd Bump for abx-dl 1.12.122 2026-08-14 08:30:44 +00:00
ArchiveBox Release Bot bb2205cd9f Bump for abx-dl 1.12.121 2026-08-14 07:46:24 +00:00
ArchiveBox Release Bot a5baa3f0cd Bump for abx-dl 1.12.120 2026-08-14 07:05:24 +00:00
ArchiveBox Release Bot be73b36391 Bump for abx-dl 1.12.118 2026-08-14 05:32:14 +00:00
Nick Sweeting 08e7a8db2d Refresh README command inventory 2026-08-13 22:10:09 -07:00
Nick Sweeting 9433eafccc Clarify installed dependency commands 2026-08-13 22:07:51 -07:00
ArchiveBox Release Bot 482f71f3d8 Bump for abx-dl 1.12.116 2026-08-14 04:11:36 +00:00
Nick Sweeting 9fe8be7c85 Refresh documentation command inventory 2026-08-13 20:53:58 -07:00
ArchiveBox Release Bot 13f0a197cb Bump for abx-dl 1.12.115 2026-08-14 02:46:22 +00:00
ArchiveBox Release Bot b10c482919 Bump for abx-dl 1.12.113 2026-08-14 00:44:47 +00:00
Nick Sweeting 06ce2d8cea Avoid unrelated prerelease dependencies in uv installs 2026-08-13 15:16:32 -07:00
ArchiveBox Release Bot e04cbdd547 Bump for abx-dl 1.12.112 2026-08-13 21:43:23 +00:00
Nick Sweeting 968d97de04 Omit archive result summary from dependency installs 2026-08-13 14:13:09 -07:00
Nick Sweeting 5f9c36eb29 Remove unused setup prerequisite and fix admin URLs 2026-08-13 12:36:56 -07:00
Nick Sweeting 2dec55ecac Refresh documentation command inventory 2026-08-13 12:15:55 -07:00
ArchiveBox Release Bot ef5ee039b1 Bump for abx-dl 1.12.111 2026-08-13 19:12:54 +00:00
Nick Sweeting ee1fd3cc48 Use interactive admin setup in Docker example 2026-08-13 11:59:48 -07:00
Nick Sweeting 6d32129007 Align install references with supported channels 2026-08-13 11:58:42 -07:00
Nick Sweeting afa6b78fff Toggle snapshot header once per click 2026-08-13 11:57:02 -07:00
Nick Sweeting 619449e05c Separate alternative package manager commands 2026-08-13 11:57:02 -07:00
Nick Sweeting a5db75fd15 Assert crawl status survives legacy migration 2026-08-13 11:55:07 -07:00
Nick Sweeting 1430aaffe0 Bootstrap abxpkg 1.12.62 in setup script 2026-08-13 11:23:15 -07:00
43 changed files with 422 additions and 454 deletions

No files matched your search

+2 -2
View File
@@ -259,7 +259,7 @@ jobs:
PY
"$DOCKER_BINARY" run --rm --user 911:911 --platform '${{ matrix.platform }}' --entrypoint /venv/bin/python3 "$IMAGE" -c \
'import json, pathlib, stat; cache_file = "/opt/archivebox/lib/env/derived.env"; raw = open(cache_file).read().strip(); records = json.loads(raw[len("ABXPKG_DERIVED_CACHE=\x27"):-1]); plans = [plan for record in records.values() for plan in record.get("script_exec_plans", {}).values()]; contexts = [json.loads(plan["run_context"])["base"]["abxpkg_env"] for plan in plans]; fingerprints = [fingerprint for plan in plans for fingerprint in plan["fingerprint"]]; actual = lambda fingerprint: (lambda path, value: {"path": str(path.resolve(strict=False)), "size": value.st_size, "mtime_ns": value.st_mtime_ns, "mode": stat.S_IMODE(value.st_mode), "euid": value.st_uid})(pathlib.Path(fingerprint["path"]), pathlib.Path(fingerprint["path"]).stat()); mismatches = [(fingerprint, actual(fingerprint)) for fingerprint in fingerprints if fingerprint != actual(fingerprint)]; assert plans and {plan.get("euid") for plan in plans} == {911}; assert all("ABXPKG_TMP_CACHE_DIR" not in context for context in contexts); assert not mismatches, mismatches[:3]'
'import json, pathlib, stat; cache_file = "/opt/archivebox/lib/env/derived.env"; raw = open(cache_file).read().strip(); records = json.loads(raw[len("ABXPKG_DERIVED_CACHE=\x27"):-1]); projections = [projection for record in records.values() for projection in record.get("request_exec_projections", {}).values()]; plans = [projection["validation"] for projection in projections]; fingerprints = [fingerprint for plan in plans for fingerprint in plan["fingerprint"]]; actual = lambda fingerprint: (lambda path, value: {"path": str(path.resolve(strict=False)), "size": value.st_size, "mtime_ns": value.st_mtime_ns, "mode": stat.S_IMODE(value.st_mode), "euid": value.st_uid})(pathlib.Path(fingerprint["path"]), pathlib.Path(fingerprint["path"]).stat()); mismatches = [(fingerprint, actual(fingerprint)) for fingerprint in fingerprints if fingerprint != actual(fingerprint)]; assert projections and {plan.get("euid") for plan in plans} == {911}; assert all(not record.get("script_exec_plans") for record in records.values()); assert "ABXPKG_TMP_CACHE_DIR" not in json.dumps(projections); assert not mismatches, mismatches[:3]'
- name: Resolve real Docker CLI test dependencies through abxpkg
if: matrix.platform == 'linux/amd64'
@@ -384,7 +384,7 @@ jobs:
'${{ runner.temp }}/archivebox-image.tar' \
'${{ steps.docker_build.outputs.digest }}' \
"$LOCAL_IMAGE_ID" \
"$((780 * 1024 * 1024))" <<'PY'
"$((910 * 1024 * 1024))" <<'PY'
from hashlib import sha256
import json
import os
+28
View File
@@ -189,5 +189,33 @@ jobs:
echo "Root-owned files remain in $root_tool_dir/data" >&2
exit 1
fi
sudo mkdir "$root_tool_dir/external"
sudo ln -s "$root_tool_dir/external" "$root_tool_dir/data/external-link"
sudo env PATH="$root_tool_dir/bin:$PATH" \
bash -c "cd '$root_tool_dir/data' && archivebox status"
sudo stat -c %U "$root_tool_dir/external" | grep -qx root
sudo mkdir "$root_tool_dir/data-universal-init"
sudo env PATH="$root_tool_dir/bin:$PATH" \
bash -c "cd '$root_tool_dir/data-universal-init' && archivebox status --init"
sudo stat -c %U "$root_tool_dir/data-universal-init" | grep -qx archivebox
sudo mkdir "$root_tool_dir/data-install"
sudo env PATH="$root_tool_dir/bin:$PATH" \
bash -c "cd '$root_tool_dir/data-install' && archivebox install --dry-run"
sudo stat -c %U "$root_tool_dir/data-install" | grep -qx archivebox
sudo useradd --create-home archivebox-smoke-user
smoke_user_group="$(id -gn archivebox-smoke-user)"
sudo mkdir "$root_tool_dir/data-mixed-owner"
sudo chown archivebox-smoke-user:root "$root_tool_dir/data-mixed-owner"
sudo env PATH="$root_tool_dir/bin:$PATH" \
bash -c "cd '$root_tool_dir/data-mixed-owner' && archivebox init"
sudo stat -c %U:%G "$root_tool_dir/data-mixed-owner" | grep -qx "archivebox-smoke-user:$smoke_user_group"
if sudo find "$root_tool_dir/data-mixed-owner" -xdev -user root -print -quit | grep -q .; then
echo "Root-owned files remain in $root_tool_dir/data-mixed-owner" >&2
exit 1
fi
sudo rm -rf "$root_tool_dir"
fi
+1 -1
View File
@@ -38,7 +38,7 @@ uv run --project .. archivebox run
Recommended CLI install:
```bash
uv tool install --python 3.13 --prerelease allow --upgrade 'git+https://github.com/ArchiveBox/ArchiveBox.git@dev'
uv tool install --python 3.13 --prerelease explicit --upgrade 'git+https://github.com/ArchiveBox/ArchiveBox.git@dev'
mkdir -p ~/archivebox/data
cd ~/archivebox/data
archivebox init --install
+7 -7
View File
@@ -10,10 +10,10 @@
# --build-context abx-plugins=../abx-plugins \
# -t archivebox/abx-dl:dev
# docker buildx build . -f Dockerfile \
# --build-arg ABX_DL_IMAGE=archivebox/abx-dl:1.11.272 \
# --build-arg ABX_DL_IMAGE=archivebox/abx-dl:1.12.158 \
# -t archivebox:multistage
ARG ABX_DL_IMAGE=archivebox/abx-dl:1.11.272
ARG ABX_DL_IMAGE=archivebox/abx-dl:1.12.158
FROM archivebox/sonic:1.4.9 AS sonic
FROM ${ABX_DL_IMAGE} AS archivebox-runtime-base
@@ -85,8 +85,8 @@ RUN cp /VERSION.txt /ABX-DL-VERSION.txt \
&& echo "BUILD_START_TIME=$(date +"%Y-%m-%d %H:%M:%S %s") TZ=${TZ} LANG=${LANG}" \
&& uname -a \
&& sed -n '1,7p' /etc/os-release \
&& abxpkg load --binproviders=env node \
&& abxpkg load --binproviders=env uv \
&& setpriv --reuid="$ARCHIVEBOX_USER" --regid="$ARCHIVEBOX_USER" --init-groups abxpkg load --binproviders=env node \
&& setpriv --reuid="$ARCHIVEBOX_USER" --regid="$ARCHIVEBOX_USER" --init-groups abxpkg load --binproviders=env uv \
) | tee -a /VERSION.txt
FROM archivebox-runtime-base AS archivebox-builder
@@ -155,7 +155,7 @@ PY
--no-install-workspace \
--no-sources
ABXPKG_NO_CACHE=True abxpkg env --install --binproviders=env,apt --lib="$ABXPKG_LIB_DIR" --overrides='{"apt":{"install_args":["binutils"]}}' strip >/dev/null
"$ABXPKG_LIB_DIR/env/bin/find" /venv/lib/python3.*/site-packages -type f -name '*.so' -print0 > /tmp/archivebox-native-libraries
/usr/bin/find /venv/lib/python3.*/site-packages -type f -name '*.so' -print0 > /tmp/archivebox-native-libraries
while IFS= read -r -d '' native_library; do
magic=''
if IFS= read -r -N 4 magic < "$native_library" && [[ "$magic" == $'\x7fELF' ]]; then
@@ -166,7 +166,7 @@ rm -f /tmp/archivebox-native-libraries
rm -f /venv/bin/uv /venv/bin/uvx
abxpkg run --binproviders=env --lib="$ABXPKG_LIB_DIR" apt-get purge -y binutils build-essential gcc libldap2-dev libsasl2-dev libssl-dev
abxpkg run --binproviders=env --lib="$ABXPKG_LIB_DIR" apt-get autoremove -y
"$ABXPKG_LIB_DIR/env/bin/find" "$ABXPKG_LIB_DIR/env/bin" -maxdepth 1 -type l -name strip -delete
/usr/bin/find "$ABXPKG_LIB_DIR/env/bin" -maxdepth 1 -type l -name strip -delete
rm -rf /var/lib/apt/lists/*
EOF
@@ -221,7 +221,7 @@ RUN echo "[*] Setting up $ARCHIVEBOX_USER user uid=${DEFAULT_ARCHIVEBOX_UID}..."
&& usermod --append --groups audio,video "$ARCHIVEBOX_USER" \
&& [[ "$(id -u "$ARCHIVEBOX_USER")" == "$DEFAULT_ARCHIVEBOX_UID" ]] || usermod -u "$DEFAULT_ARCHIVEBOX_UID" "$ARCHIVEBOX_USER" \
&& [[ "$(id -g "$ARCHIVEBOX_USER")" == "$DEFAULT_ARCHIVEBOX_GID" ]] || groupmod -g "$DEFAULT_ARCHIVEBOX_GID" "$ARCHIVEBOX_USER" \
&& abxpkg load --binproviders=env sonic | tee -a /VERSION.txt \
&& setpriv --reuid="$ARCHIVEBOX_USER" --regid="$ARCHIVEBOX_USER" --init-groups abxpkg load --binproviders=env sonic | tee -a /VERSION.txt \
&& install -d -o "$DEFAULT_ARCHIVEBOX_UID" -g "$DEFAULT_ARCHIVEBOX_GID" "$DATA_DIR" "$TMP_DIR" "$CONFIG_DIR" "$ABXPKG_LIB_DIR" "$XDG_CACHE_HOME" "$PLAYWRIGHT_BROWSERS_PATH" \
&& install -d -o "$DEFAULT_ARCHIVEBOX_UID" -g "$DEFAULT_ARCHIVEBOX_GID" "/home/$ARCHIVEBOX_USER" \
&& chown "$DEFAULT_ARCHIVEBOX_UID:$DEFAULT_ARCHIVEBOX_GID" "$DATA_DIR" "$TMP_DIR" \
+30 -34
View File
@@ -23,7 +23,7 @@ Without active preservation effort, everything on the internet eventually disapp
*ArchiveBox is an open source tool that lets organizations & individuals archive both public & private web content while retaining control over their data. It can be used to save copies of bookmarks, preserve evidence for legal cases, backup photos from FB/Insta/Flickr or media from YT/Soundcloud/etc., save research papers, and more...*
<br/>
> ➡️ Get ArchiveBox with `uv tool install --python 3.13 --prerelease allow --upgrade archivebox` on [Linux](#quickstart)/[macOS](#quickstart), or via **[Docker](#quickstart)** ⭐️ on any OS.
> ➡️ Get ArchiveBox with `uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'` on [Linux](#quickstart)/[macOS](#quickstart), or via **[Docker](#quickstart)** ⭐️ on Linux/macOS.
*Once installed, you can interact with it through the: [Browser Extension](https://github.com/ArchiveBox/archivebox-browser-extension), [CLI](#usage), [self-hosted web interface](https://github.com/ArchiveBox/ArchiveBox/wiki/Publishing-Your-Archive), [Python API](https://github.com/ArchiveBox/ArchiveBox/wiki/Usage#python-shell-usage), or [filesystem](#static-archive-exporting).*
@@ -70,27 +70,26 @@ The goal is to sleep soundly knowing the part of the internet you care about wil
<br/>
<pre lang="bash"><code style="white-space: pre-line"># Option A: Get ArchiveBox with Docker Compose (recommended):
mkdir -p ~/archivebox/data && cd ~/archivebox
curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml # edit options in this file as-needed
curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml
docker compose pull
docker compose run --rm archivebox init
docker compose run --rm archivebox install
docker compose run --rm archivebox manage createsuperuser
docker compose up -d --wait # initializes new collections automatically
docker compose exec archivebox archivebox manage createsuperuser # create the first Web UI user
# docker compose run --rm archivebox add 'https://example.com'
# docker compose run --rm archivebox help
# docker compose up
<br/>
<br/>
# Option B: Or use it as a plain Docker container:
mkdir -p ~/archivebox/data && cd ~/archivebox/data
docker run -it -v $PWD:/data archivebox/archivebox:dev init
docker run -it -v $PWD:/data archivebox/archivebox:dev install
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev init
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev install
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev manage createsuperuser
docker run -d --name archivebox -v "$PWD:/data" -p 8000:8000 archivebox/archivebox:dev
# docker run -it -v $PWD:/data archivebox/archivebox:dev add 'https://example.com'
# docker run -it -v $PWD:/data archivebox/archivebox:dev help
# docker run -it -v $PWD:/data -p 8000:8000 archivebox/archivebox:dev
<br/>
<br/>
# Option C: Or install it with uv (see Quickstart below for apt, brew, and more)
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
mkdir -p ~/archivebox/data && cd ~/archivebox/data
archivebox init
archivebox install
@@ -176,7 +175,7 @@ ArchiveBox is free for everyone to self-host, but we also provide support, secur
#### ✳️&nbsp; Easy Setup
<details>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447182-29758200-af0b-11eb-97bd-58723fee62ab.png" alt="Docker" height="28px" align="top"/> <code>docker-compose</code></b> (macOS/Linux/Windows) &nbsp; <b>👈&nbsp; recommended</b> &nbsp; <i>(click to expand)</i></summary>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447182-29758200-af0b-11eb-97bd-58723fee62ab.png" alt="Docker" height="28px" align="top"/> <code>docker-compose</code></b> (macOS/Linux) &nbsp; <b>👈&nbsp; recommended</b> &nbsp; <i>(click to expand)</i></summary>
<br/>
<i>👍 Docker Compose is recommended for the easiest install/update UX + best security + all <a href="#dependencies">extras</a> out-of-the-box.</i>
<br/><br/>
@@ -184,21 +183,18 @@ ArchiveBox is free for everyone to self-host, but we also provide support, secur
<li>Install <a href="https://docs.docker.com/get-docker/">Docker</a> on your system (if not already installed).</li>
<li>Download the <a href="https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/docker-compose.yml" download><code>docker-compose.yml</code></a> file into a new empty directory (can be anywhere).
<pre lang="bash"><code style="white-space: pre-line">mkdir -p ~/archivebox/data && cd ~/archivebox
# Read and edit docker-compose.yml options as-needed after downloading
curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml
docker compose pull
</code></pre></li>
<li>Initialize the collection, then create an admin user (or set ADMIN_USERNAME/ADMIN_PASSWORD in docker-compose.yml)
<pre lang="bash"><code style="white-space: pre-line">docker compose run --rm archivebox init
docker compose run --rm archivebox install
docker compose run --rm archivebox manage createsuperuser
<li>Start the server, which initializes a new collection automatically, then create the first admin user.
<pre lang="bash"><code style="white-space: pre-line">docker compose up -d --wait
docker compose exec archivebox archivebox manage createsuperuser
</code></pre></li>
<li>Next steps: Start the server then login to the Web UI <a href="http://archivebox.localhost:8000">http://archivebox.localhost:8000</a> ⇢ Admin.
<pre lang="bash"><code style="white-space: pre-line">docker compose up -d
# completely optional, CLI can always be used without running a server
# docker compose run --rm [-T] archivebox [subcommand] [--help]
docker compose run --rm archivebox add 'https://example.com'
docker compose run --rm archivebox help
<li>Next steps: Log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
<pre lang="bash"><code style="white-space: pre-line">
# run CLI commands inside the server container started above
docker compose exec archivebox archivebox add 'https://example.com'
docker compose exec archivebox archivebox help
</code></pre>
<i>For more info, see <a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Install#option-a-docker--docker-compose-setup-%EF%B8%8F">Install: Docker Compose</a> in the Wiki. ➡️</i>
</li>
@@ -209,17 +205,17 @@ See <a href="#%EF%B8%8F-cli-usage">below</a> for more usage examples using the C
</details>
<details>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447182-29758200-af0b-11eb-97bd-58723fee62ab.png" alt="Docker" height="28px" align="top"/> <code>docker run</code></b> (macOS/Linux/Windows)</summary>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117447182-29758200-af0b-11eb-97bd-58723fee62ab.png" alt="Docker" height="28px" align="top"/> <code>docker run</code></b> (macOS/Linux)</summary>
<br/>
<ol>
<li>Install <a href="https://docs.docker.com/get-docker/">Docker</a> on your system (if not already installed).</li>
<li>Create a new empty directory and initialize your collection (can be anywhere).
<pre lang="bash"><code style="white-space: pre-line">mkdir -p ~/archivebox/data && cd ~/archivebox/data
docker run -v $PWD:/data -it archivebox/archivebox:dev init
docker run -v $PWD:/data -it archivebox/archivebox:dev install
docker run --rm -v $PWD:/data -it archivebox/archivebox:dev init
docker run --rm -v $PWD:/data -it archivebox/archivebox:dev install
</code></pre>
</li>
<li>Optional: Start the server then login to the Web UI <a href="http://archivebox.localhost:8000">http://archivebox.localhost:8000</a> ⇢ Admin.
<li>Optional: Start the server then log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
<pre lang="bash"><code style="white-space: pre-line">docker run -v $PWD:/data -p 8000:8000 archivebox/archivebox:dev
# completely optional, CLI can always be used without running a server
# docker run -v $PWD:/data -it archivebox/archivebox:dev [subcommand] [--help]
@@ -264,7 +260,7 @@ See <a href="https://docs.sweeting.me/s/against-curl-sh">"Against curl | sh as a
<li>Install <a href="https://docs.astral.sh/uv/getting-started/installation/">uv</a> on your system (if not already installed).</li>
<li>Install the ArchiveBox package using <code>uv</code>.
<pre lang="bash"><code style="white-space: pre-line">uv tool install --python 3.13 --prerelease allow --upgrade archivebox
<pre lang="bash"><code style="white-space: pre-line">uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
archivebox version
</code></pre>
<i>See the <a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Install">Install: Bare Metal</a> Wiki for full install instructions for each OS...</i>
@@ -275,7 +271,7 @@ archivebox init # initialize a new collection
archivebox install # install all the runtime dependencies (e.g. chrome, single-file, yt-dlp, etc.)
</code></pre>
</li>
<li>Create an admin account, then optionally start the server and log in to the Web UI at <a href="http://archivebox.localhost:8000">http://archivebox.localhost:8000</a> ⇢ Admin.
<li>Create an admin account, then optionally start the server and log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
<pre lang="bash"><code style="white-space: pre-line">archivebox manage createsuperuser
archivebox server 0.0.0.0:8000
# completely optional, CLI can always be used without running a server
@@ -312,7 +308,7 @@ archivebox add 'https://example.com'
</code></pre>
<br/>
</li>
<li>Create an admin account, then optionally start the server and log in to the Web UI at <a href="http://archivebox.localhost:8000">http://archivebox.localhost:8000</a> ⇢ Admin.
<li>Create an admin account, then optionally start the server and log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
<pre lang="bash"><code style="white-space: pre-line">archivebox manage createsuperuser
archivebox server 0.0.0.0:8000
# completely optional, CLI can always be used without running a server
@@ -336,7 +332,7 @@ Run Homebrew as your normal non-root user on both macOS and Linux; do not use <c
<pre lang="bash"><code style="white-space: pre-line">brew tap archivebox/archivebox
brew trust archivebox/archivebox
brew install archivebox
archivebox version # make sure all dependencies are installed
archivebox version # verify the installed version
</code></pre>
<i>See the <a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Install#option-c-bare-metal-setup">Install: Bare Metal</a> Wiki for more granular instructions for macOS... ➡️</i>
</li>
@@ -346,7 +342,7 @@ archivebox init
archivebox install
</code></pre>
</li>
<li>Create an admin account, then optionally start the server and log in to the Web UI at <a href="http://archivebox.localhost:8000">http://archivebox.localhost:8000</a> ⇢ Admin.
<li>Create an admin account, then optionally start the server and log in to the Admin UI at <a href="http://admin.archivebox.localhost:8000">http://admin.archivebox.localhost:8000</a>.
<pre lang="bash"><code style="white-space: pre-line">archivebox manage createsuperuser
archivebox server 0.0.0.0:8000
# completely optional, CLI can always be used without running a server
@@ -405,7 +401,7 @@ See <a href="#%EF%B8%8F-cli-usage">below</a> for usage examples using the CLI, W
<summary><img src="https://github.com/ArchiveBox/ArchiveBox/assets/511499/0c46e949-00fe-49c8-a613-ee14501c014c" alt="Self-hosting Platforms" height="28px" align="top"/><b> TrueNAS / UNRAID / YunoHost / Cloudron / etc.</b> (self-hosting solutions)</summary>
<br/>
> *Warning: These are contributed by external volunteers and may lag behind the official `pip` channel.*
> *Warning: These are contributed by external volunteers and may lag behind the official Docker and `uv` channels.*
<ul>
<li><s>TrueNAS: <a href="https://truecharts.org/charts/stable/archivebox/">Official ArchiveBox TrueChart</a> / <a href="https://dev.to/finloop/setting-up-archivebox-on-truenas-scale-1788">Custom App Guide</a></s> (<a href="https://truecharts.org/news/scale-deprecation/">TrueCharts is discontinued</a>, wait for <a href="https://forums.truenas.com/t/the-future-of-electric-eel-and-apps/5409/">Electric Eel</a>)</li>
@@ -584,12 +580,12 @@ find ./archive/users -path '*/snapshots/*/*/*/index.html' # inspect snapshot da
<details>
<summary><b>🖥&nbsp; Web UI & API Usage</b></summary>
<pre lang="bash"><code style="white-space: pre-line">
# Start the server on bare metal (pip/apt/brew/etc):
# Start the server on bare metal (uv/apt/brew):
archivebox manage createsuperuser # create a new admin user via CLI
archivebox server 0.0.0.0:8000 # start the server
<br/>
# Or with Docker Compose:
nano docker-compose.yml # setup initial ADMIN_USERNAME & ADMIN_PASSWORD
docker compose run --rm archivebox manage createsuperuser
docker compose up # start the server
<br/>
# Or with a Docker container:
+21 -29
View File
@@ -303,33 +303,29 @@ def version(
continue
plugin_requested = bool(requested_names)
plugin_enabled = plugin_name in enabled_plugin_names
logical_records = get_required_binary_requests(
binary_records = get_required_binary_requests(
plugin,
plugin.config.required_binaries,
overrides=runtime_config,
derived_overrides=derived_config,
run_output_dir=CONSTANTS.DATA_DIR,
)
actual_records = get_required_binary_requests(
plugin,
plugin.config.required_binaries,
overrides=runtime_config,
derived_overrides=derived_config,
run_output_dir=CONSTANTS.DATA_DIR,
logical_names=False,
)
for logical_record, actual_record in zip(logical_records, actual_records, strict=False):
logical_name = str(logical_record["name"])
actual_name = str(actual_record["name"])
display_name = Path(actual_name).expanduser().name if ("/" in actual_name or actual_name.startswith("~")) else logical_name
for binary_record in binary_records:
actual_name = str(binary_record["name"])
logical_name = (
Path(actual_name).expanduser().name
if ("/" in actual_name or "\\" in actual_name or actual_name.startswith("~"))
else actual_name
)
display_name = logical_name
if not plugin_requested and not binary_is_requested(logical_name, actual_name, display_name):
continue
if not plugin_enabled and not requested_names:
continue
if _binary_record_matches_runtime(db_binaries.get(logical_name), config.ABXPKG_LIB_DIR):
continue
signature = json.dumps(actual_record, sort_keys=True, default=str)
declared_binary_specs.setdefault(signature, actual_record)
signature = json.dumps(binary_record, sort_keys=True, default=str)
declared_binary_specs.setdefault(signature, binary_record)
loaded_binaries: dict[str, BinaryEvent | None] = {}
if declared_binary_specs:
@@ -365,25 +361,21 @@ def version(
continue
plugin_requested = bool(requested_names)
plugin_enabled = plugin_name in enabled_plugin_names
logical_records = get_required_binary_requests(
binary_records = get_required_binary_requests(
plugin,
plugin.config.required_binaries,
overrides=runtime_config,
derived_overrides=derived_config,
run_output_dir=CONSTANTS.DATA_DIR,
)
actual_records = get_required_binary_requests(
plugin,
plugin.config.required_binaries,
overrides=runtime_config,
derived_overrides=derived_config,
run_output_dir=CONSTANTS.DATA_DIR,
logical_names=False,
)
for logical_record, actual_record in zip(logical_records, actual_records, strict=False):
logical_name = str(logical_record["name"])
actual_name = str(actual_record["name"])
display_name = Path(actual_name).expanduser().name if ("/" in actual_name or actual_name.startswith("~")) else logical_name
for binary_record in binary_records:
actual_name = str(binary_record["name"])
logical_name = (
Path(actual_name).expanduser().name
if ("/" in actual_name or "\\" in actual_name or actual_name.startswith("~"))
else actual_name
)
display_name = logical_name
if not plugin_requested and not binary_is_requested(logical_name, actual_name, display_name):
continue
@@ -401,7 +393,7 @@ def version(
# providers the current collection will never execute.
continue
else:
loaded = loaded_binaries[json.dumps(actual_record, sort_keys=True, default=str)]
loaded = loaded_binaries[json.dumps(binary_record, sort_keys=True, default=str)]
abspath = loaded.abspath if loaded is not None else ""
version_str = str(loaded.version or "unknown")[:15] if loaded is not None else "unknown"
provider = str(loaded.binprovider or "env")[:8] if loaded is not None else "env"
+2 -2
View File
@@ -59,10 +59,10 @@ def setup_django(check_db=False) -> None:
with SudoPermission(uid=0):
# running as root is a special case where it's ok to be a bit slower
# make sure data dir is always owned by the correct user
os.chown(CONSTANTS.DATA_DIR, ARCHIVEBOX_USER, ARCHIVEBOX_GROUP)
os.chown(CONSTANTS.DATA_DIR, ARCHIVEBOX_USER, ARCHIVEBOX_GROUP, follow_symlinks=False)
if CONSTANTS.DATA_DIR.exists():
for child in CONSTANTS.DATA_DIR.iterdir():
os.chown(child, ARCHIVEBOX_USER, ARCHIVEBOX_GROUP)
os.chown(child, ARCHIVEBOX_USER, ARCHIVEBOX_GROUP, follow_symlinks=False)
# Suppress the "database access during app initialization" warning
# This warning can be triggered during django.setup() but is safe to ignore
+9 -4
View File
@@ -32,11 +32,13 @@ def select_archivebox_user(
data_dir_gid: int,
account_uid: int | None,
account_gid: int | None,
data_dir_owner_gid: int | None = None,
data_dir_owner_exists: bool = True,
) -> tuple[int, int]:
if running_uid == 0:
if data_dir_uid != 0 and data_dir_owner_exists:
return data_dir_uid, data_dir_gid
owner_gid = data_dir_owner_gid if data_dir_gid == 0 and data_dir_owner_gid is not None else data_dir_gid
return data_dir_uid, owner_gid
if account_uid is not None and account_gid is not None:
return account_uid, account_gid
@@ -57,9 +59,10 @@ except PermissionError:
DATA_DIR_GID = 0
try:
pwd.getpwuid(DATA_DIR_UID)
DATA_DIR_OWNER_GID = pwd.getpwuid(DATA_DIR_UID).pw_gid
DATA_DIR_OWNER_EXISTS = True
except KeyError:
DATA_DIR_OWNER_GID = None
DATA_DIR_OWNER_EXISTS = False
DEFAULT_UID = 911
@@ -138,6 +141,7 @@ ARCHIVEBOX_USER, ARCHIVEBOX_GROUP = select_archivebox_user(
sudo_gid=SUDO_GID,
data_dir_uid=DATA_DIR_UID,
data_dir_gid=DATA_DIR_GID,
data_dir_owner_gid=DATA_DIR_OWNER_GID,
account_uid=ARCHIVEBOX_ACCOUNT.pw_uid if ARCHIVEBOX_ACCOUNT is not None else None,
account_gid=ARCHIVEBOX_ACCOUNT.pw_gid if ARCHIVEBOX_ACCOUNT is not None else None,
data_dir_owner_exists=DATA_DIR_OWNER_EXISTS,
@@ -205,7 +209,7 @@ def root_data_dir_handoff_paths(data_dir: Path, argv: list[str]) -> tuple[Path,
except (FileNotFoundError, PermissionError):
return ()
is_init = "init" in argv[1:]
is_init = any(arg in ("init", "install", "--init", "--quick-init") for arg in argv[1:])
collection_exists = any((data_dir / marker).exists() for marker in (".archivebox_id", "ArchiveBox.conf", "index.sqlite3"))
if not collection_exists and not (is_init and not children):
return ()
@@ -297,7 +301,8 @@ def drop_privileges():
# archivebox service account when the data dir is root-owned.
if os.geteuid() == 0 and ARCHIVEBOX_USER != 0 and ARCHIVEBOX_USER_EXISTS:
pw_record = pwd.getpwuid(ARCHIVEBOX_USER)
os.initgroups(pw_record.pw_name, ARCHIVEBOX_GROUP)
supplemental_gid = ARCHIVEBOX_ACCOUNT.pw_gid if ARCHIVEBOX_ACCOUNT is not None else ARCHIVEBOX_GROUP
os.initgroups(pw_record.pw_name, supplemental_gid)
if os.getegid() != ARCHIVEBOX_GROUP:
os.setegid(ARCHIVEBOX_GROUP)
if os.geteuid() != ARCHIVEBOX_USER:
+5 -1
View File
@@ -1801,7 +1801,11 @@ class HealthCheckView(View):
"""
Handle a GET request
"""
return HttpResponse("OK", content_type="text/plain", status=200)
response = HttpResponse("OK", content_type="text/plain", status=200)
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Expose-Headers"] = "X-ArchiveBox-Health"
response["X-ArchiveBox-Health"] = "OK"
return response
@render_with_table_view
+21 -14
View File
@@ -232,12 +232,21 @@ def iter_query_search_ids(
backends = get_available_backends()
configured_backend = normalize_search_backend_name(config.SEARCH_BACKEND_ENGINE)
fallback_enabled = search_mode_base == "deep" and (not forced_backend or forced_backend == configured_backend == "sonic")
if forced_backend:
if forced_backend not in backends:
raise RuntimeError(
f'Search backend "{forced_backend}" not found. Available backends: {list(backends) or "none"}',
)
backend_names = [forced_backend]
backend_names = (
[
forced_backend,
*(name for name in backends if name not in {forced_backend, "ripgrep"}),
*(["ripgrep"] if "ripgrep" in backends and forced_backend != "ripgrep" else []),
]
if fallback_enabled
else [forced_backend]
)
elif search_mode_base == "deep":
backend_names = [
*([configured_backend] if configured_backend in backends and configured_backend != "ripgrep" else []),
@@ -252,18 +261,6 @@ def iter_query_search_ids(
get_backend()
return
if "sonic" in backend_names:
import sys
from contextlib import redirect_stdout
from archivebox.core.takeover_util import ensure_daemon_stack
# Search commands can produce structured stdout such as --csv/--json.
# Daemon bootstrap progress is diagnostic output and must not corrupt
# that user-facing data stream.
with redirect_stdout(sys.stderr):
ensure_daemon_stack(reason="search query")
errors: list[Exception] = []
successful_backends = 0
seen: set[str] = set()
@@ -271,6 +268,16 @@ def iter_query_search_ids(
for backend_name in backend_names:
backend = backends[backend_name]
try:
if backend_name == "sonic":
import sys
from contextlib import redirect_stdout
from archivebox.core.takeover_util import ensure_daemon_stack
# Search commands can produce structured stdout such as
# --csv/--json. Keep daemon diagnostics on stderr.
with redirect_stdout(sys.stderr):
ensure_daemon_stack(reason="search query")
with search_backend_env(config=config):
if backend_name == "ripgrep":
ids = backend.iter_search(query, search_mode=search_mode_base)
@@ -286,7 +293,7 @@ def iter_query_search_ids(
successful_backends += 1
except Exception as err:
errors.append(err)
if search_mode_base != "deep" or forced_backend:
if not fallback_enabled:
raise
except Exception as err:
stderr()
-3
View File
@@ -538,7 +538,4 @@ async def _mark_binary_queued(binary) -> None:
def _persisted_overrides_for_request(request: BinaryRequestEvent | None) -> dict[str, Any]:
if request is None:
return {}
raw_overrides = request.extra_context.get("raw_overrides")
if isinstance(raw_overrides, Mapping):
return dict(raw_overrides)
return dict(request.overrides or {})
+2 -15
View File
@@ -46,7 +46,6 @@ from abx_dl.orchestrator import (
setup_services as setup_abx_services,
)
from abx_dl.services.process_service import ProcessService as HookProcessService
from abx_dl.services.binary_service import split_abxpkg_binary_request_overrides
from abx_dl.services.snapshot_service import SnapshotService as HookSnapshotService
from abx_dl.cli import LiveBusUI
from abxbus import BaseEvent
@@ -873,13 +872,10 @@ class CrawlRunner:
snapshot_phase_timeout=crawl_setup_phase_timeout,
snapshot_cleanup_phase_timeout=crawl_setup_phase_timeout,
crawl_cleanup_phase_timeout=crawl_setup_phase_timeout,
persist_derived=False,
auto_install=True,
emit_jsonl=False,
abort_requested=self.crawl_is_cancelled,
MachineService=None,
PluginBinariesService=None,
BinaryCacheService=None,
BinaryService=None,
ProcessService=None,
ArchiveResultService=None,
@@ -1338,7 +1334,7 @@ async def _run_binary(binary_id: str) -> None:
process_service = PersistedProcessService(bus)
binary_process_service = ArchiveBoxBinaryService(bus)
BinaryCacheService(bus, backend=ArchiveBoxDBBinaryCacheBackend())
BinaryService(bus)
BinaryService(bus, lib_dir=Path(config["ABXPKG_LIB_DIR"]))
TagService(bus)
ArchiveResultService(bus)
MachineService(bus)
@@ -1350,29 +1346,24 @@ async def _run_binary(binary_id: str) -> None:
crawl_start_enabled=False,
snapshot_cleanup_enabled=False,
crawl_cleanup_enabled=False,
persist_derived=False,
auto_install=True,
emit_jsonl=False,
BinaryCacheService=None,
BinaryService=None,
)
await _emit_machine_config(bus, config=config, derived_config=derived_config)
native_overrides, override_extra_context = split_abxpkg_binary_request_overrides(binary.overrides or None)
try:
await bus.emit(
BinaryRequestEvent(
name=binary.name,
binproviders=binary.binproviders,
overrides=native_overrides or None,
overrides=binary.overrides or None,
extra_context={
"plugin_name": "archivebox",
"hook_name": "archivebox_binary_run",
"output_dir": str(binary.output_dir),
"binary_id": str(binary.id),
"machine_id": str(binary.machine_id),
**override_extra_context,
},
),
).now(first_result=True)
@@ -1952,8 +1943,6 @@ async def _run_install(plugin_names: list[str] | None = None) -> None:
derived_config_overrides=derived_config,
emit_jsonl=False,
bus=bus,
MachineService=None,
BinaryCacheService=None,
BinaryService=None,
)
finally:
@@ -1962,8 +1951,6 @@ async def _run_install(plugin_names: list[str] | None = None) -> None:
finally:
await bus.destroy(clear=False)
bus_destroyed = True
if live_ui is not None:
live_ui.print_summary(output_dir=output_dir, archive_results=[])
finally:
if not bus_destroyed:
await bus.destroy(clear=False)
+7 -2
View File
@@ -73,8 +73,13 @@
<br/>
To create a new admin user or reset a password, run:<br/>
<pre>
cd data/ # run commands inside your data folder
archivebox manage createsuperuser &lt;username&gt;
# Docker Compose
docker compose exec archivebox archivebox manage createsuperuser --username &lt;username&gt;
docker compose exec archivebox archivebox manage changepassword &lt;username&gt;
# Native installs (run inside your data folder)
cd data/
archivebox manage createsuperuser --username &lt;username&gt;
archivebox manage changepassword &lt;username&gt;
</pre>
<pre>
+4 -4
View File
@@ -1577,7 +1577,7 @@
<span class="tag-pill permission-pill">{{ snapshot_permissions_icon }} {{ snapshot_permissions }}</span>
</span>
{% endif %}
<a href="#" class="header-toggle header-toggle-trigger">▾</a>
<a href="#" class="header-toggle">▾</a>
</div>
</div>
<div class="header-col header-meta">
@@ -2120,7 +2120,8 @@
hideSnapshotHeader()
}
}
function handleSnapshotHeaderToggle() {
function handleSnapshotHeaderToggle(event) {
event.preventDefault()
if (jQuery('.header-toggle').text().includes('▾')) {
hideSnapshotHeader()
} else {
@@ -2129,8 +2130,7 @@
return true
}
// hide header when collapse icon is clicked
jQuery('.header-toggle').on('click', handleSnapshotHeaderToggle)
// Hide or show the header once when its title row or collapse icon is clicked.
jQuery('.header-toggle-trigger').on('click', handleSnapshotHeaderToggle)
// check URL for hash e.g. #git and load relevant preview
+9 -7
View File
@@ -262,21 +262,23 @@
document.getElementById('archivebox-setup-wildcard-help').hidden = dnsMode === 'localhost';
}
function probeUrl(url, generation) {
function probeUrl(url, generation, requireArchiveBoxHealth) {
var controller = new AbortController();
var timeout = window.setTimeout(function() { controller.abort(); }, 5000);
var target = new URL(url);
target.searchParams.set('archivebox_setup_probe', String(generation));
return fetch(target.toString(), {
method: 'GET',
mode: 'no-cors',
mode: requireArchiveBoxHealth ? 'cors' : 'no-cors',
credentials: 'omit',
cache: 'no-store',
redirect: 'follow',
signal: controller.signal,
}).then(function() {
}).then(function(response) {
window.clearTimeout(timeout);
return true;
return requireArchiveBoxHealth
? response.ok && response.headers.get('X-ArchiveBox-Health') === 'OK'
: true;
}).catch(function() {
window.clearTimeout(timeout);
return false;
@@ -378,9 +380,9 @@
admin: probeUrl(preview.adminUrl, generation),
api: probeUrl(preview.apiUrl, generation),
index: probeUrl(preview.indexUrl, generation),
web: probeUrl(preview.webHealthUrl, generation),
snapshot: probeUrl(preview.snapshotHealthUrl, generation),
original: probeUrl(preview.originalHealthUrl, generation),
web: probeUrl(preview.webHealthUrl, generation, true),
snapshot: probeUrl(preview.snapshotHealthUrl, generation, true),
original: probeUrl(preview.originalHealthUrl, generation, true),
};
Promise.all(Object.keys(checks).map(function(key) { return checks[key].then(function(ok) { return [key, ok]; }); })).then(function(entries) {
if (generation !== probeGeneration || preview !== currentPreview) return;
+1
View File
@@ -990,6 +990,7 @@ def seed_0_8_data(db_path: Path) -> dict[str, list[dict]]:
"urls": uri,
"max_depth": max_depth,
"label": "",
"status": "queued",
},
)
+7 -13
View File
@@ -39,23 +39,22 @@ def _run_real_binary_state_machine(data_dir: Path, *, name: str, binproviders: s
)
def test_binary_request_preserves_raw_overrides_in_db_while_using_native_event():
def test_binary_request_preserves_native_overrides_in_db():
from abxpkg.binary_service import BinaryCacheService, BinaryEvent, BinaryRequestEvent, BinaryService
from abx_dl.orchestrator import create_bus
from archivebox.services.binary_service import ArchiveBoxDBBinaryCacheBackend
machine = Machine.current()
raw_overrides = {
overrides = {
"pip": {
"install_args": ["imagesize>=2.0.0"],
"module_name": "imagesize",
},
}
binary = Binary.objects.create(
machine=machine,
name="python3",
binproviders="env,pip",
overrides=raw_overrides,
overrides=overrides,
status=Binary.StatusChoices.QUEUED,
retry_at=timezone.now(),
)
@@ -63,7 +62,7 @@ def test_binary_request_preserves_raw_overrides_in_db_while_using_native_event()
binary.refresh_from_db()
assert binary.status == Binary.StatusChoices.INSTALLED
assert Path(binary.abspath).resolve() == Path(sys.executable).resolve()
bus = create_bus(name=f"test_binary_raw_overrides_{uuid.uuid4().hex[:8]}")
bus = create_bus(name=f"test_binary_native_overrides_{uuid.uuid4().hex[:8]}")
BinaryCacheService(bus, backend=ArchiveBoxDBBinaryCacheBackend())
BinaryService(bus)
binary_events: list[BinaryEvent] = []
@@ -78,11 +77,7 @@ def test_binary_request_preserves_raw_overrides_in_db_while_using_native_event()
BinaryRequestEvent(
name="python3",
binproviders="env,pip",
overrides={"pip": {"install_args": ["imagesize>=2.0.0"]}},
extra_context={
"raw_overrides": raw_overrides,
"provider_metadata": {"pip": {"module_name": "imagesize"}},
},
overrides=overrides,
),
).now()
await bus.wait_until_idle()
@@ -91,10 +86,9 @@ def test_binary_request_preserves_raw_overrides_in_db_while_using_native_event()
binary.refresh_from_db()
assert binary.status == Binary.StatusChoices.INSTALLED
assert binary.overrides == raw_overrides
assert binary.overrides == overrides
assert binary_events
assert binary_events[-1].overrides == {"pip": {"install_args": ["imagesize>=2.0.0"]}}
assert binary_events[-1].extra_context["raw_overrides"] == raw_overrides
assert binary_events[-1].overrides == overrides
def test_binary_request_installs_env_binary_and_recovers_stale_cache(initialized_archive, tmp_path):
+1
View File
@@ -204,6 +204,7 @@ def test_install_updates_binary_table(initialized_archive):
output = result.stdout + result.stderr
assert result.returncode == 0, output
assert "0 succeeded, 0 noresult, 0 failed, 0 skipped" not in output
with use_archivebox_db(initialized_archive):
binary_counts = {
+9 -9
View File
@@ -1660,7 +1660,7 @@ class TestRecoverOrchestratorState:
@pytest.mark.django_db(transaction=True)
@pytest.mark.timeout(300)
@pytest.mark.parametrize("chrome_isolation", ["crawl", "snapshot"])
def test_resume_queued_chrome_wait_reruns_background_prerequisites(
def test_resume_queued_chrome_navigate_reruns_background_prerequisites(
self,
initialized_archive,
recursive_test_site,
@@ -1700,21 +1700,21 @@ class TestRecoverOrchestratorState:
)
assert list_process.returncode == 0, list_process.stderr or list_process.stdout
chrome_results = parse_jsonl_output(list_process.stdout)
wait_record = next(record for record in chrome_results if record["hook_name"] == "on_Snapshot__11_chrome_wait")
snapshot_id = wait_record["snapshot_id"]
navigate_record = next(record for record in chrome_results if record["hook_name"] == "on_Snapshot__30_chrome_navigate")
snapshot_id = navigate_record["snapshot_id"]
with use_archivebox_db(initialized_archive):
tab_result = ArchiveResult.objects.get(
snapshot_id=snapshot_id,
plugin="chrome",
hook_name="on_Snapshot__10_chrome_tab.daemon.bg",
hook_name="on_Snapshot__01_chrome_tab.daemon.bg",
)
first_tab_process_id = tab_result.process_id
assert first_tab_process_id is not None
update_process = run_archivebox_cmd(
["archiveresult", "update", "--status=queued"],
stdin=next(line for line in list_process.stdout.splitlines() if wait_record["id"] in line) + "\n",
stdin=next(line for line in list_process.stdout.splitlines() if navigate_record["id"] in line) + "\n",
cwd=initialized_archive,
env=env,
timeout=60,
@@ -1741,19 +1741,19 @@ class TestRecoverOrchestratorState:
cleanup_process_group(run_process.pid)
with use_archivebox_db(initialized_archive):
wait_result = ArchiveResult.objects.get(
navigate_result = ArchiveResult.objects.get(
snapshot_id=snapshot_id,
plugin="chrome",
hook_name="on_Snapshot__11_chrome_wait",
hook_name="on_Snapshot__30_chrome_navigate",
)
tab_result = ArchiveResult.objects.get(
snapshot_id=snapshot_id,
plugin="chrome",
hook_name="on_Snapshot__10_chrome_tab.daemon.bg",
hook_name="on_Snapshot__01_chrome_tab.daemon.bg",
)
assert run_process.returncode == 0
assert wait_result.status == ArchiveResult.StatusChoices.SUCCEEDED
assert navigate_result.status == ArchiveResult.StatusChoices.SUCCEEDED
assert tab_result.process_id is not None
assert tab_result.process_id != first_tab_process_id
+4 -2
View File
@@ -719,7 +719,8 @@ def test_run_pending_crawls_processes_queued_crawl_and_real_binary(tmp_path):
assert Snapshot.objects.filter(crawl=crawl, status=Snapshot.StatusChoices.SEALED).count() == 1
assert binary.status == Binary.StatusChoices.INSTALLED
assert binary.retry_at is None
assert Path(binary.abspath).is_symlink()
assert Path(binary.abspath).is_file()
assert binary.version
assert binary.binprovider == "env"
@@ -877,7 +878,8 @@ def test_run_pending_crawls_resolves_real_binary_through_abxpkg(tmp_path):
assert result == 0
assert binary.status == Binary.StatusChoices.INSTALLED
assert binary.retry_at is None
assert Path(binary.abspath).is_symlink()
assert Path(binary.abspath).is_file()
assert binary.version
assert binary.binprovider == "env"
+2 -2
View File
@@ -70,7 +70,7 @@ class TestBackgroundHookDetection:
assert foreground_hooks
assert all(".bg." in hook.name for hook in background_hooks)
assert all(".bg." not in hook.name for hook in foreground_hooks)
assert any(hook.name == "on_Snapshot__10_chrome_tab.daemon.bg.js" for hook in background_hooks)
assert any(hook.name == "on_Snapshot__01_chrome_tab.daemon.bg.js" for hook in background_hooks)
assert any(hook.name == "on_Snapshot__06_wget.finite.bg.py" for hook in background_hooks)
assert any(hook.name == "on_Snapshot__93_hashes.py" for hook in foreground_hooks)
@@ -227,7 +227,7 @@ class TestHookDiscovery:
hooks = discover_hooks("Snapshot", filter_disabled=False)
hook_names = [h.name for h in hooks]
assert "on_Snapshot__10_chrome_tab.daemon.bg.js" in hook_names
assert "on_Snapshot__01_chrome_tab.daemon.bg.js" in hook_names
assert "on_Snapshot__21_consolelog.daemon.bg.js" in hook_names
assert "on_Snapshot__06_wget.finite.bg.py" in hook_names
assert all(hook.is_file() for hook in hooks)
+3 -2
View File
@@ -667,11 +667,12 @@ def test_crawl_data_preserved_after_migration(migration_08_data):
# Check each crawl's data is preserved
for crawl in original_data["crawls"]:
cursor.execute("SELECT urls, label FROM crawls_crawl WHERE id = ?", (crawl["id"],))
cursor.execute("SELECT urls, label, status FROM crawls_crawl WHERE id = ?", (crawl["id"],))
row = cursor.fetchone()
assert row is not None, f"Crawl {crawl['id']} not found after migration"
assert row[0] == crawl["urls"], f"URLs mismatch for crawl {crawl['id']}"
assert row[1] == crawl["label"], f"Label mismatch for crawl {crawl['id']}"
assert row[2] == crawl["status"], f"Status mismatch for crawl {crawl['id']}"
conn.close()
@@ -1061,7 +1062,7 @@ def test_each_declared_filesystem_hop_preserves_outputs(migration_08_data, fs_ve
(source_dir / "unknown-empty-dir" / "nested").mkdir(parents=True)
expected_tree = filesystem_manifest(source_dir)
result = run_archivebox_migration_cmd(work_dir, ["update"], timeout=180)
result = run_archivebox_migration_cmd(work_dir, ["update", "--migrate-only"], timeout=180)
assert result.returncode == 0, result.stderr
migrated_dir = source_dir.resolve()
+28 -3
View File
@@ -45,6 +45,22 @@ def test_root_preserves_existing_non_root_data_dir_owner():
) == (1001, 1002)
def test_root_uses_owner_primary_group_for_mixed_user_root_data_dir():
assert select_archivebox_user(
running_uid=0,
running_gid=0,
effective_uid=0,
effective_gid=0,
sudo_uid=1001,
sudo_gid=1002,
data_dir_uid=1001,
data_dir_gid=0,
data_dir_owner_gid=1002,
account_uid=911,
account_gid=911,
) == (1001, 1002)
def test_root_uses_archivebox_account_for_unknown_data_dir_owner():
assert select_archivebox_user(
running_uid=0,
@@ -105,14 +121,23 @@ def test_root_hands_off_root_or_archivebox_owned_collection_boundaries():
)
def test_root_init_hands_off_only_an_empty_data_dir(tmp_path):
def test_root_setup_commands_hand_off_only_an_empty_data_dir(tmp_path):
from archivebox.config.permissions import root_data_dir_handoff_paths
assert root_data_dir_handoff_paths(tmp_path, ["archivebox", "init"]) == (tmp_path,)
setup_commands = (
["archivebox", "init"],
["archivebox", "install"],
["archivebox", "server", "--init"],
["archivebox", "server", "--quick-init"],
["archivebox", "add", "--init", "https://example.com"],
)
for argv in setup_commands:
assert root_data_dir_handoff_paths(tmp_path, argv) == (tmp_path,)
unrelated = tmp_path / "unrelated.txt"
unrelated.write_text("keep root ownership")
assert root_data_dir_handoff_paths(tmp_path, ["archivebox", "init"]) == ()
for argv in setup_commands:
assert root_data_dir_handoff_paths(tmp_path, argv) == ()
def test_existing_collection_handoff_is_bounded_to_known_top_level_paths(tmp_path):
+10 -5
View File
@@ -1,6 +1,7 @@
import os
import shutil
import subprocess
import tomllib
from pathlib import Path
@@ -34,14 +35,16 @@ def test_setup_script_gives_archivebox_user_ownership_of_runtime_parent_dirs():
def test_setup_script_bootstraps_locked_abxpkg_version():
script = SETUP_SCRIPT.read_text()
lock = tomllib.loads((SETUP_SCRIPT.parents[1] / "uv.lock").read_text())
locked_abxpkg_version = next(package["version"] for package in lock["package"] if package["name"] == "abxpkg")
prepare_function = script.partition("prepare_abxpkg_environment() {")[2].partition("\n}")[0]
install_function = script.partition("install_archivebox_with_uv() {")[2].partition("\n}")[0]
assert 'ABXPKG_PACKAGE="${ABXPKG_PACKAGE:-abxpkg==1.12.61}"' in script
assert 'ARCHIVEBOX_PACKAGE="${ARCHIVEBOX_PACKAGE:-archivebox}"' in script
assert '--prerelease allow --upgrade "$ARCHIVEBOX_PACKAGE"' in install_function
assert f'ABXPKG_PACKAGE="${{ABXPKG_PACKAGE:-abxpkg=={locked_abxpkg_version}}}"' in script
assert 'ARCHIVEBOX_PACKAGE="${ARCHIVEBOX_PACKAGE:-archivebox>=0.9.0rc0,<0.10}"' in script
assert '--prerelease explicit --upgrade "$ARCHIVEBOX_PACKAGE"' in install_function
assert "fix_root_install_ownership" in prepare_function
assert "resolve_setup_binary git env,brew,apt true" in install_function
assert "resolve_setup_binary git env,brew,apt true" not in install_function
def test_setup_script_preserves_collection_and_compose_ownership_boundaries():
@@ -59,12 +62,14 @@ def test_setup_script_prints_root_safe_runtime_commands():
assert 'echo " cd $ARCHIVEBOX_DATA_DIR' in script
assert 'FOLLOWUP_SUDO="sudo "' in script
assert "${FOLLOWUP_SUDO}docker compose run --rm archivebox add" in script
assert "${FOLLOWUP_SUDO}docker compose exec archivebox archivebox add" in script
assert "${FOLLOWUP_SUDO}archivebox add" in script
assert "at ~/archivebox/data" not in script
assert "Server started on http://0.0.0.0" not in script
assert "server --daemonize 0.0.0.0:8000" in script
assert 'nohup "$ARCHIVEBOX_BINARY" server' not in script
assert "pkill -f 'archivebox server'" in script
assert "pkill -f archivebox " not in script
assert '"$DOCKER_BINARY" rm -f archivebox' in script
assert "--connect-timeout 1 --max-time 2" in script
@@ -12,6 +12,7 @@ from archivebox.config.common import get_config
from archivebox.core.middleware import AdminCookieIsolationMiddleware
from archivebox.core.setup_wizard import get_base_url_mismatch_context, get_setup_wizard_context
from archivebox.core.templatetags.core_tags import system_warnings_banner
from archivebox.core.views import HealthCheckView
STATIC_DIR = Path(__file__).parents[1] / "templates" / "static"
SETUP_WIZARD_CSS = (STATIC_DIR / "setup_wizard.css").read_text()
@@ -213,13 +214,28 @@ def test_setup_wizard_assets_enforce_selection_and_access_requirements():
assert "Waiting for a matching browser URL and valid setup options" in SETUP_WIZARD_JS
assert "Finish the selected DNS, ingress, and TLS setup" in SETUP_WIZARD_JS
for target in ("adminUrl", "apiUrl", "indexUrl", "webHealthUrl", "snapshotHealthUrl", "originalHealthUrl"):
for target in ("adminUrl", "apiUrl", "indexUrl"):
assert f"probeUrl(preview.{target}" in SETUP_WIZARD_JS
assert "wildcardHealthUrl" not in SETUP_WIZARD_JS
assert "results.wildcard" not in SETUP_WIZARD_JS
assert "var coreReachable = results.admin && results.api && results.index && results.web && results.snapshot;" in SETUP_WIZARD_JS
assert "probeUrl(webOrigin + '/web/https://example.com'" not in SETUP_WIZARD_JS
assert "credentials: 'omit'" in SETUP_WIZARD_JS
assert "function probeUrl(url, generation, requireArchiveBoxHealth)" in SETUP_WIZARD_JS
assert "mode: requireArchiveBoxHealth ? 'cors' : 'no-cors'" in SETUP_WIZARD_JS
assert "response.ok && response.headers.get('X-ArchiveBox-Health') === 'OK'" in SETUP_WIZARD_JS
for target in ("webHealthUrl", "snapshotHealthUrl", "originalHealthUrl"):
assert f"probeUrl(preview.{target}, generation, true)" in SETUP_WIZARD_JS
def test_health_check_is_identifiable_across_ingress_origins():
response = HealthCheckView.as_view()(RequestFactory().get("/health/"))
assert response.status_code == 200
assert response.content == b"OK"
assert response["Access-Control-Allow-Origin"] == "*"
assert response["Access-Control-Expose-Headers"] == "X-ArchiveBox-Health"
assert response["X-ArchiveBox-Health"] == "OK"
@pytest.mark.parametrize(
@@ -696,6 +696,9 @@ class TestSnapshotProgressStats:
assert "index.jsonl?preview=1" in rendered
assert "height: calc(100vh - 210px)" not in rendered
assert "iframe-large" not in rendered
assert 'class="header-toggle header-toggle-trigger"' not in rendered
assert "event.preventDefault()" in rendered
assert rendered.count(".on('click', handleSnapshotHeaderToggle)") == 1
class TestAdminSnapshotListView:
@@ -215,6 +215,7 @@ def _login_admin_session_over_http(port: int, host: str) -> requests.Session:
timeout=10,
)
assert login_page.status_code == 200, login_page.text[:500]
assert "docker compose exec archivebox archivebox manage createsuperuser" in login_page.text
csrf_match = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', login_page.text)
assert csrf_match, login_page.text[:500]
login_response = session.post(
+1 -1
View File
@@ -21,6 +21,6 @@ cd "$REPO_DIR"
./bin/build_docker.sh
echo "[√] Done. Install the built package by running:"
echo " pip install archivebox"
echo " uv tool install --force dist/archivebox-*.whl"
echo " # or"
echo " brew tap archivebox/archivebox && brew install archivebox"
+17 -12
View File
@@ -78,10 +78,10 @@ fi
ARCHIVEBOX_BRANCH="${ARCHIVEBOX_BRANCH:-dev}"
ARCHIVEBOX_IMAGE="${ARCHIVEBOX_IMAGE:-archivebox/archivebox:dev}"
ARCHIVEBOX_PYTHON="${ARCHIVEBOX_PYTHON:-3.13}"
ARCHIVEBOX_PACKAGE="${ARCHIVEBOX_PACKAGE:-archivebox}"
ARCHIVEBOX_PACKAGE="${ARCHIVEBOX_PACKAGE:-archivebox>=0.9.0rc0,<0.10}"
ARCHIVEBOX_PLATFORM="${ARCHIVEBOX_PLATFORM:-}"
ARCHIVEBOX_COMPOSE_URL="${ARCHIVEBOX_COMPOSE_URL:-https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/${ARCHIVEBOX_BRANCH}/docker-compose.yml}"
ABXPKG_PACKAGE="${ABXPKG_PACKAGE:-abxpkg==1.12.61}"
ABXPKG_PACKAGE="${ABXPKG_PACKAGE:-abxpkg==1.12.91}"
ABXPKG_LIB_DIR="${ABXPKG_LIB_DIR:-$HOME/.cache/archivebox/setup-abxpkg}"
ARCHIVEBOX_HOME_DIR="$HOME/archivebox"
ARCHIVEBOX_DATA_DIR="$ARCHIVEBOX_HOME_DIR/data"
@@ -299,10 +299,9 @@ resolve_setup_curl() {
}
install_archivebox_with_uv() {
resolve_setup_binary git env,brew,apt true
echo
echo "[+] Installing ArchiveBox python tool using uv from $ARCHIVEBOX_PACKAGE..."
"$UV_BINARY" --no-config tool install --python "$ARCHIVEBOX_PYTHON" --prerelease allow --upgrade "$ARCHIVEBOX_PACKAGE"
"$UV_BINARY" --no-config tool install --python "$ARCHIVEBOX_PYTHON" --prerelease explicit --upgrade "$ARCHIVEBOX_PACKAGE"
uv_tool_bin_dir="$("$UV_BINARY" --no-config tool dir --bin)"
ARCHIVEBOX_BINARY="$uv_tool_bin_dir/archivebox"
@@ -361,7 +360,12 @@ if [ "$DOCKER_IMAGE_READY" = "true" ] && "$DOCKER_BINARY" compose version > /dev
cd "$ARCHIVEBOX_HOME_DIR"
migrate_legacy_collection_dir
if [ ! -f docker-compose.yml ]; then
"$CURL_BINARY" -fsSL "$ARCHIVEBOX_COMPOSE_URL" > docker-compose.yml
compose_download="$(mktemp "$ARCHIVEBOX_HOME_DIR/.docker-compose.yml.XXXXXX")"
if ! "$CURL_BINARY" -fsSL "$ARCHIVEBOX_COMPOSE_URL" -o "$compose_download"; then
rm -f "$compose_download"
exit 1
fi
mv "$compose_download" docker-compose.yml
if [ "$RUNNING_AS_ROOT" = "true" ]; then
chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" docker-compose.yml
fi
@@ -381,10 +385,10 @@ if [ "$DOCKER_IMAGE_READY" = "true" ] && "$DOCKER_BINARY" compose version > /dev
echo " ${FOLLOWUP_SUDO}docker compose down"
echo " ${FOLLOWUP_SUDO}env ARCHIVEBOX_IMAGE=$ARCHIVEBOX_IMAGE docker compose pull"
echo " ${FOLLOWUP_SUDO}docker compose up"
echo " ${FOLLOWUP_SUDO}docker compose run --rm archivebox manage createsuperuser"
echo " ${FOLLOWUP_SUDO}docker compose run --rm archivebox add 'https://example.com'"
echo " ${FOLLOWUP_SUDO}docker compose run --rm archivebox list"
echo " ${FOLLOWUP_SUDO}docker compose run --rm archivebox help"
echo " ${FOLLOWUP_SUDO}docker compose exec archivebox archivebox manage createsuperuser"
echo " ${FOLLOWUP_SUDO}docker compose exec archivebox archivebox add 'https://example.com'"
echo " ${FOLLOWUP_SUDO}docker compose exec archivebox archivebox list"
echo " ${FOLLOWUP_SUDO}docker compose exec archivebox archivebox help"
exit 0
elif [ "$DOCKER_IMAGE_READY" = "true" ]; then
resolve_setup_curl
@@ -419,7 +423,8 @@ echo
echo "[!] It's highly recommended to use ArchiveBox with Docker, but Docker is unavailable (not installed, not running, or not accessible to this user)."
echo
echo " ⚠️ If you want to use Docker, press [Ctrl-C] to cancel now. ⚠️"
echo " Check 'docker info'. Start Docker Desktop on macOS, or rerun this script with sudo on Ubuntu if Docker reports a socket permission error."
echo " Check 'docker info'. Start Docker Desktop on macOS, or fix Docker socket access and rerun as your normal Ubuntu user."
echo " Rerunning with sudo intentionally creates a system-owned deployment under /var/lib/archivebox/archivebox instead of ~/archivebox."
echo " Install Docker if needed: https://docs.docker.com/get-docker/"
echo
echo "Otherwise, install will continue with uv in 12s... (press [Ctrl+C] to cancel)"
@@ -469,10 +474,10 @@ open_archivebox
echo
echo "[√] Server started on http://127.0.0.1:8000 and data directory initialized in $ARCHIVEBOX_DATA_DIR. Usage:"
echo " cd $ARCHIVEBOX_DATA_DIR # see your data dir"
echo " ${FOLLOWUP_SUDO}archivebox server --quick-init 0.0.0.0:8000 # start server process"
echo " ${FOLLOWUP_SUDO}archivebox server 0.0.0.0:8000 # start server process"
echo " ${FOLLOWUP_SUDO}archivebox manage createsuperuser # add an admin user+pass"
echo " ps aux | grep archivebox # see server process pid"
echo " ${FOLLOWUP_SUDO}pkill -f archivebox # stop the server"
echo " ${FOLLOWUP_SUDO}pkill -f 'archivebox server' # stop the server"
echo " curl -fsSL 'https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/bin/setup.sh' | ${FOLLOWUP_SUDO}bash # update versions"
echo " ${FOLLOWUP_SUDO}archivebox add 'https://example.com'" # archive a new URL
echo " ${FOLLOWUP_SUDO}archivebox list # see URLs archived"
+6 -6
View File
@@ -2,13 +2,13 @@
# mkdir -p ~/archivebox/data && cd ~/archivebox
# curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml
# docker compose pull
# docker compose run --rm archivebox init
# docker compose run --rm archivebox install
# docker compose up -d && open 'http://admin.archivebox.localhost:8000'
# docker compose up -d --wait
# docker compose exec archivebox archivebox manage createsuperuser
# open 'http://admin.archivebox.localhost:8000'
# docker compose run --rm archivebox add --depth=1 'https://news.ycombinator.com'
# docker compose run --rm -T archivebox add < ~/Downloads/bookmarks.txt
# docker compose run --rm archivebox help
# docker compose exec archivebox archivebox add --depth=1 'https://news.ycombinator.com'
# docker compose exec -T archivebox archivebox add < ~/Downloads/bookmarks.txt
# docker compose exec archivebox archivebox help
# Documentation:
# https://github.com/ArchiveBox/ArchiveBox/wiki/Docker#docker-compose
+2 -2
View File
@@ -476,7 +476,7 @@ URL users are redirected to after logging out. The default `/` keeps users on Ar
### LDAP Settings
*Options for LDAP / Active Directory authentication via [django-auth-ldap](https://github.com/django-auth-ldap/django-auth-ldap). Requires `uv tool install --python 3.13 --prerelease allow --upgrade 'archivebox[ldap]'` (which also pulls in the system `libldap` / `libsasl` headers).*
*Options for LDAP / Active Directory authentication via [django-auth-ldap](https://github.com/django-auth-ldap/django-auth-ldap). Requires `uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox[ldap]>=0.9.0rc0,<0.10'` (which also pulls in the system `libldap` / `libsasl` headers).*
---
#### `LDAP_ENABLED`
@@ -485,7 +485,7 @@ URL users are redirected to after logging out. The default `/` keeps users on Ar
Master switch for LDAP authentication. When `True`, ArchiveBox loads the `django-auth-ldap` backend and validates that `LDAP_SERVER_URI`, `LDAP_BIND_DN`, `LDAP_BIND_PASSWORD`, and `LDAP_USER_BASE` are all set — startup fails fast otherwise.
```bash
uv tool install --python 3.13 --prerelease allow --upgrade 'archivebox[ldap]'
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox[ldap]>=0.9.0rc0,<0.10'
```
Then set these configuration values:
+20 -21
View File
@@ -63,18 +63,16 @@ mkdir -p ~/archivebox/data && cd ~/archivebox
curl -fsSL 'https://docker-compose.archivebox.io' > docker-compose.yml
# (shortcut for getting https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/docker-compose.yml)
# pull the current image, initialize the collection, install runtime dependencies,
# then create an admin user for the Web UI (or set ADMIN_USERNAME/ADMIN_PASSWORD env vars)
# pull and start the current image, then create an admin user for the Web UI
# (the server initializes a new collection automatically)
docker compose pull
docker compose run --rm archivebox init
docker compose run --rm archivebox install
docker compose run --rm archivebox manage createsuperuser
docker compose up -d
docker compose up -d --wait
docker compose exec archivebox archivebox manage createsuperuser
```
ArchiveBox installs and enables both ripgrep and [Sonic](https://github.com/valeriansaliou/sonic). Sonic is selected by default in the UI, while ripgrep remains available as the fallback. To select ripgrep explicitly:
```bash
docker compose run --rm archivebox config --set SEARCH_BACKEND_ENGINE=ripgrep
docker compose exec archivebox archivebox config --set SEARCH_BACKEND_ENGINE=ripgrep
```
<br/>
@@ -87,40 +85,40 @@ See the wiki page on [Upgrading or Merging Archives: Upgrading with Docker Compo
### Usage
You can use `docker compose run --rm archivebox [subcommand]` just like the non-Docker `archivebox [subcommand]` CLI.
With the server running from the setup steps above, use `docker compose exec archivebox archivebox [subcommand]` just like the non-Docker `archivebox [subcommand]` CLI. If the server is stopped, use `docker compose run --rm archivebox [subcommand]` instead.
First, make sure you're `cd`'ed into the same folder as your `docker-compose.yml` file (e.g. `~/archivebox`):
```bash
docker compose run --rm archivebox help
docker compose exec archivebox archivebox help
```
To add an individual URL, pass it in as an arg or via stdin:
```bash
docker compose run --rm archivebox add 'https://example.com'
docker compose exec archivebox archivebox add 'https://example.com'
# OR
echo 'https://example.com' | docker compose run --rm -T archivebox add
echo 'https://example.com' | docker compose exec -T archivebox archivebox add
```
To add multiple URLs at once, pipe them in via stdin, or place them in a file inside `./data/sources` so that ArchiveBox can access it from within the container:
```bash
# pipe URLs in from a file outside Docker
docker compose run --rm -T archivebox add < ~/Downloads/example_urls.txt
docker compose exec -T archivebox archivebox add < ~/Downloads/example_urls.txt
# OR ingest URLs from a file mounted inside Docker
docker compose run --rm archivebox add --depth=1 /data/sources/example_urls.txt
docker compose exec archivebox archivebox add --depth=1 /data/sources/example_urls.txt
# OR pipe in URLs from a remote source
curl 'https://example.com/some/rss/feed.xml' | docker compose run --rm -T archivebox add
docker compose run --rm archivebox add --depth=1 'https://example.com/some/rss/feed.xml'
curl 'https://example.com/some/rss/feed.xml' | docker compose exec -T archivebox archivebox add
docker compose exec archivebox archivebox add --depth=1 'https://example.com/some/rss/feed.xml'
```
The `--depth=1` flag tells ArchiveBox to look inside the provided source and archive all the URLs within:
```bash
# this archives just the RSS file itself (probably not what you want)
docker compose run --rm archivebox add 'https://example.com/some/feed.rss'
docker compose exec archivebox archivebox add 'https://example.com/some/feed.rss'
# this archives the RSS feed file + all the URLs mentioned inside of it
docker compose run --rm archivebox add --depth=1 'https://example.com/some/feed.rss'
docker compose exec archivebox archivebox add --depth=1 'https://example.com/some/feed.rss'
```
<br/>
@@ -146,12 +144,12 @@ ArchiveBox running with `docker compose` accepts all the same config options as
The recommended way configure ArchiveBox in Docker Compose is using `archivebox config --set ...` or by editing `ArchiveBox.conf`.
```bash
docker compose run --rm archivebox config --set TIMEOUT=120
docker compose exec archivebox archivebox config --set TIMEOUT=120
# OR edit ./data/ArchiveBox.conf and add this under its existing [ARCHIVING_CONFIG] section:
TIMEOUT=120
# plugin-specific options work the same way (see https://archivebox.github.io/abx-plugins/)
docker compose run --rm archivebox config --set YTDLP_MAX_SIZE=750m
docker compose exec archivebox archivebox config --set YTDLP_MAX_SIZE=750m
```
This will apply the config to all containers or archivebox instances that access the collection.
@@ -199,8 +197,9 @@ Fetch and run the ArchiveBox Docker image to create your initial archive.
docker pull archivebox/archivebox:dev
mkdir -p ~/archivebox/data && cd ~/archivebox/data
docker run -it -v $PWD:/data archivebox/archivebox:dev init
docker run -it -v $PWD:/data archivebox/archivebox:dev install
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev init
docker run --rm -it -v "$PWD:/data" archivebox/archivebox:dev manage createsuperuser
docker run -d --name archivebox -v "$PWD:/data" -p 8000:8000 archivebox/archivebox:dev
```
*(You can create a collection in any directory you want, `~/archivebox/data` is just used as an example here)*
+25 -14
View File
@@ -76,7 +76,9 @@ If you're on macOS or Ubuntu, there is an optional auto-setup script provided.
curl -fsSL 'https://get.archivebox.io' | bash
# shortcut to run https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/bin/setup.sh
```
The script uses Docker automatically when a working Docker Compose installation is available. Otherwise it shows the native `uv` install plan and pauses so you can cancel before continuing. It initializes the collection, installs ArchiveBox's runtime dependencies, and starts the server; create the first admin afterward with the command printed at the end.
The script uses Docker Compose when available, otherwise plain Docker when it can pull the released image. If Docker is unavailable, it shows the native `uv` install plan and pauses so you can cancel before continuing. It initializes the collection, installs ArchiveBox's runtime dependencies, and starts the server; create the first admin afterward with the command printed at the end.
Run it as your normal user unless you want a system-owned deployment. When run as root, the script creates the `archivebox` service user and places the collection under that account's home directory; it prints the exact path and follow-up commands when finished.
<img src="https://imgur.zervice.io/VMTzm0G.png" width="99%"/>
@@ -121,10 +123,13 @@ Make sure you have [Homebrew](https://brew.sh/) installed first.
brew tap archivebox/archivebox
brew trust archivebox/archivebox
brew install archivebox
```
# or use Homebrew to install uv, then install ArchiveBox with uv
Or use Homebrew to install `uv`, then install ArchiveBox with `uv`:
```bash
brew install uv
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
```
<img src="https://assets.ubuntu.com/v1/c5cb0f8e-picto-ubuntu.svg" width="30px" align="right"/>
@@ -151,17 +156,17 @@ dependencies such as Chromium, yt-dlp, SingleFile, and other plugin-managed
tools are installed by `archivebox install`; use `sudo archivebox install` only
if you want it to install missing system packages via apt.
Alternatively, install with `uv` or [Homebrew on Linux](https://docs.brew.sh/Homebrew-on-Linux):
Run Homebrew as your normal non-root user; Homebrew does not support `sudo brew` or root installs.
Alternatively, install with `uv`:
```bash
# uv
curl -LsSf https://astral.sh/uv/install.sh | sh
source "$HOME/.local/bin/env"
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
```
# or Homebrew, after installing Linuxbrew
Or install with [Homebrew on Linux](https://docs.brew.sh/Homebrew-on-Linux). Run Homebrew as your normal non-root user; Homebrew does not support `sudo brew` or root installs.
```bash
brew tap archivebox/archivebox
brew trust archivebox/archivebox
brew install archivebox
@@ -171,11 +176,11 @@ brew install archivebox
### 2. Install ArchiveBox using `uv`
If you are not using the apt package above, install ArchiveBox with `uv`.
If you are not using the apt or Homebrew packages above, install ArchiveBox with `uv`.
```bash
# get the dev version of ArchiveBox
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
# if the optional ldap extra must compile locally on Debian/Ubuntu, install its headers and retry
# sudo apt install build-essential libldap2-dev libsasl2-dev
@@ -208,7 +213,7 @@ archivebox help
### Troubleshooting
Make sure the `uv`-installed version of `archivebox` is available in your `$PATH`.
For `uv` installs, make sure the `uv`-installed version of `archivebox` is available in your `$PATH`.
```bash
uv tool list # show info about uv-installed tools
@@ -257,11 +262,17 @@ See our [[Usage]] Wiki documentation page for more examples.
### Next Steps: *Upgrading Archivebox to a new version*
Upgrade ArchiveBox itself first; `archivebox install` will then re-resolve compatible host binaries and update any managed runtime dependencies.
Stop any running ArchiveBox processes and back up the entire collection first. Upgrade ArchiveBox with the same package manager you originally used; `archivebox install` will then re-resolve compatible host binaries and update any managed runtime dependencies.
```bash
# back up the full collection before upgrading
cd ~/archivebox
tar -czf "archivebox-data-$(date +%s).tar.gz" data/
# get the dev version of ArchiveBox
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
# or: sudo apt update && sudo apt install --only-upgrade archivebox
# or: brew update && brew upgrade archivebox
# run init inside any data directories to migrate the index to the latest version
cd ~/archivebox/data
+11 -10
View File
@@ -7,23 +7,24 @@ There are two ways to publish your archive: using the `archivebox server` or by
## 1. Use the built-in web server
```bash
# set the permissions depending on how public/locked down you want it to be
archivebox config --set PUBLIC_INDEX=True
archivebox config --set PUBLIC_ADD_VIEW=True
archivebox config --set PERMISSIONS=public # default visibility of newly created snapshots (was: PUBLIC_SNAPSHOTS=True)
archivebox config --set BASE_URL=https://archive.example.com
archivebox config --set SERVER_SECURITY_MODE=safe-subdomains-fullreplay
# create an admin username and password for yourself
archivebox manage createsuperuser
# then start the webserver and open the web UI in your browser
# start the webserver and open the local admin UI
archivebox server 0.0.0.0:8000
open https://web.archive.example.com
open http://admin.archivebox.localhost:8000
```
This server is enabled out-of-the-box if you're using `docker-compose` to run ArchiveBox.
If hosting publicly, place an SSL termination server in front of ArchiveBox. Start ArchiveBox normally, then follow the first-run wizard for the settings to enter in Cloudflare, Nginx Proxy Manager, Caddy, Traefik, Tailscale, or your hosting platform's ingress UI.
If hosting publicly, complete the first-run wizard before exposing ArchiveBox. It guides you through choosing a security mode and shows the settings to enter in Cloudflare, Nginx Proxy Manager, Caddy, Traefik, Tailscale, or your hosting platform's ingress UI. Configure HTTPS there, then open the canonical URL you chose in the wizard.
After ingress is working, set the permissions depending on how public you want the archive to be:
```bash
archivebox config --set PUBLIC_INDEX=True
archivebox config --set PUBLIC_ADD_VIEW=True
archivebox config --set PERMISSIONS=public
```
> [!TIP]
> Advanced: You can use nginx to serve a static export directly from the filesystem. Do not proxy live replay paths back onto the admin origin; use ArchiveBox's security-mode routing.
+1 -1
View File
@@ -84,7 +84,7 @@ LOGOUT_REDIRECT_URL=https://auth.yourcompany.example.com/after/logout
First, install the `ldap` add-on to use this feature (not needed for Docker Archivebox).
```bash
uv tool install --python 3.13 --prerelease allow --upgrade 'archivebox[ldap]'
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox[ldap]>=0.9.0rc0,<0.10'
```
Then set these configuration values to finish configuring LDAP:
+21 -7
View File
@@ -8,7 +8,7 @@ cd ~/archivebox
tar -czf "archivebox-data-$(date +%s).tar.gz" data/
# Update ArchiveBox using the package manager you originally installed it with
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
# or: sudo apt update && sudo apt install --only-upgrade archivebox
# or: brew update && brew upgrade archivebox
@@ -96,9 +96,9 @@ docker stop CONTAINER_ID
cd ~/archivebox/data # or wherever your existing collection is stored
docker pull archivebox/archivebox:dev
docker run -v $PWD:/data -it archivebox/archivebox:dev init
docker run -v $PWD:/data -it archivebox/archivebox:dev install
docker run -v $PWD:/data -it archivebox/archivebox:dev update --migrate-only
docker run --rm -v $PWD:/data -it archivebox/archivebox:dev init
docker run --rm -v $PWD:/data -it archivebox/archivebox:dev install
docker run --rm -v $PWD:/data -it archivebox/archivebox:dev update --migrate-only
# restart the archivebox server container if needed
docker run -v $PWD:/data -it -p 8000:8000 archivebox/archivebox:dev server 0.0.0.0:8000
@@ -117,14 +117,28 @@ Package manager releases take a lot of effort to maintain ([contributions welcom
cd ~/archivebox/data # or wherever your data folder is
# upgrade ArchiveBox using the package manager you originally used to install it
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
# or
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
```
Or, for apt:
```bash
cd ~/archivebox/data # or wherever your data folder is
sudo apt update
sudo apt install --only-upgrade archivebox
# or
```
Or, for Homebrew:
```bash
cd ~/archivebox/data # or wherever your data folder is
brew update
brew upgrade archivebox
```
Then finish the collection upgrade:
```bash
archivebox init # run init to upgrade the collection to the latest version
archivebox install # refresh runtime dependencies if needed
+64 -56
View File
@@ -5,20 +5,20 @@ version = 2
[snippets]
# README.md
"568e2aa45f6256e9-1" = "illustration"
"dfce88ac5547f8e1-1" = "illustration"
"c6654ae1f15bca88-1" = "run"
"c5440af2c171391a-1" = "illustration"
"1754491b1b6255de-1" = "illustration"
"72fddf697c7b0142-1" = "illustration"
"fe1f06a0a6b02c01-1" = "illustration"
"220882ff87e9c738-1" = "run"
"698f2b0f88796c9b-1" = "illustration"
"398611ab1880ef51-1" = "illustration"
"778c693286d7b895-1" = "illustration"
"16f90085b2eb8756-1" = "illustration"
"50b37bfc08509639-1" = "run"
"39bc0d99ed165be2-1" = "run"
"d5ae3240405670bb-1" = "run"
"4da22c86e09ce7d4-1" = "illustration"
"a7a123ba522227be-1" = "run"
"fcdd073a5440f5ca-1" = "run"
"4da22c86e09ce7d4-2" = "illustration"
"05be9e44a8ae9875-1" = "run"
"8fe4a2c15abb0ab2-1" = "run"
"2b08779e4d24d305-1" = "run"
"4da22c86e09ce7d4-3" = "illustration"
"1b9438e093866ff0-1" = "illustration"
@@ -26,7 +26,7 @@ version = 2
"e244b54155aa0bff-1" = "run"
"b5176e0b5ad1bcfc-1" = "illustration"
"13fefe6d12cab753-1" = "illustration"
"234ffac977318d0c-1" = "illustration"
"47bd31942b5c44c4-1" = "illustration"
"4a595390c5fe50a4-1" = "illustration"
"30fc18bc7b74b79c-1" = "illustration"
"56df5d0efb8a7a7d-1" = "illustration"
@@ -52,7 +52,7 @@ version = 2
# AGENTS.md
"2695777473e3a217-1" = "run"
"16f996904f0eaa96-1" = "run"
"b894a7c4f93e4e92-1" = "run"
"167cab3db82a6aee-1" = "run"
"6a6234b63cd3d0ae-1" = "run"
"396490664ce459e9-1" = "run"
@@ -67,7 +67,7 @@ version = 2
# skills/archivebox/SKILL.md
"ac2c1555d3b9cd8f-1" = "run"
"6b5798897504b1c8-1" = "run"
"11a520069efa3387-1" = "run"
"46fe9d81940ea385-1" = "run"
"396490664ce459e9-2" = "run"
@@ -98,7 +98,7 @@ version = 2
"ce0de8ed0d7cd552-1" = "run"
"83cb9be168d203ca-1" = "run"
"2028f9a887105a13-1" = "run"
"fa8a95a3d0d51b76-1" = "run"
"70389ddb24a77820-1" = "run"
"00c51bd82b457d50-1" = "illustration"
"48b48da014bd91dd-1" = "illustration"
"c4266755c0748aa0-1" = "run"
@@ -107,16 +107,16 @@ version = 2
# docs/Docker.md
"03a42395e6560eba-1" = "illustration"
"75490fc7993139ef-1" = "illustration"
"10f9ca01b3b7a305-1" = "run"
"fef4f0641483ee49-1" = "run"
"8104bfe431c6b914-1" = "run"
"df0dbbd212722e34-1" = "illustration"
"b10a271100fee968-1" = "illustration"
"4deb738714bdbd33-1" = "illustration"
"affb9c5d5980ed1c-1" = "run"
"0e9c1f41ef579e79-1" = "run"
"746a3e228d29f9d2-1" = "run"
"66a7d54ea81d64dc-1" = "illustration"
"aa4884ebe3101cf6-1" = "illustration"
"150ae2c7f6d012ab-1" = "illustration"
"646dda5284de16a5-1" = "run"
"8d86727108f1dbad-1" = "run"
"6bcb6c2e8770907a-1" = "illustration"
"fcb2471eb154161b-1" = "illustration"
"d093b42a06e0485e-1" = "illustration"
"6353a7cf3014612e-1" = "illustration"
"d66324df4c7f97c7-1" = "illustration"
"17ed06e5b75aac1b-1" = "illustration"
@@ -128,17 +128,19 @@ version = 2
# docs/Install.md
"662be0691524555c-1" = "illustration"
"5e4e9a2a8a9435b2-1" = "run"
"c8df632a4f4e3100-1" = "run"
"4d29a8d4b3902ab3-1" = "run"
"73f9f36a369f2398-1" = "run"
"a6837d7c229936af-1" = "illustration"
"c2eb27c95729f2b4-1" = "run"
"b92d8bdeb23143fc-1" = "illustration"
"311abf57a4443a9e-1" = "illustration"
"fcaf2b90e39272df-1" = "run"
"77b61fd52c4c3c32-1" = "run"
"a8f16f6be4f2b3c0-1" = "run"
"035ecb710f7fc2a8-1" = "illustration"
"e4e22c56cb720344-1" = "illustration"
"2905d7f9dece0b61-1" = "run"
"a64c69417bb98a93-1" = "illustration"
"d62c4ceb9083ac5e-1" = "run"
"a5bb45966ec97884-1" = "run"
# docs/Merging-Collections.md
"befadadb67c37c7b-1" = "illustration"
@@ -154,7 +156,8 @@ version = 2
"3ecf97d15d7e8ae9-1" = "illustration"
# docs/Publishing-Your-Archive.md
"a3e3e05317559025-1" = "illustration"
"d582053c0ef82865-1" = "illustration"
"9cf635e7d35fd992-1" = "illustration"
"44fb1b5e4e4b8019-1" = "illustration"
"e3fbec28244a58a0-1" = "illustration"
@@ -198,7 +201,7 @@ version = 2
# docs/Setting-up-Authentication.md
"e7e99e4304de5ac7-1" = "illustration"
"9821835f15ad67b2-1" = "illustration"
"fa8a95a3d0d51b76-2" = "run"
"70389ddb24a77820-2" = "run"
"7caaf25736e20132-1" = "illustration"
"4f35c142a9d6a1c6-1" = "illustration"
"38551637f2a2b4ee-1" = "illustration"
@@ -229,10 +232,13 @@ version = 2
"03803c44e54399c4-1" = "illustration"
# docs/Upgrading.md
"91252f0b5ea69d28-1" = "illustration"
"4c75ef58fc49ad99-1" = "illustration"
"6678cb1b999c44ea-1" = "illustration"
"09bbb69bbb6cf07f-1" = "illustration"
"d4d84a74feec85a7-1" = "illustration"
"23c840fa4bc05801-1" = "illustration"
"3075ca963b666b72-1" = "illustration"
"c47a6f74d71e34c2-1" = "illustration"
"24ce21d1e112e656-1" = "illustration"
"4a08ffc142dc82c9-1" = "illustration"
# docs/Usage.md
"865eeba3c03d1e2d-1" = "run"
@@ -249,12 +255,12 @@ version = 2
"85f531841ec3e169-1" = "run"
[scenarios]
"c6654ae1f15bca88-1" = "docker"
"50b37bfc08509639-1" = "system"
"220882ff87e9c738-1" = "docker"
"39bc0d99ed165be2-1" = "system"
"d5ae3240405670bb-1" = "collection"
"a7a123ba522227be-1" = "system"
"fcdd073a5440f5ca-1" = "system"
"05be9e44a8ae9875-1" = "system"
"8fe4a2c15abb0ab2-1" = "system"
"2b08779e4d24d305-1" = "system"
"6392c7b5f3593d07-1" = "collection"
"e244b54155aa0bff-1" = "docker"
@@ -266,7 +272,7 @@ version = 2
"fab7ffa7e26bc3e5-1" = "collection"
"2695777473e3a217-1" = "project"
"16f996904f0eaa96-1" = "project"
"b894a7c4f93e4e92-1" = "system"
"167cab3db82a6aee-1" = "system"
"6a6234b63cd3d0ae-1" = "collection"
"396490664ce459e9-1" = "project"
"d9ff84abeb97d380-1" = "collection"
@@ -274,7 +280,7 @@ version = 2
"0b307d4f1033723f-1" = "collection"
"7d33d72cc96df65b-1" = "collection"
"ac2c1555d3b9cd8f-1" = "project"
"6b5798897504b1c8-1" = "system"
"11a520069efa3387-1" = "system"
"46fe9d81940ea385-1" = "collection"
"396490664ce459e9-2" = "project"
"201988374bff6d14-1" = "collection"
@@ -284,20 +290,21 @@ version = 2
"ce0de8ed0d7cd552-1" = "collection"
"83cb9be168d203ca-1" = "collection"
"2028f9a887105a13-1" = "collection"
"fa8a95a3d0d51b76-1" = "system"
"70389ddb24a77820-1" = "system"
"c4266755c0748aa0-1" = "collection"
"ea6d6196f0985d3c-1" = "collection"
"10f9ca01b3b7a305-1" = "docker"
"fef4f0641483ee49-1" = "docker"
"8104bfe431c6b914-1" = "docker"
"646dda5284de16a5-1" = "docker"
"5e4e9a2a8a9435b2-1" = "system"
"affb9c5d5980ed1c-1" = "docker"
"0e9c1f41ef579e79-1" = "docker"
"746a3e228d29f9d2-1" = "docker"
"8d86727108f1dbad-1" = "docker"
"c8df632a4f4e3100-1" = "system"
"4d29a8d4b3902ab3-1" = "system"
"73f9f36a369f2398-1" = "system"
"c2eb27c95729f2b4-1" = "system"
"fcaf2b90e39272df-1" = "system"
"77b61fd52c4c3c32-1" = "collection"
"a8f16f6be4f2b3c0-1" = "collection"
"2905d7f9dece0b61-1" = "collection"
"d62c4ceb9083ac5e-1" = "system-data"
"a5bb45966ec97884-1" = "system-data"
"9bbf385bf2eb1160-1" = "collection"
"c84835e713d1c625-1" = "collection"
"302de01a4a1490f0-1" = "docker"
@@ -310,7 +317,7 @@ version = 2
"d8013405262d9dee-1" = "system"
"0fdcf228bb957933-1" = "docker-data"
"74e75b4a84bbb636-1" = "docker"
"fa8a95a3d0d51b76-2" = "system"
"70389ddb24a77820-2" = "system"
"1e5a9ff68510b35d-1" = "collection"
"9613aa41cb49f134-1" = "collection"
"ebebc2e7861a80e9-1" = "collection"
@@ -328,12 +335,12 @@ version = 2
"85f531841ec3e169-1" = "collection"
[environments]
"c6654ae1f15bca88-1" = "docker"
"50b37bfc08509639-1" = "ubuntu"
"220882ff87e9c738-1" = "docker"
"39bc0d99ed165be2-1" = "ubuntu"
"d5ae3240405670bb-1" = "ubuntu"
"a7a123ba522227be-1" = "root"
"fcdd073a5440f5ca-1" = "root"
"05be9e44a8ae9875-1" = "macos"
"8fe4a2c15abb0ab2-1" = "macos"
"2b08779e4d24d305-1" = "macos"
"6392c7b5f3593d07-1" = "ubuntu"
"e244b54155aa0bff-1" = "docker"
@@ -345,7 +352,7 @@ version = 2
"fab7ffa7e26bc3e5-1" = "ubuntu"
"2695777473e3a217-1" = "ubuntu"
"16f996904f0eaa96-1" = "ubuntu"
"b894a7c4f93e4e92-1" = "ubuntu"
"167cab3db82a6aee-1" = "ubuntu"
"6a6234b63cd3d0ae-1" = "ubuntu"
"396490664ce459e9-1" = "ubuntu"
"d9ff84abeb97d380-1" = "ubuntu"
@@ -353,7 +360,7 @@ version = 2
"0b307d4f1033723f-1" = "ubuntu"
"7d33d72cc96df65b-1" = "ubuntu"
"ac2c1555d3b9cd8f-1" = "ubuntu"
"6b5798897504b1c8-1" = "ubuntu"
"11a520069efa3387-1" = "ubuntu"
"46fe9d81940ea385-1" = "ubuntu"
"396490664ce459e9-2" = "ubuntu"
"201988374bff6d14-1" = "ubuntu"
@@ -363,20 +370,21 @@ version = 2
"ce0de8ed0d7cd552-1" = "ubuntu"
"83cb9be168d203ca-1" = "ubuntu"
"2028f9a887105a13-1" = "ubuntu"
"fa8a95a3d0d51b76-1" = "ubuntu"
"70389ddb24a77820-1" = "ubuntu"
"c4266755c0748aa0-1" = "ubuntu"
"ea6d6196f0985d3c-1" = "ubuntu"
"10f9ca01b3b7a305-1" = "docker"
"fef4f0641483ee49-1" = "docker"
"8104bfe431c6b914-1" = "docker"
"646dda5284de16a5-1" = "docker"
"5e4e9a2a8a9435b2-1" = "macos"
"affb9c5d5980ed1c-1" = "docker"
"0e9c1f41ef579e79-1" = "docker"
"746a3e228d29f9d2-1" = "docker"
"8d86727108f1dbad-1" = "docker"
"c8df632a4f4e3100-1" = "macos"
"4d29a8d4b3902ab3-1" = "macos"
"73f9f36a369f2398-1" = "root"
"c2eb27c95729f2b4-1" = "ubuntu"
"fcaf2b90e39272df-1" = "ubuntu"
"77b61fd52c4c3c32-1" = "ubuntu"
"a8f16f6be4f2b3c0-1" = "ubuntu"
"2905d7f9dece0b61-1" = "ubuntu"
"d62c4ceb9083ac5e-1" = "ubuntu"
"a5bb45966ec97884-1" = "ubuntu"
"9bbf385bf2eb1160-1" = "ubuntu"
"c84835e713d1c625-1" = "ubuntu"
"302de01a4a1490f0-1" = "docker"
@@ -389,7 +397,7 @@ version = 2
"d8013405262d9dee-1" = "root"
"0fdcf228bb957933-1" = "docker"
"74e75b4a84bbb636-1" = "docker"
"fa8a95a3d0d51b76-2" = "ubuntu"
"70389ddb24a77820-2" = "ubuntu"
"1e5a9ff68510b35d-1" = "ubuntu"
"9613aa41cb49f134-1" = "ubuntu"
"ebebc2e7861a80e9-1" = "ubuntu"
+1 -1
View File
@@ -23,7 +23,7 @@ ArchiveBox
.. code-block:: bash
mkdir my-archive; cd my-archive/
uv tool install --python 3.13 --prerelease allow --upgrade archivebox
uv tool install --python 3.13 --prerelease explicit --upgrade 'archivebox>=0.9.0rc0,<0.10'
archivebox init
archivebox install
+1
View File
@@ -479,6 +479,7 @@ def run_snippets(snippet_ids: tuple[str, ...]) -> None:
f" chmod -R a+rX {root_checkout_venv}\n"
"fi\n"
f'export PYTHONPATH="{root_checkout_import_root}${{PYTHONPATH:+:${{PYTHONPATH}}}}"\n'
f'export PATH="{root_checkout_python.parent}:$PATH"\n'
f'exec {root_checkout_python} -m archivebox "$@"\n',
)
archivebox_wrapper.chmod(0o755)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "archivebox",
"version": "0.9.35rc241",
"version": "0.9.35rc286",
"repository": "github:ArchiveBox/ArchiveBox",
"license": "MIT",
"dependencies": {
+3 -3
View File
@@ -1,6 +1,6 @@
[project]
name = "archivebox"
version = "0.9.35rc241"
version = "0.9.35rc286"
requires-python = ">=3.13"
description = "Self-hosted internet archiving solution."
authors = [{name = "Nick Sweeting", email = "pyproject.toml@archivebox.io"}]
@@ -83,7 +83,7 @@ dependencies = [
"abxbus", # direct imports only; version constrained by abx-dl -> abx-plugins
"abxpkg", # direct imports only; version constrained by abx-dl -> abx-plugins
"abx-plugins", # direct imports only; version constrained by abx-dl
"abx-dl==1.12.110", # shared ArchiveBox downloader package
"abx-dl==1.12.167", # shared ArchiveBox downloader package
### UUID7 backport for Python <3.14
"uuid7>=0.1.0; python_version < '3.14'", # provides the uuid_extensions module on Python 3.13
]
@@ -344,7 +344,7 @@ Donate = "https://github.com/ArchiveBox/ArchiveBox/wiki/Donations"
[tool.bumpver]
current_version = "v0.9.35rc241"
current_version = "v0.9.35rc286"
version_pattern = "vMAJOR.MINOR.PATCH[PYTAGNUM]"
commit_message = "bump version {old_version} -> {new_version}"
tag_message = "{new_version}"
+1 -1
View File
@@ -34,7 +34,7 @@ uv run --project .. archivebox install
Recommended CLI install:
```bash
uv tool install --python 3.13 --prerelease allow --upgrade 'git+https://github.com/ArchiveBox/ArchiveBox.git@dev'
uv tool install --python 3.13 --prerelease explicit --upgrade 'git+https://github.com/ArchiveBox/ArchiveBox.git@dev'
mkdir -p ~/archivebox/data
cd ~/archivebox/data
archivebox init
Generated
+13 -156
View File
@@ -13,24 +13,23 @@ supported-markers = [
]
[options]
exclude-newer = "2026-08-08T17:08:49.206372985Z"
exclude-newer = "2026-08-11T03:34:33.381986164Z"
exclude-newer-span = "P5D"
[options.exclude-newer-package]
abxbus = { timestamp = "2026-08-13T17:08:48.206388628Z", span = "PT1S" }
abxbus = { timestamp = "2026-08-16T03:34:32.382002568Z", span = "PT1S" }
abx-plugins = "2100-01-01T00:00:00Z"
abx-dl = "2100-01-01T00:00:00Z"
abxpkg = "2100-01-01T00:00:00Z"
[[package]]
name = "abx-dl"
version = "1.12.110"
version = "1.12.167"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "abx-plugins", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "abxbus", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "abxpkg", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "jambo", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "psutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -39,26 +38,25 @@ dependencies = [
{ name = "rich", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "rich-click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/01/d90f9bd97cf8640f025be1c00acdb853da956636712a8103f9c3fd38ea72/abx_dl-1.12.110.tar.gz", hash = "sha256:2c4631affc31d21732e48bfade094bc5db1e469de7dcedcea22774c69de4aefb", size = 88243, upload-time = "2026-08-13T17:03:04.670869Z" }
sdist = { url = "https://files.pythonhosted.org/packages/28/b9/4745fe4757554fb6be5686c30cb96fbcc60a2051a2fa7c4737e419de667a/abx_dl-1.12.167.tar.gz", hash = "sha256:e4df25d5ddbde271d1afe6af6319e7486db1c6f1da68f19e6e6ee2cb171aaec2", size = 83586, upload-time = "2026-08-16T03:29:30.997059Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/ab/1e5def077aecbb0e8501fbd3c5da7bff4c14ebbe30813558b829a1149972/abx_dl-1.12.110-py3-none-any.whl", hash = "sha256:cc9c22586ccf7c3c8c0769b7427cd4f21a82a9b0c0063b1df01e6d61b44082c3", size = 92313, upload-time = "2026-08-13T17:03:05.717631Z" },
{ url = "https://files.pythonhosted.org/packages/2b/69/999aec7d2cf6bd6817c991fb0fb85f0c38879b7eda34b3d704598d5171f9/abx_dl-1.12.167-py3-none-any.whl", hash = "sha256:64e16593ca53678e2766a8f42e16a2670c2a7e793e25b348b9a04b7d72e145f2", size = 87277, upload-time = "2026-08-16T03:29:29.741963Z" },
]
[[package]]
name = "abx-plugins"
version = "1.12.98"
version = "1.12.154"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "abxbus", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "abxpkg", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "imagesize", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "jambo", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "rich-click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "uv", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/56/60/9dfb16893a1ad54279ae2f496f77af72c10a4e1da7f06798844e6dd98eae/abx_plugins-1.12.98.tar.gz", hash = "sha256:60ccfca900db653453f086daf2f615996e14487467013b283b7f2d18ad4650f6", size = 255512, upload-time = "2026-08-13T16:48:50.176Z" }
sdist = { url = "https://files.pythonhosted.org/packages/4a/a7/ba36af9c53fe9594df08bfad53078a8e63f646eb06845b9dddd1f44db7b8/abx_plugins-1.12.154.tar.gz", hash = "sha256:a5811447d03e501edc4cab28a7bcc845c6f688560fb9a387f04c1c2ea0b77d80", size = 253870, upload-time = "2026-08-16T03:16:29.243Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/fd/83f7ab2c0b54064d21dc1bf06c24cb81940ef22e457f21605bc81148da65/abx_plugins-1.12.98-py3-none-any.whl", hash = "sha256:f4bce2858987643228fc3b1666384ca6e21fca8b3deed603bfc42c44f8a3ca91", size = 406217, upload-time = "2026-08-13T16:48:51.449Z" },
{ url = "https://files.pythonhosted.org/packages/06/2a/4ea008be973c9721a20a1276ba00825e111630621ec4baf841ec9c213f18/abx_plugins-1.12.154-py3-none-any.whl", hash = "sha256:e208bff42d875e0d5711d6091e41b82848ec78682ae0c3b11e1438eae3234a2c", size = 405106, upload-time = "2026-08-16T03:16:27.703Z" },
]
[[package]]
@@ -77,7 +75,7 @@ wheels = [
[[package]]
name = "abxpkg"
version = "1.12.61"
version = "1.12.91"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -85,9 +83,9 @@ dependencies = [
{ name = "rich-click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5d/26/217df4713c35fedf0309c28f0b8276a37ac20ffc8437917069ad5d73ba6d/abxpkg-1.12.61.tar.gz", hash = "sha256:786357fdba13df9f69a66b542fefea924c7efc063208bb3d87c210472ca442b2", size = 233362, upload-time = "2026-08-13T16:37:19.019Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/a9/39f043f58d667f48b61a9fd68ff9a92cce57ee7810495b2eb533aaeff122/abxpkg-1.12.91.tar.gz", hash = "sha256:1c8b0fe0cea603ff51251d04b02257c136d8fbc1e8255533dac5ff5f86e8a1fd", size = 238948, upload-time = "2026-08-16T01:50:16.768Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/58/7374ddf88f934427c52971fdc8a3173d2444d66c90617ffb5354f1017a99/abxpkg-1.12.61-py3-none-any.whl", hash = "sha256:52032baf7e818da26b668e697d6933e205549bd397702ba1c38b462dcb7c476f", size = 247543, upload-time = "2026-08-13T16:37:20.5Z" },
{ url = "https://files.pythonhosted.org/packages/41/40/5e1e5fa17af317045f234cac8b3b9edbca6e3a68a2efe6cfb9343b64d226/abxpkg-1.12.91-py3-none-any.whl", hash = "sha256:06e332dd7e6deca09155ed687967aed497a4d2123d1ef92c782db00e72f494ed", size = 253111, upload-time = "2026-08-16T01:50:15.275Z" },
]
[[package]]
@@ -122,7 +120,7 @@ wheels = [
[[package]]
name = "archivebox"
version = "0.9.35rc241"
version = "0.9.35rc286"
source = { editable = "." }
dependencies = [
{ name = "abx-dl", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -222,7 +220,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "abx-dl", specifier = "==1.12.110" },
{ name = "abx-dl", specifier = "==1.12.167" },
{ name = "abx-plugins" },
{ name = "abxbus" },
{ name = "abxpkg" },
@@ -908,15 +906,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/36/85/c4e42d21cf748c696b8c05316bbd8e8666f17eeda0cf1743056f4cf7622b/djdt_flamegraph-0.2.13-py2.py3-none-any.whl", hash = "sha256:b3252b8cc9b586829166cc158b26952626cd6f41a3ffa92dceef2f5dbe5b99a0", size = 15256, upload-time = "2020-01-17T05:40:37.799Z" },
]
[[package]]
name = "dnspython"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
]
[[package]]
name = "docutils"
version = "0.22.4"
@@ -926,19 +915,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" },
]
[[package]]
name = "email-validator"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "dnspython", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
]
[[package]]
name = "executing"
version = "2.2.1"
@@ -1094,17 +1070,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
]
[[package]]
name = "jambo"
version = "0.1.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "email-validator", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "jsonschema", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/91/f5/74de157c7aece6a070f99f18201a0e2f46cdfd0f9e337efd411745ed9b22/jambo-0.1.7.tar.gz", hash = "sha256:df89ab8209ebdf7a6e92252ec925979cd3d32811bf4a8182a97dc35b7df58f74", size = 137822, upload-time = "2026-01-14T19:17:30.302Z" }
[[package]]
name = "jedi"
version = "0.20.0"
@@ -1129,33 +1094,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "jsonschema"
version = "4.26.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "jsonschema-specifications", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "referencing", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "rpds-py", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
]
[[package]]
name = "jsonschema-specifications"
version = "2025.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "referencing", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
]
[[package]]
name = "lexid"
version = "2021.1006"
@@ -2124,19 +2062,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/77/ed589c75db5d02a77a1d5d2d9abc63f29676467d396c64277f98b50b79c2/recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f", size = 10214, upload-time = "2020-12-17T19:24:55.137Z" },
]
[[package]]
name = "referencing"
version = "0.37.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "rpds-py", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
]
[[package]]
name = "regex"
version = "2026.7.10"
@@ -2260,74 +2185,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" },
]
[[package]]
name = "rpds-py"
version = "2026.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" },
{ url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" },
{ url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" },
{ url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" },
{ url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" },
{ url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" },
{ url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" },
{ url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" },
{ url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" },
{ url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" },
{ url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" },
{ url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" },
{ url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" },
{ url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" },
{ url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" },
{ url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" },
{ url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" },
{ url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" },
{ url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" },
{ url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" },
{ url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" },
{ url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" },
{ url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" },
{ url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" },
{ url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" },
{ url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" },
{ url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" },
{ url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" },
{ url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" },
{ url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" },
{ url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" },
{ url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" },
{ url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" },
{ url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" },
{ url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" },
{ url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" },
{ url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" },
{ url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" },
{ url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" },
{ url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" },
{ url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" },
{ url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" },
{ url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" },
{ url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" },
{ url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" },
{ url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" },
{ url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" },
{ url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" },
{ url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" },
{ url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" },
{ url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" },
{ url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" },
{ url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" },
{ url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" },
{ url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" },
{ url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" },
{ url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" },
{ url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" },
{ url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" },
{ url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" },
]
[[package]]
name = "ruff"
version = "0.15.21"