diff --git a/README.md b/README.md
index 8c6735b6..b5e86667 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,7 @@ archivebox install
# archivebox server 0.0.0.0:8000
-# 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
@@ -208,18 +208,17 @@ See below for more usage examples using the C
-
bash auto-setup script (macOS/Linux)
+
bash uv install shortcut (macOS/Linux/BSD)
-- Install Docker on your system (optional, highly recommended but not required).
-- Run the automatic setup script.
+
- Run the shortcut for the
uv install method. It installs uv first when needed.
curl -fsSL 'https://get.archivebox.io' | bash
For more info, see Install: Bare Metal in the Wiki. ➡️
See below for more usage examples using the CLI, Web UI, or filesystem/SQL/Python to manage your archive.
-See setup.sh for the source code of the auto-install script.
+See setup.sh for the source code of the uv install shortcut.
See "Against curl | sh as an install method" blog post for my thoughts on the shortcomings of this install method.
@@ -340,7 +339,7 @@ See below for more usage examples using the C
- Arch:
yay -S archivebox (contributed by @imlonghao, maintained by @jasongodev)
-- FreeBSD:
curl -fsSL 'https://get.archivebox.io' | bash (uses pkg + uv under-the-hood)
+- FreeBSD:
curl -fsSL 'https://get.archivebox.io' | bash (uses uv)
- Nix:
nix-env --install archivebox (contributed by @siraben)
- Guix:
guix install archivebox (contributed by @rakino)
- More: contribute another distribution...!
diff --git a/archivebox/tests/test_setup_script.py b/archivebox/tests/test_setup_script.py
index bae0e801..c2dfad64 100644
--- a/archivebox/tests/test_setup_script.py
+++ b/archivebox/tests/test_setup_script.py
@@ -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 <=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
diff --git a/bin/bump_version.sh b/bin/bump_version.sh
index 6f8d11f6..ff122c48 100755
--- a/bin/bump_version.sh
+++ b/bin/bump_version.sh
@@ -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
diff --git a/bin/setup.sh b/bin/setup.sh
index 104e0b8a..1f176cd8 100755
--- a/bin/setup.sh
+++ b/bin/setup.sh
@@ -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 <
-## 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.
@@ -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`.