Update CLAUDE.md: Forgejo pipeline is live, document runner internals and tradeoffs
All checks were successful
Deploy / deploy (push) Successful in 22s
All checks were successful
Deploy / deploy (push) Successful in 22s
This commit is contained in:
parent
49e3d20977
commit
c161cecc9d
1 changed files with 66 additions and 55 deletions
121
CLAUDE.md
121
CLAUDE.md
|
|
@ -70,66 +70,77 @@ container user (`99:100` / `nobody:users`), healthcheck, log rotation,
|
||||||
`init: true`. Reverse-proxied by Nginx Proxy Manager at `hippofam.com/cards`
|
`init: true`. Reverse-proxied by Nginx Proxy Manager at `hippofam.com/cards`
|
||||||
with per-user HTTP Basic Auth.
|
with per-user HTTP Basic Auth.
|
||||||
|
|
||||||
**Current deploy mechanism (as of 2026-08-25): manual.** Changes are copied
|
**Current deploy mechanism (live as of 2026-08-25): Forgejo push-to-deploy.**
|
||||||
to the server by hand (`scp`) and the container is rebuilt over SSH; the two
|
Self-hosted Forgejo (soft-fork of Gitea) runs as its own container on the
|
||||||
copies of the repo (local Mac, server) are kept in sync by committing on
|
same Unraid box (`/mnt/user/appdata/forgejo`), proxied at
|
||||||
both sides. There is currently no automated push-to-deploy path connected —
|
`https://git.hippofam.com` via Nginx Proxy Manager (wildcard Let's Encrypt
|
||||||
see below.
|
cert issued via Porkbun DNS-01 challenge — Cox blocks inbound port 80, so
|
||||||
|
the default HTTP-01 challenge doesn't work on this network; DNS challenge
|
||||||
|
sidesteps that entirely). Forgejo Actions is enabled instance-wide, with a
|
||||||
|
self-hosted runner (`/mnt/user/appdata/forgejo-runner`, container name
|
||||||
|
`forgejo-runner`) registered **globally** (not repo-scoped — see tradeoff
|
||||||
|
below).
|
||||||
|
|
||||||
**Superseded approach, fully torn down: git-shell restricted deploy.** A
|
This repo has a `forgejo` remote (`https://git.hippofam.com/ninja_hippo/card-grader.git`,
|
||||||
|
HTTPS not SSH — see note below) and pushing to `main` triggers
|
||||||
|
`.forgejo/workflows/deploy.yml`, which rsyncs the checkout into
|
||||||
|
`/mnt/user/appdata/card-grader` (excluding `.git`, `.env`, `data/` — those
|
||||||
|
stay server-side only) and runs `docker compose up -d --build`. Typical
|
||||||
|
deploy completes in well under a minute.
|
||||||
|
|
||||||
|
**Why HTTPS, not SSH, for git:** Forgejo's git-SSH listens on host port
|
||||||
|
`2222` (port 22 was already taken by Unraid's own sshd), but the router
|
||||||
|
only forwards 80/443 to this box — 2222 was never opened externally, and
|
||||||
|
setting that up requires the user's own router admin access, which Claude
|
||||||
|
doesn't have. Git auth instead uses a Forgejo access token stored in macOS
|
||||||
|
Keychain via `git config --global credential.helper osxkeychain` — pushing
|
||||||
|
just works, no per-push prompt.
|
||||||
|
|
||||||
|
**Runner internals, only useful if the pipeline breaks:** the runner's job
|
||||||
|
containers are the `catthehacker/ubuntu:act-latest` image (has git/Node
|
||||||
|
bundled for the `actions/checkout` JS action, but *not* rsync — installed
|
||||||
|
as an explicit workflow step each run) with the host Docker socket
|
||||||
|
automounted (`container.docker_host: automount` in the runner's
|
||||||
|
`config.yaml`) and `/mnt/user/appdata` bind-mounted into every job
|
||||||
|
container at the identical path (`container.options`), which also required
|
||||||
|
adding `/mnt/user/appdata` to `container.valid_volumes` (empty by default —
|
||||||
|
Forgejo silently drops any bind mount not explicitly allow-listed there,
|
||||||
|
with only a debug-log line, no visible job failure). The identical-path
|
||||||
|
mount trick matters because `docker compose`'s relative volume paths are
|
||||||
|
resolved by the *host* daemon (Docker-outside-of-Docker via the automounted
|
||||||
|
socket), so a mismatched path silently bind-mounts the wrong location.
|
||||||
|
|
||||||
|
**Known tradeoff, accepted, not yet resolved:** the runner is registered
|
||||||
|
globally rather than scoped per-repo, and it has full Docker socket access
|
||||||
|
— a workflow file pushed to *any* repo on this Forgejo instance can control
|
||||||
|
any container on the box, not just its own. The access boundary is "who
|
||||||
|
can push a workflow file to a repo on this instance," not anything
|
||||||
|
narrower. This runner also now serves three other projects on the same
|
||||||
|
box — `lunch-notifier`, `n64-tracker`, `arr-summary` — each with the
|
||||||
|
identical deploy pattern (see their own `CLAUDE.md`s). Revisit with a
|
||||||
|
`docker-socket-proxy` (scoped per compose project) if this needs tightening
|
||||||
|
later; not done yet because the basic pipeline mattered more first.
|
||||||
|
|
||||||
|
**Superseded, fully torn down: git-shell restricted deploy.** A
|
||||||
`carddeploy` service account (login shell `git-shell`, one bare repo, a
|
`carddeploy` service account (login shell `git-shell`, one bare repo, a
|
||||||
`post-receive` hook triggering a single root-owned deploy script via one
|
`post-receive` hook triggering a single root-owned deploy script via one
|
||||||
narrowly-scoped `sudo` rule) was built, tested, and verified working
|
narrowly-scoped `sudo` rule) was built, tested, and verified working
|
||||||
end-to-end — then explicitly torn down at the user's request in favor of
|
end-to-end — then explicitly torn down in favor of Forgejo. Nothing from it
|
||||||
Forgejo (below). Nothing from it remains: the account, sudoers rule, bare
|
remains: account, sudoers rule, bare repo, `/boot/config/go` persistence
|
||||||
repo, `/boot/config/go` persistence block, and the `sshd_config`
|
block, and the `sshd_config` `AllowUsers` addition were all removed and
|
||||||
`AllowUsers` addition were all removed and verified reverted. The local git
|
verified reverted. If this pattern (restricted git-shell account +
|
||||||
remote and SSH keypair for it were deleted too. If this pattern (restricted
|
sudo-scoped deploy script) is useful as a reference for scoping *other*
|
||||||
git-shell account + sudo-scoped deploy script) is useful as a reference for
|
access requests later, it's a solid model — abandoned for tooling reasons
|
||||||
scoping *other* access requests later, it's a solid model — it was
|
(moving to a real git host), not because it didn't work.
|
||||||
abandoned for tooling reasons (moving to a real git host), not because it
|
|
||||||
didn't work.
|
|
||||||
|
|
||||||
**In progress: Forgejo.** Decision made 2026-08-25 to self-host Forgejo
|
All server access for this migration was narrowly scoped and time-boxed: a
|
||||||
(soft-fork of Gitea) as the git remote and CI/CD trigger, replacing the
|
temporary root SSH key was generated solely to do the Forgejo/runner/NPM
|
||||||
git-shell approach entirely. Plan, run by the user themselves (not by
|
setup work above, then removed from the server's `authorized_keys` and
|
||||||
Claude — user explicitly wants to execute server-side setup steps
|
deleted locally once the pipeline was verified end-to-end working — checked
|
||||||
personally):
|
by confirming the key no longer authenticates. Treat that as the standing
|
||||||
|
expectation for any future server-access ask on this project — default to
|
||||||
1. Forgejo itself runs as its own Docker container on the same Unraid box
|
the smallest scope that does the job, time-box it, and confirm
|
||||||
(`/mnt/user/appdata/forgejo`), proxied at `git.hippofam.com` via Nginx
|
teardown/persistence explicitly rather than leaving it open-ended.
|
||||||
Proxy Manager.
|
|
||||||
2. This repo gets a `forgejo` remote and is pushed there (full history,
|
|
||||||
not a fresh copy).
|
|
||||||
3. Forgejo Actions (instance + repo level) is enabled, and a self-hosted
|
|
||||||
runner is registered **at the repo level** (not instance-wide) as its
|
|
||||||
own container (`/mnt/user/appdata/forgejo-runner`), with the host
|
|
||||||
Docker socket mounted in so it can rebuild the `card-grader` container.
|
|
||||||
4. A `.forgejo/workflows/deploy.yml` workflow (checkout + `docker compose
|
|
||||||
up -d --build` on push to `main`) does the actual deploy.
|
|
||||||
|
|
||||||
**Known tradeoff, not yet resolved:** unlike the git-shell mailbox (which
|
|
||||||
could only ever trigger one hardcoded script), a CI runner executes
|
|
||||||
whatever a workflow YAML says — mounting the Docker socket gives it control
|
|
||||||
over *any* container on the box, not just this one. The access boundary
|
|
||||||
becomes "who can push a workflow file to this repo" rather than a sudoers
|
|
||||||
rule. If tighter isolation is wanted later, a `docker-socket-proxy` in
|
|
||||||
front of the runner (scoped to just the `card-grader` compose project) was
|
|
||||||
proposed but not yet built — revisit if this matters more once the basic
|
|
||||||
pipeline is working.
|
|
||||||
|
|
||||||
**Status:** walkthrough delivered to the user 2026-08-25; not yet executed.
|
|
||||||
**Do not assume any of this is live** — confirm current state before
|
|
||||||
relying on it or making claims about deploy mechanism to the user.
|
|
||||||
|
|
||||||
A prior root SSH key/access to the whole box was explicitly revoked by the
|
|
||||||
user before any of the above; every access grant since then (including a
|
|
||||||
one-time temporary root key used only to run the git-shell setup script,
|
|
||||||
itself removed immediately after) has been narrowly scoped and time-boxed
|
|
||||||
on request. Treat that as the standing expectation for any future
|
|
||||||
server-access ask on this project — default to the smallest scope that
|
|
||||||
does the job, time-box it, and confirm teardown/persistence explicitly
|
|
||||||
rather than leaving it open-ended.
|
|
||||||
|
|
||||||
## Conventions and constraints established for this project
|
## Conventions and constraints established for this project
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue