promote: dev → main (Forgejo on dev.jinemi.com; jinemi registry namespace) #152
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Jinemi/thermograph#152
Loading…
Reference in a new issue
No description provided.
Delete branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Promotes 15 commits / 51 files from
dev. Merging deploys beta on vps2.Why now
FORGEJO_DOMAINnow defaults todev.jinemi.com(#151). That default only reaches vps2's/opt/thermographcheckout via this hop — until it does, the Forgejo stack's liveROOT_URLdepends on whoever last randocker stack deployremembering to pass the env var, and a redeploy without it silently reverts the domain togit.thermograph.organd breaks the OAuth callback.What rides along
dev.jinemi.comserved alongsidegit.thermograph.orgon one Caddy site block, then made canonical (ROOT_URL, OAuth callback). Already applied live on vps1 and verified.jinemi/registry namespace.verify-parity --allmade host-aware./etc/centralis.envrendered from the vault.X-Forwarded-Proto.runner-vps2/files,.claude/settings.local.jsongitignored.Pre-flight on the one thing that could break the deploy
The namespace change (#150) flips the image-path default from
emi/thermograph/*tojinemi/thermograph/*in the stack files,deploy.shanddeploy-stack.sh. Beta and prod are both runningemi/...images at persisted tagsha-deb039ee249f, so a deploy that resolved the new path against a namespace with no images would fail the pull.Checked against the live registry from vps2 — all four exist:
emi/jinemi/thermograph/backend:sha-deb039ee249fthermograph/frontend:sha-deb039ee249fSo the namespace default can resolve either way without a missing-image failure.
Repos moved to the Jinemi org; the container packages did not follow, since Forgejo does not transfer packages with a repo. The deploy path still resolved `emi/thermograph/*` — a user_redirect to admin_emi — while build-push.yml derives its push path from ${github.repository}, now jinemi/thermograph. The next backend or frontend build would have published somewhere no deploy looks. Point the image paths at jinemi/thermograph/* (lowercase: OCI references admit no uppercase, which is why build-push.yml already pipes through tr), the clone URLs at Jinemi/thermograph, and the registry logins at admin_emi — the account that actually owns the tokens, rather than the redirect. The live tags and both ci-runner tags were copied into the Jinemi namespace first, so the switch has something to pull. thermograph-infra, thermograph-observability and the retired */app packages stay under admin_emi; they did not move.`docker login --password-stdin` strips exactly ONE trailing newline. `echo "$T"` adds one. So a secret pasted WITH a trailing newline -- which is what copying from a terminal, or an editor that terminates files with one, produces -- arrives at the registry as "<token>\n" and gets back: Error response from daemon: Get "https://.../v2/": denied: with no further detail. That is indistinguishable from a revoked or wrong token. On 2026-08-01 it stopped every build and deploy in the estate and cost an afternoon of diagnosis on a credential that was in fact valid: the same token, tested by hand, returned 200 on /v2/ and was issued a pull,push-scoped registry token for jinemi/thermograph/backend. So: strip leading/trailing whitespace and CR/LF before the pipe, and never `echo` a credential into stdin. The token now travels through the ENVIRONMENT rather than being interpolated into the script text. `${{ }}` is substituted before bash parses the line, so a value containing a quote or a newline changes the shape of the command itself, not merely its arguments. Two diagnostics, deliberately asymmetric: * empty/unset -> HARD FAILURE naming where to set it. There is no case where proceeding helps. * wrong shape -> WARNING only (length, and whether it is outside [0-9a-f]), then attempt the login anyway. A hard assertion on token format would block every build the day Forgejo changes that format, which is a worse failure than the one being prevented. The warning is enough to turn the registry's opaque "denied:" into a diagnosis. Neither diagnostic prints the value; only its length and character class. Note the username is not a factor: tested against the live registry, all of admin_emi, emi and jinemi authenticate identically with a valid token and are each issued a push-scoped token. Forgejo's container registry authenticates on the token, not the username.Forgejo evaluates ${{ }} expressions inside a step's `run:` script, comments included. An EMPTY expression is a parse error, and the runner's response is to drop the step -- no log line, no error, no failed status. The login step simply never ran, so every subsequent `docker push` went out anonymous. The registry then answers `unauthorized: reqPackageAccess` (or, depending on the client path, `no basic auth credentials`), which reads exactly like a revoked token or a missing scope. It is neither. Both are downstream of a comment. Isolated on one branch, one variable, back to back: * empty expression present -> both legs fail, no `Login Succeeded` in the log * empty expression removed -> both legs pass, `Login Succeeded` present, sha-df409f88b3fd published for backend and frontend Nothing was wrong with the credential. The token, its scope and whether it sat at repo or organization level were all ruled out first: pushes to jinemi/thermograph/* succeed by hand from vps1 and from the runner host, with matching and mismatched usernames, and the run log shows REGISTRY_TOKEN arriving in the job environment. Do not write a bare ${{ }} in a run block, in a comment or otherwise.