Document the Gitea Actions CI setup and the follow-up security review
Adds CONTINUOUS_INTEGRATION.md (mirrors HOP's CI doc structure), links it from README, and records the CI changelog entry plus the follow-up review's open findings (scheduler crash-loop, System.gc(), connection pooling, the PostgresService field-routing invariant, and the mislabeled main-method "tests") in the roadmap's Phase 2/Phase 5 checklists.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# Continuous integration
|
||||
|
||||
Last verified: 2026-08-24
|
||||
|
||||
## Current topology
|
||||
|
||||
WeatherTool uses Gitea Actions for continuous integration:
|
||||
|
||||
```text
|
||||
bot/WeatherTool repository on gitea.packet.garden
|
||||
→ repository-scoped weathertool-ci-rocky-01 runner on the coding PC
|
||||
→ dedicated Docker-in-Docker daemon
|
||||
→ disposable per-job containers (backend: hseeberger/scala-sbt, frontend: node:20-bookworm)
|
||||
```
|
||||
|
||||
Gitea does not execute workflow commands itself. The runner polls Gitea for
|
||||
jobs and executes them. The runner is registered only to `bot/WeatherTool`;
|
||||
it is not a user-, organization-, or instance-wide runner.
|
||||
|
||||
The runner host is the Rocky Linux coding PC — the same machine already used
|
||||
for all compile/build/test/staging work. This is a second, independent
|
||||
runner instance on that host: HOP already runs its own Forgejo runner there
|
||||
(`hop-forgejo-runner-*`, registered to a different Forgejo instance's
|
||||
`bot/hop` repo). The two don't conflict — each is a separate long-polling
|
||||
client registered to a different server with its own token, own Docker
|
||||
network, and own Docker-in-Docker daemon; they only share host CPU/RAM,
|
||||
which is a non-issue for occasional CI runs.
|
||||
|
||||
## Isolation boundary
|
||||
|
||||
Same pattern as HOP's Forgejo runner: the runner uses a dedicated
|
||||
Docker-in-Docker daemon rather than exposing the coding PC's normal Docker
|
||||
socket to workflow containers. The nested daemon:
|
||||
|
||||
- runs in its own privileged container because a nested Docker daemon
|
||||
requires that capability;
|
||||
- exposes no port on the host or LAN;
|
||||
- is reachable only through the runner's private Compose network; and
|
||||
- stores its images and state in the `weathertool-gitea-runner_dind_data`
|
||||
volume.
|
||||
|
||||
This prevents ordinary WeatherTool workflow jobs from enumerating or
|
||||
mutating the coding PC's normal development containers (including the
|
||||
Rocky staging stack itself).
|
||||
|
||||
## Host-local runner files
|
||||
|
||||
The active runner is operated from:
|
||||
|
||||
```text
|
||||
/home/sandbox/.local/share/weathertool-gitea-runner/
|
||||
compose.yml
|
||||
runner-token
|
||||
```
|
||||
|
||||
The directory is mode `0700`. `runner-token` is mode `0600`. It is
|
||||
deliberately outside the WeatherTool Git repository and was written directly
|
||||
to disk from an interactive prompt — it was never printed, pasted into
|
||||
chat, or copied into project documentation.
|
||||
|
||||
Normal operator commands are run from the directory above:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
docker compose ps
|
||||
docker compose logs --tail=100 runner
|
||||
docker compose pull
|
||||
```
|
||||
|
||||
The expected steady state is:
|
||||
|
||||
- `docker-in-docker`: `Up (healthy)`;
|
||||
- `runner`: `Up`; and
|
||||
- `weathertool-ci-rocky-01`: `Idle` in Gitea when no job is queued.
|
||||
|
||||
`Idle` is healthy. It means the runner is authenticated and polling for
|
||||
work. The runner is available only while the coding PC, Docker, and these
|
||||
containers are running.
|
||||
|
||||
One known, non-fatal warning in the runner logs: `cannot init cache server,
|
||||
it will be disabled: mkdir /.cache: permission denied` — the runner
|
||||
container runs as uid 1000 with no writable `$HOME`, so the `actions/cache`
|
||||
action type is unavailable. Nothing in the current workflow uses it. Fixable
|
||||
later by setting `HOME=/data` in the runner's environment if caching
|
||||
becomes worth adding.
|
||||
|
||||
## WeatherTool workflow
|
||||
|
||||
The version-controlled workflow is:
|
||||
|
||||
```text
|
||||
.gitea/workflows/ci.yml
|
||||
```
|
||||
|
||||
It runs for:
|
||||
|
||||
- pushes to `codex/staging-baseline`;
|
||||
- pull requests targeting `codex/staging-baseline`; and
|
||||
- manual `workflow_dispatch` requests.
|
||||
|
||||
Two independent jobs, each with its own container image rather than one
|
||||
shared runner-label image with ad-hoc installs — this reuses images already
|
||||
proven for this project rather than bootstrapping a second language runtime
|
||||
into a single shared container:
|
||||
|
||||
- **`backend`** (`hseeberger/scala-sbt:17.0.2_1.6.2_2.13.8`, the same image
|
||||
used all session for local Rocky builds): checkout, `sbt -batch test`.
|
||||
- **`frontend`** (`node:20-bookworm`): checkout, `npm ci`, `npm run
|
||||
typecheck`, `npm run build`, `npm audit`, `npm audit --omit=dev` — the
|
||||
same five checks already documented as the manual Rocky verification
|
||||
routine in `DEVELOPMENT_AND_STAGING.md`.
|
||||
|
||||
Verified before writing the workflow that `sbt test` compiles and passes
|
||||
with no `.env` file present at all (via a `git archive HEAD` dry run into a
|
||||
clean scratch directory) — matching exactly what a checkout-only CI job
|
||||
actually has, since real credentials must never reach CI. The sbt-dotenv
|
||||
plugin logs a graceful warning and continues; nothing in the current test
|
||||
suite (`FileNameServiceSpec`; `ParserSpec` has no live assertions) touches
|
||||
`sys.env` or a live database.
|
||||
|
||||
The first complete green run was verified on 2026-08-24 for commit
|
||||
`5b88e69` (`ci.yml #1`, 33s).
|
||||
|
||||
## Current boundary
|
||||
|
||||
This workflow is CI only. A green result proves the committed source
|
||||
compiles, passes its (currently minimal) test suite, typechecks, builds,
|
||||
and has no known frontend dependency vulnerabilities. It does not deploy to
|
||||
Rocky staging or the VPS, publish an image, or access any staging/production
|
||||
secret — the runner's job containers never see `.env`, `.env.staging`, or
|
||||
any real provider credential. Continuous delivery, and expanding actual test
|
||||
coverage (see the roadmap's Phase 2), remain separate, not yet started work.
|
||||
Reference in New Issue
Block a user