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`
|
||||
with per-user HTTP Basic Auth.
|
||||
|
||||
**Current deploy mechanism (as of 2026-08-25): manual.** Changes are copied
|
||||
to the server by hand (`scp`) and the container is rebuilt over SSH; the two
|
||||
copies of the repo (local Mac, server) are kept in sync by committing on
|
||||
both sides. There is currently no automated push-to-deploy path connected —
|
||||
see below.
|
||||
**Current deploy mechanism (live as of 2026-08-25): Forgejo push-to-deploy.**
|
||||
Self-hosted Forgejo (soft-fork of Gitea) runs as its own container on the
|
||||
same Unraid box (`/mnt/user/appdata/forgejo`), proxied at
|
||||
`https://git.hippofam.com` via Nginx Proxy Manager (wildcard Let's Encrypt
|
||||
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
|
||||
`post-receive` hook triggering a single root-owned deploy script via one
|
||||
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
|
||||
Forgejo (below). Nothing from it remains: the account, sudoers rule, bare
|
||||
repo, `/boot/config/go` persistence block, and the `sshd_config`
|
||||
`AllowUsers` addition were all removed and verified reverted. The local git
|
||||
remote and SSH keypair for it were deleted too. If this pattern (restricted
|
||||
git-shell account + sudo-scoped deploy script) is useful as a reference for
|
||||
scoping *other* access requests later, it's a solid model — it was
|
||||
abandoned for tooling reasons (moving to a real git host), not because it
|
||||
didn't work.
|
||||
end-to-end — then explicitly torn down in favor of Forgejo. Nothing from it
|
||||
remains: account, sudoers rule, bare repo, `/boot/config/go` persistence
|
||||
block, and the `sshd_config` `AllowUsers` addition were all removed and
|
||||
verified reverted. If this pattern (restricted git-shell account +
|
||||
sudo-scoped deploy script) is useful as a reference for scoping *other*
|
||||
access requests later, it's a solid model — 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
|
||||
(soft-fork of Gitea) as the git remote and CI/CD trigger, replacing the
|
||||
git-shell approach entirely. Plan, run by the user themselves (not by
|
||||
Claude — user explicitly wants to execute server-side setup steps
|
||||
personally):
|
||||
|
||||
1. Forgejo itself runs as its own Docker container on the same Unraid box
|
||||
(`/mnt/user/appdata/forgejo`), proxied at `git.hippofam.com` via Nginx
|
||||
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.
|
||||
All server access for this migration was narrowly scoped and time-boxed: a
|
||||
temporary root SSH key was generated solely to do the Forgejo/runner/NPM
|
||||
setup work above, then removed from the server's `authorized_keys` and
|
||||
deleted locally once the pipeline was verified end-to-end working — checked
|
||||
by confirming the key no longer authenticates. 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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue