diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6c4f628 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +# Exclude runtime config and data directories from the Docker build context. +# The config/ directory may contain secrets (e.g. twitter-cookies.txt) that are +# only needed at runtime via a volume mount — they must never reach the builder. +config/ +docker/ + +# Development and VCS noise +.git/ +.gitignore diff --git a/.gitignore b/.gitignore index 7dbca8c..2f797ce 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ !NEXT.md !Dockerfile +!.dockerignore !docker-compose.yml !docker/ !docker/** diff --git a/Dockerfile b/Dockerfile index 6a451d2..7602011 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,18 +44,24 @@ FROM debian:bookworm-slim # Runtime dependencies: # chromium used by single-file-cli for full-page archiving -# nodejs + npm runtime for single-file-cli +# nodejs (20+) runtime for single-file-cli (requires Node >=20; Debian +# bookworm ships 18, so we install from the NodeSource repo) +# ffmpeg required by yt-dlp to merge separate audio/video streams +# (e.g. YouTube bestvideo+bestaudio format selection) # python3 + pip + venv twitter scraper -# ca-certificates outbound HTTPS from the server +# ca-certificates outbound HTTPS from the server and NodeSource HTTPS # libssl3 OpenSSL linked by the Rust binary RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y --no-install-recommends \ chromium \ nodejs \ - npm \ + ffmpeg \ python3 \ python3-pip \ python3-venv \ - ca-certificates \ libssl3 \ && rm -rf /var/lib/apt/lists/* @@ -88,10 +94,13 @@ ENV ARCHIVR_STATIC_DIR=/usr/share/archivr-server/static \ ARCHIVR_SINGLE_FILE=/usr/local/bin/single-file \ ARCHIVR_TWEET_PYTHON=/opt/archivr-venv/bin/python3 \ ARCHIVR_TWEET_SCRAPER=/usr/local/lib/archivr/scrape_user_tweet_contents.py \ - ARCHIVR_YT_DLP=/opt/archivr-venv/bin/yt-dlp + ARCHIVR_YT_DLP=/opt/archivr-venv/bin/yt-dlp \ + ARCHIVR_CHROME_ARGS=--no-sandbox EXPOSE 8080 # Expects the TOML config at /config/archivr-server.toml (mount a volume). # Copy docker/config.example.toml as a starting point. -ENTRYPOINT ["archivr-server", "/config/archivr-server.toml"] +# Using CMD (not ENTRYPOINT) so `docker compose run archivr archivr init …` +# can override the whole command for first-time archive initialisation. +CMD ["archivr-server", "/config/archivr-server.toml"] diff --git a/crates/archivr-core/src/downloader/singlefile.rs b/crates/archivr-core/src/downloader/singlefile.rs index d50d554..4e19110 100644 --- a/crates/archivr-core/src/downloader/singlefile.rs +++ b/crates/archivr-core/src/downloader/singlefile.rs @@ -68,10 +68,22 @@ fn save_with( // without a writable user-data-dir. Using a subdirectory of temp_dir // keeps it isolated and it gets cleaned up with the rest of the temp dir. let chrome_data_dir = temp_dir.join("chrome-data"); - let browser_args = format!( - "[\"--disable-web-security\",\"--user-data-dir={}\"]", - chrome_data_dir.display() - ); + // Build the browser-args JSON array. Start with the flags always required, + // then append any extra flags from ARCHIVR_CHROME_ARGS (space-separated). + // Docker containers running as root need "--no-sandbox" here because + // Chromium refuses to start as root without it. + let mut chrome_flags = vec![ + "--disable-web-security".to_string(), + format!("--user-data-dir={}", chrome_data_dir.display()), + ]; + if let Ok(extra) = std::env::var("ARCHIVR_CHROME_ARGS") { + chrome_flags.extend(extra.split_whitespace().filter(|s| !s.is_empty()).map(str::to_string)); + } + let quoted: Vec = chrome_flags + .iter() + .map(|f| format!("\"{}\"", f.replace('\\', "\\\\").replace('"', "\\\""))) + .collect(); + let browser_args = format!("[{}]", quoted.join(",")); let out = Command::new(single_file) .arg(url) diff --git a/docs/README.md b/docs/README.md index 1d796bb..e9b616f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -209,7 +209,7 @@ A `Dockerfile` and `docker-compose.yml` are provided for self-hosting without Ni The image includes the `archivr` CLI for this purpose: ```sh - docker compose run --rm archivr archivr init /data/archives/main --name "Main Archive" + docker compose run --rm archivr archivr init /data/archives/main /data/archives/main/.archivr/store --name "Main Archive" ``` This creates `/data/archives/main/.archivr/` with the metadata the server requires. @@ -300,6 +300,11 @@ dependencies (Chromium, Node.js, Python) land in the final layer. - `ARCHIVR_CHROME` - Optional. - Overrides the Chromium/Chrome executable passed to `single-file` via `--browser-executable-path`. Set automatically by the Nix wrapper and the Docker image. Default: `chromium`. +- `ARCHIVR_CHROME_ARGS` + - Optional. + - Space-separated extra flags appended to Chromium's `--browser-args`. The Docker + image sets this to `--no-sandbox` because Chromium refuses to run as root without + it. Leave unset when running natively (Nix, Linux desktop). - `ARCHIVR_TWITTER_CREDENTIALS_FILE` - Required for tweet/thread scraping inputs such as `tweet:ID` and `x:thread:ID`. - Must point to a cookies file for the vendored scraper.