27ab795f54
Pushing to rocky alone doesn't trigger CI (Gitea can't see that private bare repo), and two separate git push commands are easy to half-remember. 'all' has two push URLs (rocky, gitea) so one push updates the staging checkout's source and triggers CI together. Deliberately excludes origin (GitHub) pending the still-open question about that repo's ownership/involvement.
149 lines
5.6 KiB
Markdown
149 lines
5.6 KiB
Markdown
# 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.
|
|
|
|
## Triggering CI
|
|
|
|
CI only runs on a push (or PR) reaching the `gitea` remote — pushing to
|
|
`rocky` alone does not trigger it, since Gitea has no visibility into that
|
|
private bare repo. To avoid needing two separate `git push` commands (and
|
|
forgetting one), a combined `all` remote pushes to both `rocky` and `gitea`
|
|
in one command:
|
|
|
|
```bash
|
|
git push all codex/staging-baseline
|
|
```
|
|
|
|
`all` deliberately does not include `origin` (the GitHub
|
|
`guntisdev/WeatherTool` repo) — see `DEVELOPMENT_AND_STAGING.md`'s commit
|
|
workflow section for why.
|
|
|
|
`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.
|