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:
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