Make get.archivebox.io a uv install shortcut

This commit is contained in:
Nick Sweeting 2026-08-28 19:05:55 -07:00
parent 96e129d3ad
commit 08c639c25d
No known key found for this signature in database
5 changed files with 56 additions and 703 deletions

View File

@ -98,7 +98,7 @@ archivebox install
# archivebox server 0.0.0.0:8000
<br/>
<br/>
# Option D: Or use the optional auto setup script to install it
# Option D: Or use the uv install shortcut for Option C
curl -fsSL 'https://get.archivebox.io' | bash
</code></pre>
<br/>
@ -208,18 +208,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/117456282-08665e80-af16-11eb-91a1-8102eff54091.png" alt="curl sh automatic setup script" height="28px" align="top"/> <code>bash</code> auto-setup script</b> (macOS/Linux)</summary>
<summary><b><img src="https://user-images.githubusercontent.com/511499/117456282-08665e80-af16-11eb-91a1-8102eff54091.png" alt="curl sh uv install shortcut" height="28px" align="top"/> <code>bash</code> uv install shortcut</b> (macOS/Linux/BSD)</summary>
<br/>
<ol>
<li>Install <a href="https://docs.docker.com/get-docker/">Docker</a> on your system (optional, highly recommended but not required).</li>
<li>Run the automatic setup script.
<li>Run the shortcut for the <code>uv</code> install method. It installs <code>uv</code> first when needed.
<pre lang="bash"><code style="white-space: pre-line">curl -fsSL 'https://get.archivebox.io' | bash</code></pre>
<i>For more info, see <a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Install#option-b-automatic-setup-script">Install: Bare Metal</a> in the Wiki. ➡️</i>
</li>
</ol>
See <a href="#%EF%B8%8F-cli-usage">below</a> for more usage examples using the CLI, Web UI, or filesystem/SQL/Python to manage your archive.<br/>
See <a href="https://github.com/ArchiveBox/ArchiveBox/blob/dev/bin/setup.sh"><code>setup.sh</code></a> for the source code of the auto-install script.<br/>
See <a href="https://github.com/ArchiveBox/ArchiveBox/blob/dev/bin/setup.sh"><code>setup.sh</code></a> for the source code of the <code>uv</code> install shortcut.<br/>
See <a href="https://docs.sweeting.me/s/against-curl-sh">"Against curl | sh as an install method"</a> blog post for my thoughts on the shortcomings of this install method.
<br/><br/>
</details>
@ -340,7 +339,7 @@ See <a href="#%EF%B8%8F-cli-usage">below</a> for more usage examples using the C
<ul>
<li>Arch: <a href="https://aur.archlinux.org/packages/archivebox/"><code>yay -S archivebox</code></a> (contributed by <a href="https://github.com/imlonghao"><code>@imlonghao</code></a>, maintained by <a href="https://github.com/jasongodev"><code>@jasongodev</code></a>)</li>
<li>FreeBSD: <a href="https://github.com/ArchiveBox/ArchiveBox#%EF%B8%8F-easy-setup"><code>curl -fsSL 'https://get.archivebox.io' | bash</code></a> (uses <code>pkg</code> + <code>uv</code> under-the-hood)</li>
<li>FreeBSD: <a href="https://github.com/ArchiveBox/ArchiveBox#%EF%B8%8F-easy-setup"><code>curl -fsSL 'https://get.archivebox.io' | bash</code></a> (uses <code>uv</code>)</li>
<li>Nix: <a href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/archivebox/default.nix"><code>nix-env --install archivebox</code></a> (contributed by <a href="https://github.com/siraben"><code>@siraben</code></a>)</li>
<li>Guix: <a href="https://packages.guix.gnu.org/packages/archivebox/"><code>guix install archivebox</code></a> (contributed by <a href="https://github.com/rakino"><code>@rakino</code></a>)</li>
<li>More: <a href="https://github.com/ArchiveBox/ArchiveBox/issues/new"><i>contribute another distribution...!</i></a></li>

View File

@ -1,199 +1,34 @@
import os
import shutil
import subprocess
import tomllib
from pathlib import Path
SETUP_SCRIPT = Path(__file__).parents[2] / "bin" / "setup.sh"
def test_setup_script_creates_archivebox_user_instead_of_rejecting_root():
def test_setup_script_is_a_uv_install_shortcut():
script = SETUP_SCRIPT.read_text()
install_flow = script.partition("cat <<EOF")[0]
assert "You cannot run this script as root" not in script
assert "useradd --system --create-home" in script
assert 'ARCHIVEBOX_SYSTEM_USER="archivebox"' in script
assert "${BOOTSTRAP_UV_BINARY#/var/root/}" in script
assert "mkdir -p /usr/local/bin" in script
def test_setup_script_never_recursively_chowns_collection_data():
script = SETUP_SCRIPT.read_text()
assert 'chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" "$ARCHIVEBOX_HOME_DIR" "$ARCHIVEBOX_DATA_DIR"' in script
assert "chown -R" not in script
def test_setup_script_gives_archivebox_user_ownership_of_runtime_parent_dirs():
script = SETUP_SCRIPT.read_text()
assert 'for path in "$HOME/.local" "$HOME/.local/share" "$HOME/.cache" "$HOME/.cache/archivebox" "$HOME/.config"; do' in script
assert 'chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" "$path"' in script
assert '"$HOME/.config/uv"' in script
assert 'runuser -u "$ARCHIVEBOX_SYSTEM_USER"' in script
assert '(cd "$HOME" && runuser' in script
assert 'run_as_archivebox_user "$BOOTSTRAP_UV_BINARY" tool run' in script
assert 'run_as_archivebox_user "$UV_BINARY" --no-config tool install' in script
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 f'ABXPKG_PACKAGE="${{ABXPKG_PACKAGE:-abxpkg=={locked_abxpkg_version}}}"' in script
assert 'ARCHIVEBOX_PYTHON="${ARCHIVEBOX_PYTHON:-3.13}"' 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" not in install_function
assert '"$UV_BINARY" tool install --python "$ARCHIVEBOX_PYTHON" --prerelease explicit --upgrade "$ARCHIVEBOX_PACKAGE"' in install_flow
assert "https://astral.sh/uv/install.sh" in script
assert "archivebox init" not in install_flow
assert "archivebox install" not in install_flow
def test_setup_script_preserves_collection_and_compose_ownership_boundaries():
def test_setup_script_does_not_select_a_system_installer_or_runtime():
script = SETUP_SCRIPT.read_text()
assert "data|docker-compose.yml|compose.yml|compose.yaml|.env|Caddyfile)" in script
assert 'mv -n "$path" "$ARCHIVEBOX_DATA_DIR/"' in script
assert "if [ ! -f docker-compose.yml ]; then" in script
assert 'chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" docker-compose.yml' in script
assert 'mv -i "$HOME"/archivebox/*' not in script
assert "docker" not in script.lower()
assert "debian-archivebox" not in script
assert "apt-get" not in script
assert "launchpad" not in script
assert "archivebox server" not in script
assert "useradd" not in script
def test_setup_script_prints_root_safe_runtime_commands():
script = SETUP_SCRIPT.read_text()
def test_setup_script_has_valid_bash_syntax():
result = subprocess.run(["bash", "-n", SETUP_SCRIPT], capture_output=True, text=True, timeout=10)
assert 'echo " cd $ARCHIVEBOX_DATA_DIR' in script
assert 'FOLLOWUP_SUDO="sudo "' 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
assert "manage createsuperuser" not in script
assert "Open http://localhost:8000/admin/ to create the first admin and finish web setup." in script
assert '"$OPEN_BINARY" "http://admin.archivebox.localhost:8000/admin/"' in script
def test_setup_script_pulls_docker_image_once_and_reports_unavailable_docker_accurately():
script = SETUP_SCRIPT.read_text()
install_flow = script.partition("prepare_abxpkg_environment")[2]
assert install_flow.count("docker_pull_archivebox") == 1
assert "Docker wasn't found" not in install_flow
assert "not installed, not running, or not accessible to this user" in install_flow
def test_setup_script_does_not_fail_when_clear_cannot_use_terminal():
script = SETUP_SCRIPT.read_text()
assert "if [ -t 1 ]; then\n clear || true\nfi" in script
def test_setup_script_keeps_optional_binary_probes_quiet():
script = SETUP_SCRIPT.read_text()
assert "resolve_setup_binary open env false 2>/dev/null" in script
assert "resolve_setup_binary docker env false 2>/dev/null" in script
assert 'if [ -n "$OPEN_BINARY" ] && [ -t 1 ]; then' in script
def test_setup_script_selects_collection_library_before_installing_dependencies():
script = SETUP_SCRIPT.read_text()
native_install = script.partition(': | "$ARCHIVEBOX_BINARY" init')[2]
select_library = "select_archivebox_lib_dir"
install_dependencies = '"$ARCHIVEBOX_BINARY" install'
assert select_library in native_install
assert native_install.index(select_library) < native_install.index(install_dependencies)
def test_setup_script_preserves_existing_collection_library(tmp_path):
archivebox_binary = shutil.which("archivebox")
assert archivebox_binary
data_dir = tmp_path / "data"
data_dir.mkdir()
bootstrap_lib = tmp_path / "bootstrap-lib"
configured_lib = tmp_path / "configured-lib"
command_env = {**os.environ, "HOME": str(tmp_path), "ABXPKG_LIB_DIR": str(bootstrap_lib), "TERM": "dumb"}
init_result = subprocess.run(
[archivebox_binary, "init"],
cwd=data_dir,
capture_output=True,
text=True,
env=command_env,
timeout=60,
)
assert init_result.returncode == 0, init_result.stderr or init_result.stdout
config_result = subprocess.run(
[archivebox_binary, "config", "--set", f"ABXPKG_LIB_DIR={configured_lib}"],
cwd=data_dir,
capture_output=True,
text=True,
env=command_env,
timeout=60,
)
assert config_result.returncode == 0, config_result.stderr or config_result.stdout
script = SETUP_SCRIPT.read_text()
function_body = script.partition("select_archivebox_lib_dir() {")[2].partition("\n}")[0]
harness = tmp_path / "select-library.sh"
harness.write_text(f"select_archivebox_lib_dir() {{{function_body}\n}}\n")
select_result = subprocess.run(
[
"bash",
"-c",
'source "$1"; ARCHIVEBOX_BINARY="$2"; select_archivebox_lib_dir; printf "%s" "$ABXPKG_LIB_DIR"',
"bash",
str(harness),
archivebox_binary,
],
cwd=data_dir,
capture_output=True,
text=True,
env=command_env,
timeout=60,
)
assert select_result.returncode == 0, select_result.stderr or select_result.stdout
assert select_result.stdout == str(configured_lib)
def test_setup_script_moves_legacy_collection_without_moving_compose(tmp_path):
script = SETUP_SCRIPT.read_text()
function_prefix = script.partition("\ndocker_pull_archivebox() {")[0]
harness = tmp_path / "setup-functions.sh"
harness.write_text(function_prefix)
archivebox_home = tmp_path / "archivebox"
data_dir = archivebox_home / "data"
data_dir.mkdir(parents=True)
(data_dir / "existing.txt").write_text("keep")
(archivebox_home / "index.sqlite3").write_text("legacy-db")
(archivebox_home / ".archivebox_id").write_text("legacy-id")
(archivebox_home / "docker-compose.yml").write_text("services: {}")
result = subprocess.run(
["bash", "-c", 'source "$1"; migrate_legacy_collection_dir', "bash", str(harness)],
capture_output=True,
text=True,
env={**os.environ, "HOME": str(tmp_path), "TERM": "xterm"},
timeout=10,
)
assert result.returncode == 0, result.stderr or result.stdout
assert (data_dir / "existing.txt").read_text() == "keep"
assert (data_dir / "index.sqlite3").read_text() == "legacy-db"
assert (data_dir / ".archivebox_id").read_text() == "legacy-id"
assert (archivebox_home / "docker-compose.yml").read_text() == "services: {}"
assert result.returncode == 0, result.stderr

View File

@ -64,24 +64,3 @@ if next(package['version'] for package in locked['package'] if package['name'] =
raise SystemExit('Updated ArchiveBox lock version does not match the requested version')
print(version)
PY
uv run --no-cache --no-project python - <<'PY'
from pathlib import Path
import re
import tomllib
packages = tomllib.loads(Path('uv.lock').read_text())['package']
abxpkg_version = next(package['version'] for package in packages if package['name'] == 'abxpkg')
setup_path = Path('bin/setup.sh')
setup = setup_path.read_text()
updated, count = re.subn(
r'^(ABXPKG_PACKAGE="\$\{ABXPKG_PACKAGE:-abxpkg==)[^}"]+(\}")$',
rf'\g<1>{abxpkg_version}\g<2>',
setup,
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise SystemExit('Failed to update ABXPKG_PACKAGE in bin/setup.sh')
setup_path.write_text(updated)
PY

View File

@ -1,507 +1,49 @@
#!/usr/bin/env bash
# ArchiveBox Setup Script (Ubuntu/Debian/FreeBSD/macOS)
# - Project Homepage: https://github.com/ArchiveBox/ArchiveBox
# - Install Documentation: https://github.com/ArchiveBox/ArchiveBox/wiki/Install
# Script Usage:
# curl -fsSL 'https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/bin/setup.sh' | bash
# (aka https://get.archivebox.io)
# ArchiveBox uv install shortcut (macOS/Linux/BSD)
#
# Usage:
# curl -fsSL 'https://get.archivebox.io' | bash
### Bash Environment Setup
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
# https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
set -e
set -u
if (set -o pipefail) 2>/dev/null; then
set -o pipefail
fi
if (set -o errtrace) 2>/dev/null; then
set -o errtrace
fi
if [ -t 1 ]; then
clear || true
fi
set -Eeuo pipefail
RUNNING_AS_ROOT="false"
ARCHIVEBOX_SYSTEM_USER="archivebox"
ARCHIVEBOX_SYSTEM_UID=""
ARCHIVEBOX_SYSTEM_GID=""
FOLLOWUP_SUDO=""
if [ "$(id -u)" -eq 0 ]; then
RUNNING_AS_ROOT="true"
if ! id "$ARCHIVEBOX_SYSTEM_USER" >/dev/null 2>&1; then
echo "[+] Creating the archivebox system user..."
case "$(uname -s)" in
Linux)
useradd --system --create-home --home-dir /var/lib/archivebox --shell /bin/bash "$ARCHIVEBOX_SYSTEM_USER"
;;
FreeBSD)
pw useradd "$ARCHIVEBOX_SYSTEM_USER" -m -d /var/db/archivebox -s /bin/sh
;;
Darwin)
archivebox_uid=499
while dscl . -search /Users UniqueID "$archivebox_uid" 2>/dev/null | grep -q .; do
archivebox_uid=$((archivebox_uid - 1))
done
dscl . -create "/Users/$ARCHIVEBOX_SYSTEM_USER"
dscl . -create "/Users/$ARCHIVEBOX_SYSTEM_USER" RealName ArchiveBox
dscl . -create "/Users/$ARCHIVEBOX_SYSTEM_USER" UserShell /bin/bash
dscl . -create "/Users/$ARCHIVEBOX_SYSTEM_USER" UniqueID "$archivebox_uid"
dscl . -create "/Users/$ARCHIVEBOX_SYSTEM_USER" PrimaryGroupID 20
dscl . -create "/Users/$ARCHIVEBOX_SYSTEM_USER" NFSHomeDirectory "/Users/$ARCHIVEBOX_SYSTEM_USER"
mkdir -p "/Users/$ARCHIVEBOX_SYSTEM_USER"
chown "$archivebox_uid:20" "/Users/$ARCHIVEBOX_SYSTEM_USER"
;;
*)
echo "[X] Cannot create the archivebox system user on $(uname -s)."
exit 2
;;
esac
fi
ARCHIVEBOX_SYSTEM_UID="$(id -u "$ARCHIVEBOX_SYSTEM_USER")"
ARCHIVEBOX_SYSTEM_GID="$(id -g "$ARCHIVEBOX_SYSTEM_USER")"
if command -v getent >/dev/null 2>&1; then
HOME="$(getent passwd "$ARCHIVEBOX_SYSTEM_USER" | cut -d: -f6)"
elif [ "$(uname -s)" = "FreeBSD" ]; then
HOME="$(pw usershow "$ARCHIVEBOX_SYSTEM_USER" | cut -d: -f9)"
else
HOME="$(dscl . -read "/Users/$ARCHIVEBOX_SYSTEM_USER" NFSHomeDirectory | awk '{print $2}')"
fi
export HOME
fi
if [ "$RUNNING_AS_ROOT" = "true" ] && [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
FOLLOWUP_SUDO="sudo "
fi
ARCHIVEBOX_BRANCH="${ARCHIVEBOX_BRANCH:-dev}"
ARCHIVEBOX_IMAGE="${ARCHIVEBOX_IMAGE:-archivebox/archivebox:dev}"
ARCHIVEBOX_PYTHON="${ARCHIVEBOX_PYTHON:-3.13}"
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.109}"
ABXPKG_LIB_DIR="${ABXPKG_LIB_DIR:-$HOME/.cache/archivebox/setup-abxpkg}"
ARCHIVEBOX_HOME_DIR="$HOME/archivebox"
ARCHIVEBOX_DATA_DIR="$ARCHIVEBOX_HOME_DIR/data"
BOOTSTRAP_UV_BINARY=""
UV_BINARY=""
DOCKER_BINARY=""
CURL_BINARY=""
OPEN_BINARY=""
ARCHIVEBOX_BINARY=""
DOCKER_PLATFORM_ARGS=""
if [ -n "$ARCHIVEBOX_PLATFORM" ]; then
DOCKER_PLATFORM_ARGS="--platform $ARCHIVEBOX_PLATFORM"
fi
DOCKER_RUN_TTY_ARG="-i"
DOCKER_COMPOSE_RUN_TTY_ARG="-T"
if [ -t 0 ]; then
DOCKER_RUN_TTY_ARG="-it"
DOCKER_COMPOSE_RUN_TTY_ARG=""
fi
fix_root_install_ownership() {
if [ "$RUNNING_AS_ROOT" != "true" ]; then
return 0
fi
# Keep shared runtime parents writable after ArchiveBox drops privileges.
for path in "$HOME/.local" "$HOME/.local/share" "$HOME/.cache" "$HOME/.cache/archivebox" "$HOME/.config"; do
if [ -e "$path" ]; then
chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" "$path"
fi
done
# Only hand off bounded runtime roots. Provider libraries can be very large,
# and their existing contents only need to remain readable/executable.
for path in "$HOME/.local/bin" "$HOME/.local/share/uv" "$HOME/.cache/uv" "$HOME/.config/uv" "$ABXPKG_LIB_DIR" "$ABXPKG_LIB_DIR/env" "$ABXPKG_LIB_DIR/env/bin"; do
if [ -e "$path" ]; then
chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" "$path"
fi
done
}
run_as_archivebox_user() {
if [ "$RUNNING_AS_ROOT" != "true" ]; then
"$@"
return
fi
if command -v runuser >/dev/null 2>&1; then
(cd "$HOME" && runuser -u "$ARCHIVEBOX_SYSTEM_USER" -- env HOME="$HOME" PATH="$PATH" ABXPKG_LIB_DIR="$ABXPKG_LIB_DIR" "$@")
else
(cd "$HOME" && sudo -u "$ARCHIVEBOX_SYSTEM_USER" env HOME="$HOME" PATH="$PATH" ABXPKG_LIB_DIR="$ABXPKG_LIB_DIR" "$@")
fi
}
ensure_archivebox_data_dir() {
mkdir -p "$ARCHIVEBOX_DATA_DIR"
if [ "$RUNNING_AS_ROOT" = "true" ]; then
chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" "$ARCHIVEBOX_HOME_DIR" "$ARCHIVEBOX_DATA_DIR"
fi
}
migrate_legacy_collection_dir() {
if [ ! -f "$ARCHIVEBOX_HOME_DIR/index.sqlite3" ]; then
return 0
fi
for path in "$ARCHIVEBOX_HOME_DIR"/* "$ARCHIVEBOX_HOME_DIR"/.[!.]* "$ARCHIVEBOX_HOME_DIR"/..?*; do
if [ ! -e "$path" ] && [ ! -L "$path" ]; then
continue
fi
case "$(basename "$path")" in
data|docker-compose.yml|compose.yml|compose.yaml|.env|Caddyfile)
continue
;;
esac
mv -n "$path" "$ARCHIVEBOX_DATA_DIR/"
done
}
docker_pull_archivebox() {
if [ -n "$ARCHIVEBOX_PLATFORM" ]; then
"$DOCKER_BINARY" pull --platform "$ARCHIVEBOX_PLATFORM" "$ARCHIVEBOX_IMAGE"
else
"$DOCKER_BINARY" pull "$ARCHIVEBOX_IMAGE"
fi
}
docker_run_archivebox() {
if [ -n "$ARCHIVEBOX_PLATFORM" ]; then
"$DOCKER_BINARY" run --platform "$ARCHIVEBOX_PLATFORM" "$DOCKER_RUN_TTY_ARG" -v "$PWD":/data --rm "$ARCHIVEBOX_IMAGE" "$@"
else
"$DOCKER_BINARY" run "$DOCKER_RUN_TTY_ARG" -v "$PWD":/data --rm "$ARCHIVEBOX_IMAGE" "$@"
fi
}
docker_run_archivebox_init() {
docker_run_archivebox init
}
docker_run_archivebox_install() {
docker_run_archivebox install
}
docker_run_archivebox_server() {
if [ -n "$ARCHIVEBOX_PLATFORM" ]; then
"$DOCKER_BINARY" run --platform "$ARCHIVEBOX_PLATFORM" -v "$PWD":/data -d -p 8000:8000 --name=archivebox "$ARCHIVEBOX_IMAGE"
else
"$DOCKER_BINARY" run -v "$PWD":/data -d -p 8000:8000 --name=archivebox "$ARCHIVEBOX_IMAGE"
fi
}
docker_compose_run_archivebox() {
if [ -n "$DOCKER_COMPOSE_RUN_TTY_ARG" ]; then
"$DOCKER_BINARY" compose run "$DOCKER_COMPOSE_RUN_TTY_ARG" --rm archivebox "$@"
else
"$DOCKER_BINARY" compose run --rm archivebox "$@"
fi
}
wait_for_archivebox() {
url="http://127.0.0.1:8000/health/"
host_header="admin.archivebox.localhost:8000"
attempts=60
attempt=1
while [ "$attempt" -le "$attempts" ]; do
if "$CURL_BINARY" -fsS --connect-timeout 1 --max-time 2 -H "Host: ${host_header}" "$url" >/dev/null 2>&1; then
return 0
fi
sleep 1
attempt=$((attempt + 1))
done
echo "[!] Server process started, but health check did not become ready at $url after ${attempts}s."
echo " Inspect $ARCHIVEBOX_DATA_DIR/logs/ or run 'docker compose logs' to diagnose startup."
return 1
}
open_archivebox() {
if [ -n "$OPEN_BINARY" ] && [ -t 1 ]; then
"$OPEN_BINARY" "http://admin.archivebox.localhost:8000/admin/" || true
fi
}
ensure_uv() {
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
if command -v uv > /dev/null 2>&1; then
BOOTSTRAP_UV_BINARY="$(command -v uv)"
if [ "$RUNNING_AS_ROOT" = "true" ] && { [ "${BOOTSTRAP_UV_BINARY#/root/}" != "$BOOTSTRAP_UV_BINARY" ] || [ "${BOOTSTRAP_UV_BINARY#/var/root/}" != "$BOOTSTRAP_UV_BINARY" ]; }; then
mkdir -p "$HOME/.local/bin"
install -m 0755 "$BOOTSTRAP_UV_BINARY" "$HOME/.local/bin/uv"
if command -v uvx >/dev/null 2>&1; then
install -m 0755 "$(command -v uvx)" "$HOME/.local/bin/uvx"
fi
fix_root_install_ownership
BOOTSTRAP_UV_BINARY="$HOME/.local/bin/uv"
fi
if [ "$RUNNING_AS_ROOT" = "true" ] && [ "${BOOTSTRAP_UV_BINARY#"$HOME"/}" != "$BOOTSTRAP_UV_BINARY" ]; then
chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" "$BOOTSTRAP_UV_BINARY"
if [ -e "$HOME/.local/bin/uvx" ]; then
chown "$ARCHIVEBOX_SYSTEM_UID:$ARCHIVEBOX_SYSTEM_GID" "$HOME/.local/bin/uvx"
fi
fi
return 0
fi
export PATH="${XDG_BIN_HOME:-$HOME/.local/bin}:$HOME/.cargo/bin:$PATH"
UV_BINARY="$(command -v uv || true)"
if [ -z "$UV_BINARY" ]; then
echo "[+] Installing uv..."
if command -v curl > /dev/null 2>&1; then
if [ "$RUNNING_AS_ROOT" = "true" ]; then
curl -LsSf https://astral.sh/uv/install.sh | run_as_archivebox_user env UV_NO_MODIFY_PATH=1 sh
else
curl -LsSf https://astral.sh/uv/install.sh | sh
fi
elif command -v wget > /dev/null 2>&1; then
wget -qO- https://astral.sh/uv/install.sh | run_as_archivebox_user sh
if command -v curl >/dev/null 2>&1; then
curl -LsSf https://astral.sh/uv/install.sh | sh
elif command -v wget >/dev/null 2>&1; then
wget -qO- https://astral.sh/uv/install.sh | sh
else
echo "[X] curl or wget is required to install uv."
echo "[X] curl or wget is required to install uv." >&2
exit 1
fi
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
if ! command -v uv > /dev/null 2>&1; then
echo "[X] uv was installed, but the uv command was not found in PATH."
echo " Add ~/.local/bin to PATH, then run this script again."
exit 1
fi
BOOTSTRAP_UV_BINARY="$(command -v uv)"
fix_root_install_ownership
}
resolve_setup_binary() {
local binary_name="$1"
local binproviders="$2"
local install_binary="$3"
local resolved_binary
local -a abxpkg_args=(
env
--json
"--lib=$ABXPKG_LIB_DIR"
"--binproviders=$binproviders"
)
if [ "$install_binary" = "true" ]; then
abxpkg_args+=(--install)
fi
abxpkg_args+=("$binary_name")
run_as_archivebox_user "$BOOTSTRAP_UV_BINARY" tool run --from "$ABXPKG_PACKAGE" abxpkg "${abxpkg_args[@]}" >/dev/null
resolved_binary="$ABXPKG_LIB_DIR/env/bin/$binary_name"
test -L "$resolved_binary"
test -x "$resolved_binary"
}
prepare_abxpkg_environment() {
ensure_uv
mkdir -p "$ABXPKG_LIB_DIR/env/bin"
fix_root_install_ownership
export ABXPKG_LIB_DIR
export PATH="$ABXPKG_LIB_DIR/env/bin:$PATH"
resolve_setup_binary uv env false
UV_BINARY="$ABXPKG_LIB_DIR/env/bin/uv"
BOOTSTRAP_UV_BINARY="$UV_BINARY"
if resolve_setup_binary open env false 2>/dev/null; then
OPEN_BINARY="$ABXPKG_LIB_DIR/env/bin/open"
fi
fix_root_install_ownership
}
resolve_setup_curl() {
resolve_setup_binary curl env,brew,apt true
CURL_BINARY="$ABXPKG_LIB_DIR/env/bin/curl"
}
install_archivebox_with_uv() {
echo
echo "[+] Installing ArchiveBox python tool using uv from $ARCHIVEBOX_PACKAGE..."
run_as_archivebox_user "$UV_BINARY" --no-config tool install --python "$ARCHIVEBOX_PYTHON" --prerelease explicit --upgrade "$ARCHIVEBOX_PACKAGE"
uv_tool_bin_dir="$(run_as_archivebox_user "$UV_BINARY" --no-config tool dir --bin)"
ARCHIVEBOX_BINARY="$uv_tool_bin_dir/archivebox"
test -x "$ARCHIVEBOX_BINARY"
if [ "$RUNNING_AS_ROOT" != "true" ]; then
"$UV_BINARY" --no-config tool update-shell || true
fi
fix_root_install_ownership
if [ "$RUNNING_AS_ROOT" = "true" ]; then
mkdir -p /usr/local/bin
ln -sf "$ARCHIVEBOX_BINARY" /usr/local/bin/archivebox
ARCHIVEBOX_BINARY="/usr/local/bin/archivebox"
fi
}
select_archivebox_lib_dir() {
local configured_lib_dir
configured_lib_dir="$(
COLUMNS=10000 env -u ABXPKG_LIB_DIR "$ARCHIVEBOX_BINARY" config --get ABXPKG_LIB_DIR \
| awk '
!found && /^[[:space:]]*ABXPKG_LIB_DIR[[:space:]]*=/ {
value = $0
sub(/^[^=]*=[[:space:]]*/, "", value)
sub(/^"/, "", value)
sub(/"[[:space:]]*$/, "", value)
print value
found = 1
}
'
)"
if [ -z "$configured_lib_dir" ]; then
echo "[X] Could not resolve the ArchiveBox dependency library directory."
exit 1
fi
ABXPKG_LIB_DIR="$configured_lib_dir"
export ABXPKG_LIB_DIR
export PATH="$ABXPKG_LIB_DIR/env/bin:$PATH"
fix_root_install_ownership
}
prepare_abxpkg_environment
if resolve_setup_binary docker env false 2>/dev/null; then
DOCKER_BINARY="$ABXPKG_LIB_DIR/env/bin/docker"
hash -r
UV_BINARY="$(command -v uv || true)"
fi
DOCKER_IMAGE_READY="false"
if [ -n "$DOCKER_BINARY" ] && docker_pull_archivebox; then
DOCKER_IMAGE_READY="true"
if [ -z "$UV_BINARY" ]; then
echo "[X] uv was installed but is not available on PATH." >&2
echo " See https://docs.astral.sh/uv/getting-started/installation/" >&2
exit 1
fi
if [ "$DOCKER_IMAGE_READY" = "true" ] && "$DOCKER_BINARY" compose version > /dev/null; then
resolve_setup_curl
echo "[+] Initializing an ArchiveBox data folder at $ARCHIVEBOX_DATA_DIR using Docker Compose..."
ensure_archivebox_data_dir || exit 1
cd "$ARCHIVEBOX_HOME_DIR"
migrate_legacy_collection_dir
if [ ! -f docker-compose.yml ]; then
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
fi
export ARCHIVEBOX_IMAGE ARCHIVEBOX_PLATFORM
docker_compose_run_archivebox init
docker_compose_run_archivebox install
echo
echo "[+] Starting ArchiveBox server using: docker compose up -d..."
"$DOCKER_BINARY" compose up -d
wait_for_archivebox
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_HOME_DIR"
echo " ${FOLLOWUP_SUDO}docker compose ps"
echo " ${FOLLOWUP_SUDO}docker compose down"
echo " ${FOLLOWUP_SUDO}env ARCHIVEBOX_IMAGE=$ARCHIVEBOX_IMAGE docker compose pull"
echo " ${FOLLOWUP_SUDO}docker compose up"
echo " Open http://localhost:8000/admin/ to create the first admin and finish web setup."
echo " (When running remotely, replace localhost with this server's IP address or hostname.)"
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
echo "[+] Initializing an ArchiveBox data folder at $ARCHIVEBOX_DATA_DIR using Docker..."
ensure_archivebox_data_dir || exit 1
cd "$ARCHIVEBOX_HOME_DIR"
migrate_legacy_collection_dir
cd ./data
docker_run_archivebox_init
docker_run_archivebox_install
echo
echo "[+] Starting ArchiveBox server using: docker run -d $ARCHIVEBOX_IMAGE..."
"$DOCKER_BINARY" rm -f archivebox >/dev/null 2>&1 || true
docker_run_archivebox_server
wait_for_archivebox
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"
echo " ${FOLLOWUP_SUDO}docker ps --filter name=archivebox"
echo " ${FOLLOWUP_SUDO}docker rm -f archivebox"
echo " ${FOLLOWUP_SUDO}docker pull $ARCHIVEBOX_IMAGE"
echo " ${FOLLOWUP_SUDO}docker run $DOCKER_PLATFORM_ARGS -v $PWD:/data -d -p 8000:8000 --name=archivebox $ARCHIVEBOX_IMAGE"
echo " Open http://localhost:8000/admin/ to create the first admin and finish web setup."
echo " (When running remotely, replace localhost with this server's IP address or hostname.)"
echo " ${FOLLOWUP_SUDO}docker run $DOCKER_PLATFORM_ARGS -v $PWD:/data -it $ARCHIVEBOX_IMAGE add 'https://example.com'"
echo " ${FOLLOWUP_SUDO}docker run $DOCKER_PLATFORM_ARGS -v $PWD:/data -it $ARCHIVEBOX_IMAGE list"
echo " ${FOLLOWUP_SUDO}docker run $DOCKER_PLATFORM_ARGS -v $PWD:/data -it $ARCHIVEBOX_IMAGE help"
exit 0
fi
echo "[+] Installing ArchiveBox with uv..."
"$UV_BINARY" tool install --python "$ARCHIVEBOX_PYTHON" --prerelease explicit --upgrade "$ARCHIVEBOX_PACKAGE"
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 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)"
echo
sleep 12 || exit 1
echo "Proceeding with uv..."
echo
cat <<EOF
echo "[i] ArchiveBox Setup Script 📦"
echo
echo " This is a helper script which installs ArchiveBox and bootstraps its runtime dependencies."
echo " You may be prompted for a sudo password in order to install the following:"
echo
echo " - uv / curl / ca-certificates (as needed to bootstrap ArchiveBox)"
echo " - archivebox (installed by uv with Python $ARCHIVEBOX_PYTHON)"
echo " - extractor/plugin dependencies (installed/discovered by archivebox install)"
echo
echo " If you'd rather install these manually as-needed, you can find detailed documentation here:"
echo " https://github.com/ArchiveBox/ArchiveBox/wiki/Install"
echo
echo "Continuing in 12s... (press [Ctrl+C] to cancel)"
echo
sleep 12 || exit 1
echo "Proceeding to install ArchiveBox..."
echo
[+] ArchiveBox is installed. Initialize a collection when you are ready:
echo
mkdir -p ~/archivebox/data
cd ~/archivebox/data
archivebox init
archivebox install
resolve_setup_curl
install_archivebox_with_uv
echo
echo "[+] Initializing ArchiveBox data folder at $ARCHIVEBOX_DATA_DIR..."
ensure_archivebox_data_dir || exit 1
cd "$ARCHIVEBOX_HOME_DIR"
migrate_legacy_collection_dir
cd ./data
: | "$ARCHIVEBOX_BINARY" init # pipe in empty command to make sure stdin is closed
select_archivebox_lib_dir
"$ARCHIVEBOX_BINARY" install
# init shows version output at the end too
echo
echo "[+] Starting ArchiveBox server using: archivebox server --daemonize..."
"$ARCHIVEBOX_BINARY" server --daemonize 0.0.0.0:8000
wait_for_archivebox
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 0.0.0.0:8000 # start server process"
echo " Open http://localhost:8000/admin/ to create the first admin and finish web setup."
echo " (When running remotely, replace localhost with this server's IP address or hostname.)"
echo " ps aux | grep archivebox # see server process pid"
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"
echo " ${FOLLOWUP_SUDO}archivebox help # see more help & examples"
More install and usage options: https://github.com/ArchiveBox/ArchiveBox/wiki/Install
EOF

View File

@ -9,7 +9,7 @@ ArchiveBox is primarily distributed as a Python package installed with `uv`, but
- *[Supported Systems](#supported-systems)*
- Install Instructions
- **[Option A. Docker / Docker Compose ⭐️](#option-a-docker--docker-compose-setup-%EF%B8%8F)**
- [Option B. Automatic Setup Script](#option-b-automatic-setup-script)
- [Option B. uv Install Shortcut](#option-b-uv-install-shortcut)
- [Option C. System Package Manager Setup](#option-c-bare-metal-setup)
- *[Upgrading ArchiveBox to a new version](#upgrading-archivebox-to-a-new-version)*
- *[Next Steps](#next-steps)*
@ -66,9 +66,9 @@ It's also recommended to use a filesystem with compression and/or [deduplication
<br/>
## Option B. Automatic Setup Script
## Option B. uv Install Shortcut
If you're on macOS or Ubuntu, there is an optional auto-setup script provided.
On macOS, Linux, or BSD, `get.archivebox.io` is a shortcut for the `uv` install method.
*(or scroll further down for manual install instructions)*
@ -76,9 +76,7 @@ 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 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. Open the printed Admin UI URL to create the first admin. If `BASE_URL` is not configured yet, continue through the web setup wizard.
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.
The script installs `uv` when needed, then runs the same `uv tool install` command documented below. It does not initialize a collection, install runtime dependencies, or start a server; continue with the Quickstart after it finishes.
<img src="https://imgur.zervice.io/VMTzm0G.png" width="99%"/>
@ -94,7 +92,7 @@ After running the setup script, continue with the [Quickstart](https://github.co
## Option C. Bare Metal Setup
If you'd rather not use [Docker](https://github.com/ArchiveBox/ArchiveBox#%EF%B8%8F-easy-setup) or our [auto-install script](https://github.com/ArchiveBox/ArchiveBox#%EF%B8%8F-easy-setup), you can follow these manual setup instructions to install ArchiveBox and its dependencies using `uv`, `apt`, or Homebrew.
If you'd rather not use [Docker](https://github.com/ArchiveBox/ArchiveBox#%EF%B8%8F-easy-setup) or our [`uv` install shortcut](https://github.com/ArchiveBox/ArchiveBox#%EF%B8%8F-easy-setup), you can follow these manual setup instructions to install ArchiveBox and its dependencies using `uv`, `apt`, or Homebrew.
See our [Dependencies](https://github.com/ArchiveBox/ArchiveBox#dependencies) documentation to see the full list of dependencies and how they're used. Not all the dependencies are required for all modes. If you disable some archive methods you can skip installing those dependencies — for example, if you set [`MEDIA_ENABLED=False`](https://archivebox.github.io/abx-plugins/#media) you don't need to install `yt-dlp`, and if you set [`PDF_ENABLED=False`](https://archivebox.github.io/abx-plugins/#pdf), [`SCREENSHOT_ENABLED=False`](https://archivebox.github.io/abx-plugins/#screenshot), and [`DOM_ENABLED=False`](https://archivebox.github.io/abx-plugins/#dom) you don't need `chromium`.