Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit cee23eea84b8a1fbec291fa4038574dcf50f8982
parent b2c76161e1120c771fbea7cb8cf37e905aba7171
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue,  6 Oct 2026 08:33:09 -0400

docs: RUNNING_IN_DOCKER — publishing runs in the container (exec, not run --rm), Cloudflare from .env, the homepage and its /source mount, substituting yt-dlp, the two runners, a Windows checklist

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
MRUNNING_IN_DOCKER.md | 209++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 187 insertions(+), 22 deletions(-)

diff --git a/RUNNING_IN_DOCKER.md b/RUNNING_IN_DOCKER.md @@ -5,10 +5,12 @@ One command stands up a working archive server: the editor, the tools it drives Vulkan image), and a reverse proxy that is the only thing on the box with an open port. -> This document is about **running the apps**. Fanning per-site *export builds* out -> across containers is a different thing entirely — it uses `Dockerfile.build`, and -> it is in [PUBLISH.md](PUBLISH.md#building-every-site-in-containers). Neither -> affects the other. +> This document is about **running the apps** — publishing included: every publish +> stage (index, build, deploy) runs inside the editor's container. Fanning per-site +> *export builds* out across containers is a different thing — the opt-in docker +> runner of a HOST install, from `Dockerfile.build`, in +> [PUBLISH.md](PUBLISH.md#building-every-site-in-containers). Neither affects the +> other. --- @@ -194,7 +196,10 @@ it accurately. ### Publish the archive The site server serves a *static build of your corpus*, which does not exist -until you make one: +until you make one. Publishing is a set of **stages** — update the index, build a +site, deploy it — and every one of them runs **inside the editor's container**: +the queued jobs the editor starts (Sites → Publish, the publish lane) and the +commands below alike. The quick way, for the local `site` service: ```sh docker compose exec editor /repo/docker/publish-site.sh <site-id> @@ -202,9 +207,88 @@ docker compose --profile site up -d ``` Create the site itself in the editor first (**Sites → New**); run the script with -no argument to list the ones you have. It runs the same `pnpm --filter export run -build` a host install runs, then copies the output into the volume the `site` -service serves. No restart needed afterwards. +no argument to list the ones you have. The script is three stages in a row, and +you can run them yourself: + +```sh +docker compose exec editor pnpm archilyzer publish index +docker compose exec editor pnpm archilyzer publish build <site-id> +docker compose exec editor pnpm archilyzer publish deploy <site-id> --to local +docker compose exec editor pnpm archilyzer publish status +``` + +A stage that is already fresh does nothing, so running one twice is cheap. A +local deploy copies the built bundle into the volume the `site` service serves; +no restart needed afterwards. A **private** site (`audience: "private"`) is +built but never deployed, locally or anywhere else. + +**`exec`, never `run --rm`.** `docker compose run --rm editor …` starts a SECOND +container with its own copy of the image's `export/public` and a second writer on +the index, and the publish lock (which keeps a stage you start from colliding +with one the editor is running) cannot see across containers. `exec` runs in the +editor's own container, beside its jobs, under the same lock. `pnpm ops publish` +from the host goes through the editor too. + +#### Deploying to Cloudflare from the container + +The same stages deploy to Cloudflare Pages. The container has no browser for +`wrangler login`, so it authenticates with a token from `.env`, which the editor +reads and every stage it runs inherits: + +```sh +# .env +CLOUDFLARE_API_TOKEN=... # an API token with "Cloudflare Pages: Edit" +CLOUDFLARE_ACCOUNT_ID=... +# only when Settings → archive storage names an R2 bucket for oversize archives: +R2_ACCESS_KEY_ID=... +R2_SECRET_ACCESS_KEY=... +``` + +```sh +docker compose up -d # picks up .env +docker compose exec editor pnpm archilyzer publish deploy <site-id> --preview <branch> +docker compose exec editor pnpm archilyzer publish deploy <site-id> # production +``` + +Nothing is fetched at deploy time: wrangler is pinned in the workspace the image +ships. With no token a deploy is refused before wrangler runs, with a sentence +saying so; `archilyzer doctor` reports whether each credential is **set** (never +its value). Every deploy is checked live afterwards. PUBLISH.md has the whole +publish flow; this is only what is different in a container. + +#### The homepage, and its /source mirror + +The image carries a build of the project's own homepage, served by the +`homepage` service. A real one — the corpus's numbers, the `/source` mirror of +this repo — is a publish like any other: + +```sh +docker compose exec editor pnpm archilyzer publish homepage --deploy --to local +docker compose --profile homepage restart homepage # once, after the first +``` + +The `homepage` service serves the local deploy (`/data/builds/homepage`) once +there is one, else the baked build — chosen at boot, hence the one restart. + +The `/source` mirror needs two things the image deliberately lacks: + +- **The repository.** The image has no `.git`. `docker-compose.source.yml` mounts + the host's git directory read-only at `/data/source.git` and points + `ARCHILYZER_SOURCE_REPO` at it (default `./.git`; from a git worktree set + `ARCHILYZER_SOURCE_HOST_DIR` to the primary checkout's `.git`). Without it the + homepage builds with an empty `/source` page. +- **The operator's scrub rules and denylist.** They live in the config volume — + `ARCHILYZER_CONFIG_DIR` is `/data/config/archilyzer` in the container, made + (mode 700, empty) on the first boot — and are never printed by anything: + +```sh +docker compose -f docker-compose.yml -f docker-compose.source.yml up -d +docker compose cp ~/.config/archilyzer/source-scrub.txt editor:/data/config/archilyzer/ +docker compose cp ~/.config/archilyzer/source-denylist.txt editor:/data/config/archilyzer/ +docker compose exec editor chmod 600 /data/config/archilyzer/source-scrub.txt /data/config/archilyzer/source-denylist.txt +``` + +`git-filter-repo` (pinned) is in the image. ### Model choice @@ -230,6 +314,49 @@ boot self-updates it. Or, once: docker compose exec editor yt-dlp -U ``` +Every editor boot logs which yt-dlp it will run, and whether it runs: + +``` +[entrypoint] yt-dlp: /usr/local/bin/yt-dlp 2026.09.30 (image) +``` + +### Substituting yt-dlp + +To run your own yt-dlp — a patched build, say — set `YTDLP_BIN`. It is a swap at +run time: nothing is rebuilt, and the image's release binary stays where it is +(`ARCHILYZER_IMAGE_YTDLP`, `/usr/local/bin/yt-dlp`). Two ways: + +**A source checkout**, run with the image's python (yt-dlp's optional modules +are installed beside it): + +```sh +# .env +YTDLP_SOURCE_HOST_DIR=/home/you/yt-dlp-patched # the directory holding yt_dlp/ +``` + +```sh +docker compose -f docker-compose.yml -f docker-compose.ytdlp.yml up -d +``` + +The overlay mounts the checkout read-only at `/opt/yt-dlp-src` and sets +`YTDLP_BIN=/usr/local/bin/yt-dlp-from-source`, a wrapper baked into the image. + +**A zipapp** built on the host (`make yt-dlp` in the checkout), copied into the +config volume: + +```sh +docker compose exec editor mkdir -p /data/config/bin +docker compose cp ./yt-dlp editor:/data/config/bin/yt-dlp +docker compose exec editor chmod 755 /data/config/bin/yt-dlp +# .env +YTDLP_BIN=/data/config/bin/yt-dlp +``` + +Either way the boot line says `(override)`, and `YTDLP_AUTO_UPDATE` leaves an +override alone with a warning — update it where you build it. A substitute that +does not run is reported as `yt-dlp: MISSING — … does not run: …` (the editor +still starts), and by `archilyzer doctor`. + ### Driving the editor without a browser Every editor gesture is a server action, which is fine for a person and hostile @@ -511,6 +638,8 @@ The `site` profile does not need the mount: an export build never reads `data/`. docker compose logs -f editor docker compose exec editor bash # a shell in the image docker compose exec editor yt-dlp --version +docker compose exec editor pnpm archilyzer doctor # what this container can do +docker compose exec editor pnpm archilyzer publish status docker compose restart editor docker compose down # stop; volumes survive docker compose down -v # stop AND DELETE the corpus @@ -523,7 +652,10 @@ docker compose down -v # stop AND DELETE the corp **Baked in:** node + the installed workspace, the built editor / umtool / homepage, `yt-dlp`, a JavaScript runtime for it (`deno` — without one yt-dlp warns and silently loses formats), `ffmpeg`/`ffprobe`, `whisper-cli` (statically linked), -`zip`/`tar`/`xz`/`gzip`, `rsync`, `git`, `curl`, `ps`. The Vulkan image adds +`zip`/`tar`/`xz`/`gzip`, `rsync`, `git`, `curl`, `ps`; `python3` with yt-dlp's +optional modules (for a substituted yt-dlp), `pipx` and a pinned `git-filter-repo` +(the homepage's source mirror), and — in the workspace's `node_modules` — a +pinned `wrangler`, so a deploy fetches nothing. The Vulkan image adds `parakeet-cli`, the Mesa Vulkan drivers and `vulkaninfo`. **Not baked, on purpose:** @@ -532,7 +664,14 @@ silently loses formats), `ffmpeg`/`ffprobe`, `whisper-cli` (statically linked), in the `corpus` volume. - **whisper models.** 142 MB to 3 GB, and the choice is yours. Fetched on boot. - **The export site build.** It is a static render *of a corpus*, and there is no - corpus at image-build time. `docker/publish-site.sh` makes it at run time. + corpus at image-build time. The publish stages make it at run time. +- **Credentials.** `CLOUDFLARE_API_TOKEN` and friends come from `.env` at run + time, never from the image. +- **The repository.** No `.git`; `docker-compose.source.yml` mounts the host's + for the `/source` mirror. The build's commit and branch are baked as + `ARCHILYZER_COMMIT`/`ARCHILYZER_BRANCH` when the build passes them + (`ARCHILYZER_COMMIT=$(git rev-parse HEAD) ARCHILYZER_BRANCH=$(git branch + --show-current) docker compose build`) — the publish stamps record them. - **ImageMagick with Pango, and `qrencode`.** Only `umtool/report-to-video/` needs them, and rendering a report to video is a workstation task, not something a server does. Run that part on a host checkout. @@ -542,20 +681,21 @@ silently loses formats), `ffmpeg`/`ffprobe`, `whisper-cli` (statically linked), Point `OLLAMA_URL` at a host ollama (`http://host.docker.internal:11434`) if you want digests. -### The multi-site build pipeline falls back inside a container +### Two build runners, and the container has one -The editor's **Build all sites** fans per-site export builds out across containers -whenever a container engine answers (see [PUBLISH.md](PUBLISH.md#building-every-site-in-containers)). Inside a container there -is no `docker` binary, so that path is unavailable. It already handles this — the -build logs +Every site build goes through one stage contract with two runners: -``` -[notice] No container engine available — building sites serially on the host. -``` +- **local** — the default everywhere, in a container or not: each stage is a child + process of the editor (or of the CLI you ran), one at a time. +- **docker** — a HOST install's opt-in fan-out (`settings.publish.runner: + "docker"`, or `publish build all --runner docker`): one `Dockerfile.build` + container per site (see [PUBLISH.md](PUBLISH.md#building-every-site-in-containers)). + Both write the same bundles and stamps, so a deploy does not care which built it. -and does the builds one after another in-process. Correct, just not parallel. **Do -not** try to make docker-in-docker work for this; run the parallel pipeline from a -host checkout if you need it. +Inside the container there is no engine, and the docker runner **refuses** ("the +docker runner needs an engine on this host") rather than pretend. The editor never +gets the docker socket — **do not** mount it, and do not try to make +docker-in-docker work; run the fan-out from a host checkout if you want it. ### It runs as root @@ -582,6 +722,23 @@ Open http://localhost:8081. Keep the clone on the Windows filesystem — the bui context is small, and the corpus lives in a Docker volume inside the WSL2 VM, which is where the I/O actually happens. +Publishing needs nothing else on Windows — Docker Desktop is the whole +requirement. A checklist, in PowerShell from the clone: + +1. `copy .env.example .env`, then set in `.env`: `WORKER_TOKEN` (for `pnpm ops` + and agents), `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` (to deploy). +2. `docker compose up -d --build` +3. Open http://localhost:8081 → **Sites** → **Publish now**, or: + `docker compose exec editor pnpm archilyzer publish now` +4. A local preview: `docker compose --profile site up -d site`, then + `docker compose exec editor pnpm archilyzer publish deploy <site-id> --to local`, + and open http://localhost:8080. +5. `docker compose exec editor pnpm archilyzer doctor` — every check, including + whether the Cloudflare token is set (never its value). +6. Optional, your own yt-dlp: set `YTDLP_SOURCE_HOST_DIR` (a Windows path such as + `C:\src\yt-dlp` works) and run + `docker compose -f docker-compose.yml -f docker-compose.ytdlp.yml up -d`. + You still want WSL2 directly if you intend to *develop* the project or run Claude Code against it — see [README.md](README.md#claude-code-on-windows). For running an archive, this is enough. @@ -609,7 +766,15 @@ comparison above. The usual cause is a missing `/dev/dri` — a container withou the render node still transcribes, just slowly. **Downloads started failing on every channel.** Almost always a stale yt-dlp: -`docker compose exec editor yt-dlp -U`. +`docker compose exec editor yt-dlp -U`. Check `docker compose logs editor | grep +yt-dlp:` first — `(override)` means `YTDLP_BIN` names your own build, which `-U` +does not touch, and `MISSING` means it does not run at all (a from-source +override with no checkout mounted, say). + +**A deploy is refused before wrangler runs.** `CLOUDFLARE_API_TOKEN` is not set in +`.env`, or the editor was started before it was: `docker compose up -d` again +(a restart does not re-read `.env`; `up` recreates the container). +`archilyzer doctor`'s `cloudflare-auth` line says which. **Port 8080 is already taken.** `SITE_HTTP_PORT=9080` in `.env`. Those variables move the *outside* port; nothing inside the containers changes.