Correct two doc sections written before the real CI bugs were found
CI / backend (push) Successful in 1m27s
CI / frontend (push) Successful in 52s

The changelog claimed ci.yml #1 verified green (it didn't -- that was a
wrong WebFetch summary of the Gitea Actions page, reported as fact without
checking it visually) and CONTINUOUS_INTEGRATION.md described the
HOME/cache permission warning as harmless (it wasn't -- it blocked
resolving actions/checkout entirely). Both corrected with what actually
happened: three real, different bugs, found only by running the workflow
and reading user-provided screenshots of the actual failures.
This commit is contained in:
b0txec
2026-08-24 22:27:39 +03:00
parent 27ab795f54
commit ea583ed57d
2 changed files with 33 additions and 10 deletions
+31 -9
View File
@@ -93,12 +93,16 @@ workflow section for why.
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.
`compose.yml` sets `HOME: /data` on the runner container. Without it, the
first real run failed every job, both times it was tried, with `Unable to
clone https://github.com/actions/checkout ...: mkdir /.cache: permission
denied` — this was initially misdiagnosed (both in the runner's own startup
log, which only calls it "cache server disabled", and in this doc's first
draft) as merely disabling the optional `actions/cache` action type. It
actually blocks resolving *any* remote action at all: with uid 1000 and no
writable `$HOME`, the runner has nowhere to git-clone an action's source
into before running it, and `actions/checkout` is exactly that. `HOME=/data`
(the already-writable bind-mounted data directory) fixed it.
## WeatherTool workflow
@@ -120,7 +124,9 @@ 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`.
used all session for local Rocky builds): installs a real Node 20 via
NodeSource's setup script (see below for why), 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
@@ -134,8 +140,24 @@ 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).
`actions/checkout` is a JavaScript action — it needs a Node runtime inside
the job container to execute at all, which `hseeberger/scala-sbt` doesn't
have. `frontend`'s image already has one, so it passed as soon as the
`HOME` fix landed. `backend` needed an explicit install step first
(`apt-get install nodejs` alone wasn't enough either: `hseeberger/scala-sbt`
is Debian **bullseye**-based, whose default apt `nodejs` package is a stale
Node 12, too old to parse `actions/checkout@v4`'s modern JS —
`SyntaxError: Unexpected token '{'` on a class static block. NodeSource's
`setup_20.x` script installs an actual current Node 20 regardless of the
distro's packaged version).
None of the three bugs above were caught by writing the workflow carefully
or by the pre-write `sbt test` dry run — all three only surfaced by actually
running it and reading the real failure in the Gitea Actions UI, once per
bug, in order: the `HOME`/cache fix, then the missing-Node fix, then the
wrong-Node-version fix. The first four runs (`ci.yml #1``#4`) all failed.
The first complete green run was `ci.yml #5`, commit `2c78ae8`, 2m24s,
verified 2026-08-24.
## Current boundary
+2 -1
View File
@@ -449,5 +449,6 @@ Record completed work here by date and commit after the Git workflow is establis
| 2026-08-24 | `0be325f` | Deploy the security-review fixes (`6b9c7cf`, `8c45d8d`) to the VPS as image `weathertool:0be325fbbbf92b03bc8d574dc6f7b9449ef310ea`; release `b0b58d2` remains the immediate application rollback. Application-only release — PostgreSQL and Authelia were not restarted | Yes — exact release image smoke-tested on Rocky against a throwaway local Postgres before transfer (traversal/injection payloads 404, gated FTP route 503, legitimate queries 200); checksum verified on both ends; `app` service recreated cleanly (healthy, no auth/DB errors in logs); loopback re-verification of the same traversal/injection/FTP-gate checks plus real `/api/warnings` and `/api/water-temperatures` responses; public HTTPS confirmed unchanged (302 unauthenticated page, 401 unauthenticated API) |
| 2026-08-24 | `9bab93d` | A follow-up independent fresh-eyes review (requested specifically as a second pass, not a rerun of the same pentest) caught that the `6b9c7cf` FTP auth-bypass fix was incomplete: only `/api/fetch/lvgmc/stations` (the one route the original review named) was gated behind `ENABLE_LVGMC_FTP_JOBS`. A sibling route, `/api/show/lvgmc-forecast/{fileName}`, calls the same `fetch.fetchFile` — a real, unauthenticated LVGMC FTP login — and had only gotten the traversal fix (`ValidateFileName`), not the auth-bypass fix, because the two routes were fixed along different mental categories (traversal batch vs. the one named auth-bypass fix) instead of by tracing every caller of the dangerous method. Gated this route the same way; traced every HTTP-reachable caller of `fetch.fetchFile`/`fetchWeatherStations` this time to confirm no others remain. Deployed to both Rocky and the VPS (image `weathertool:9bab93daa6b3a0fb99165a8fac9accfa3a3ccbf1`) immediately given this was live and exploitable in production | Yes — Scala tests, Rocky rebuild and curl verification (both FTP routes 503 while the flag is off), VPS image smoke-tested against a throwaway local Postgres before transfer, checksum verified, `app` service recreated cleanly, loopback re-verification of both gated routes, public HTTPS unchanged (302/401) |
| 2026-08-24 | *(review, no commit)* | A follow-up independent fresh-eyes review (explicitly scoped as a second, different pass — not a rerun of the original pentest) sanity-checked the `6b9c7cf`/`8c45d8d` fixes and did a broader skeptical pass. Caught the incomplete FTP fix documented under `9bab93d` above. Confirmed `ValidateFileName`'s minor `"."` edge case is harmless (resolves to a directory, not exploitable) and found no other unvalidated `Fragment.const` usages. Surfaced several open architectural/testability findings now tracked in Phase 2/Phase 5 above: the `parMapN` scheduler crash-loop pattern (still fully live), per-request `System.gc()` in `getBinaryChunk`, `PostgresService.query`'s unenforced field-routing invariant, no JDBC connection pooling, and several files under `src/main/scala` (`DataServiceTest`, `FetchServiceTest` ×2, `OpenDataStationServiceTest`, `GribParserTest`) that look like tests but are actually `main`-method scripts never run by `sbt test` — compiled straight into the production jar, at least one capable of writing to the real database or hitting live endpoints if run by mistake |
| 2026-08-24 | `5b88e69` | Add Gitea Actions CI: a repository-scoped `weathertool-ci-rocky-01` runner on Rocky (isolated behind its own Docker-in-Docker daemon, mirroring the isolation pattern already proven by HOP's Forgejo runner on the same host — the two coexist without conflict, each an independent client registered to a different server) runs `sbt test` and the frontend typecheck/build/audit routine on every push/PR to `codex/staging-baseline` plus manual dispatch. Verified `sbt test` compiles and passes with no `.env` present at all (via a `git archive HEAD` dry run) before writing the workflow, since CI must never see real credentials. Repo mirrored to Gitea (`bot/WeatherTool`) as an additional remote alongside the existing `rocky`/`origin` ones — deliberately not replacing either, pending an open question about the GitHub `origin` (`guntisdev/WeatherTool`) that won't be resolved until 2026-08-26. See `CONTINUOUS_INTEGRATION.md` | Yes — first run (`ci.yml #1`, commit `5b88e69`) verified green in the Gitea Actions UI (33s), and confirmed via the runner's nested Docker daemon that both job images (`hseeberger/scala-sbt`, `node:20-bookworm`) were actually pulled and used, not just orchestration logs |
| 2026-08-24 | `5b88e69` | Add Gitea Actions CI: a repository-scoped `weathertool-ci-rocky-01` runner on Rocky (isolated behind its own Docker-in-Docker daemon, mirroring the isolation pattern already proven by HOP's Forgejo runner on the same host — the two coexist without conflict, each an independent client registered to a different server) runs `sbt test` and the frontend typecheck/build/audit routine on every push/PR to `codex/staging-baseline` plus manual dispatch. Verified `sbt test` compiles and passes with no `.env` present at all (via a `git archive HEAD` dry run) before writing the workflow, since CI must never see real credentials. Repo mirrored to Gitea (`bot/WeatherTool`) as an additional remote alongside the existing `rocky`/`origin` ones — deliberately not replacing either, pending an open question about the GitHub `origin` (`guntisdev/WeatherTool`) that won't be resolved until 2026-08-26. See `CONTINUOUS_INTEGRATION.md` | **Correction**: this row originally claimed the first run (`ci.yml #1`) verified green, based on a WebFetch summary of the Gitea Actions page that turned out to be simply wrong — reported as fact without visually checking it first. It actually failed, along with the next three runs, on three different real bugs (see `7e1b492`/`2c78ae8` below and `CONTINUOUS_INTEGRATION.md`). First actual green run was `ci.yml #5`, commit `2c78ae8` |
| 2026-08-24 | `7e1b492``2c78ae8` | Fix three real CI bugs surfaced by actually running the new workflow, found via user-provided screenshots of the Gitea Actions UI after the WebFetch-based "success" claim above turned out false: (1) the runner's `HOME`/cache-directory permission error, initially misdiagnosed as only disabling the optional `actions/cache` action type, actually blocked resolving `actions/checkout` at all — fixed with `HOME=/data` on the runner container (operational fix, in `compose.yml`, not this repo); (2) `actions/checkout` is a JavaScript action needing Node in the job container, which `backend`'s `hseeberger/scala-sbt` image doesn't have — fixed with an `apt-get install nodejs` step before checkout (`7e1b492`); (3) that apt-installed Node was Debian bullseye's own stale default (Node 12), too old for `actions/checkout@v4`'s modern JS — fixed by installing a real current Node 20 via NodeSource's setup script instead (`2c78ae8`). See `CONTINUOUS_INTEGRATION.md` for the full detail | Yes — `ci.yml #5` (commit `2c78ae8`) verified green in the Gitea Actions UI via user-provided screenshot (2m24s), both jobs passing |
| 2026-08-24 | `2c44888``b0b58d2` | Replace the hardcoded per-route static-file list (`/station`, `/cities`, `/latvia`, `/database`, `/harmonie`, `/lvgmc-forecast`, `/bridinajumi`) with a general SPA fallback: real files serve as-is, anything else falls back to `index.html` so the SolidJS router handles it client-side — fixes a real, user-reported bug where a direct hit (e.g. a browser refresh) on `/faktiska` or `/udens-temperatura` 404ed instead of loading the app, since those two routes were never added to the old list. A missing file under `/assets` specifically still 404s properly rather than silently serving HTML, so a stale tab after a future deploy gets a clean error instead of a confusing JS parse failure | Yes — first attempt silently no-op'd because the fix was built into a `git archive HEAD` image before being committed (classic mistake, caught immediately by re-testing and finding identical old behavior); after committing, verified via curl on all previously-working routes, both previously-broken routes, a missing asset (404), a real asset (200), and `/api` (200), then a full headless-browser render check (zero console errors, real data, correct nav state) on a genuine direct hit — not just HTTP status codes; deployed to VPS in release `b0b58d2`, verified the same way through both the loopback port and the public domain (Authelia gate still correctly redirects unauthenticated requests) |