Archilyzer · Source

archilyzer

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

commit 202144a874eee88edb8e78c2b76a1a0bc7415aa9
parent ffc39afe8fe978be00000a7623bece22d2c9e9af
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue,  6 Oct 2026 09:33:06 -0400

docker: the editor's publish-lock identity is fixed (ARCHILYZER_HOST_ID=archilyzer-editor) — a container's hostname changes on every recreate

On the editor service only: another container with the same id and its own pid
namespace would judge the editor's live lock dead. envVars row (read by S1's
stageLock.ts); RUNNING_IN_DOCKER.md says how a foreign-host lock is cleared.

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

Diffstat:
MENVIRONMENT.md | 1+
MRUNNING_IN_DOCKER.md | 13++++++++++++-
Mcommon/lib/envVars.ts | 1+
Mdocker-compose.yml | 7+++++++
4 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md @@ -61,6 +61,7 @@ Tokens, credentials and knobs a running process reads. Most configuration is not | `R2_SECRET_ACCESS_KEY` | — | See `R2_ACCESS_KEY_ID`. | common/publish/build.ts, common/bin/doctor.ts (set or not) | | `CLOUDFLARE_ACCOUNT_ID` | — | The Cloudflare account: the R2 endpoint's, and the one wrangler deploys to when the token can see more than one. In Docker it comes from `.env`. | common/publish/build.ts, wrangler, common/bin/doctor.ts (set or not) | | `CLOUDFLARE_API_TOKEN` | unset (wrangler's own `wrangler login` config, on a host) | The API token every deploy's wrangler authenticates with (Cloudflare Pages: Edit). The way a container deploys — there is no browser for `wrangler login` in one; set it in `.env`. | wrangler (every deploy), common/bin/doctor.ts (set or not, never the value) | +| `ARCHILYZER_HOST_ID` | the hostname | Which host the publish lock (`<EXPORT_BUILDS_DIR>/.publish.lock`) names as its holder's: a lock from this host whose pid is dead is stale and taken over; another host's is waited on. docker-compose.yml fixes it for the editor (`archilyzer-editor`), whose hostname is a container id that changes on every recreate. | common/publish/stageLock.ts (the publish lock) | | `ARCHILYZER_SOURCE_REPO` | this checkout's git common dir | The git DIR `archilyzer source publish` mirrors `main` from, when the checkout has none: in Docker, `/data/source.git`, the host's git common dir mounted read-only by docker-compose.source.yml. A value that names nothing refuses the publish. | common/publish/source.ts, common/bin/doctor.ts, docker/entrypoint.sh | | `YTDLP_SOURCE_HOST_DIR` | — (required by the overlay) | Docker: the HOST path of a yt-dlp source checkout (the directory holding `yt_dlp/`), mounted read-only at `/opt/yt-dlp-src` by docker-compose.ytdlp.yml. See [RUNNING_IN_DOCKER.md](RUNNING_IN_DOCKER.md), "Substituting yt-dlp". | docker-compose.ytdlp.yml | | `YTDLP_AUTO_UPDATE` | off | Docker: `1` runs `yt-dlp -U` on every editor boot — on the image's yt-dlp only; an override (`YTDLP_BIN` naming another) is left alone, with a warning. | docker/entrypoint.sh, common/bin/doctor.ts | diff --git a/RUNNING_IN_DOCKER.md b/RUNNING_IN_DOCKER.md @@ -225,10 +225,21 @@ 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 +with one the editor is running) cannot see across containers — worse, the second +container carries the editor's host identity below with its own pids, so it would +judge the editor's live lock dead and take it. `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. +The lock names its holder by host and pid. A container's hostname changes every +time it is recreated, so compose gives the editor a fixed identity +(`ARCHILYZER_HOST_ID=archilyzer-editor`): a lock left by a stage that died with the +container is then recognised as this editor's own, and taken over once its pid is +gone. A lock naming any OTHER host is waited on, never taken. If one is left +behind — say, from before this setting, or by a host install sharing the volume +that is gone for good — and you are sure nothing is publishing, delete it: +`docker compose exec editor rm /data/builds/.export-builds/.publish.lock`. + #### Deploying to Cloudflare from the container The same stages deploy to Cloudflare Pages. The container has no browser for diff --git a/common/lib/envVars.ts b/common/lib/envVars.ts @@ -97,6 +97,7 @@ const DECLARED: EnvVarDecl[] = [ { name: "R2_SECRET_ACCESS_KEY", audience: "runtime", default: "—", readBy: "common/publish/build.ts, common/bin/doctor.ts (set or not)", doc: "See `R2_ACCESS_KEY_ID`." }, { name: "CLOUDFLARE_ACCOUNT_ID", audience: "runtime", default: "—", readBy: "common/publish/build.ts, wrangler, common/bin/doctor.ts (set or not)", doc: "The Cloudflare account: the R2 endpoint's, and the one wrangler deploys to when the token can see more than one. In Docker it comes from `.env`." }, { name: "CLOUDFLARE_API_TOKEN", audience: "runtime", default: "unset (wrangler's own `wrangler login` config, on a host)", readBy: "wrangler (every deploy), common/bin/doctor.ts (set or not, never the value)", doc: "The API token every deploy's wrangler authenticates with (Cloudflare Pages: Edit). The way a container deploys — there is no browser for `wrangler login` in one; set it in `.env`." }, + { name: "ARCHILYZER_HOST_ID", audience: "runtime", default: "the hostname", readBy: "common/publish/stageLock.ts (the publish lock)", doc: "Which host the publish lock (`<EXPORT_BUILDS_DIR>/.publish.lock`) names as its holder's: a lock from this host whose pid is dead is stale and taken over; another host's is waited on. docker-compose.yml fixes it for the editor (`archilyzer-editor`), whose hostname is a container id that changes on every recreate." }, { name: "ARCHILYZER_SOURCE_REPO", audience: "runtime", default: "this checkout's git common dir", readBy: "common/publish/source.ts, common/bin/doctor.ts, docker/entrypoint.sh", doc: "The git DIR `archilyzer source publish` mirrors `main` from, when the checkout has none: in Docker, `/data/source.git`, the host's git common dir mounted read-only by docker-compose.source.yml. A value that names nothing refuses the publish." }, { name: "YTDLP_SOURCE_HOST_DIR", audience: "runtime", default: "— (required by the overlay)", readBy: "docker-compose.ytdlp.yml", doc: "Docker: the HOST path of a yt-dlp source checkout (the directory holding `yt_dlp/`), mounted read-only at `/opt/yt-dlp-src` by docker-compose.ytdlp.yml. See [RUNNING_IN_DOCKER.md](RUNNING_IN_DOCKER.md), \"Substituting yt-dlp\"." }, { name: "YTDLP_AUTO_UPDATE", audience: "runtime", default: "off", readBy: "docker/entrypoint.sh, common/bin/doctor.ts", doc: "Docker: `1` runs `yt-dlp -U` on every editor boot — on the image's yt-dlp only; an override (`YTDLP_BIN` naming another) is left alone, with a warning." }, diff --git a/docker-compose.yml b/docker-compose.yml @@ -118,6 +118,13 @@ services: command: ["editor"] environment: <<: *app-env + # The publish lock's host identity (common/publish/stageLock.ts). Its + # default, the hostname, is the container id here and changes on every + # recreate, so a lock left by a crashed stage would look like another + # host's forever. Fixed, so this editor recognises its own stale lock. + # The EDITOR only, deliberately: another container with the same id but + # its own pid namespace would judge the editor's live lock dead. + ARCHILYZER_HOST_ID: archilyzer-editor volumes: - corpus:/data/transcripts - config:/data/config