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 work. The runner is available only while the coding PC, Docker, and these
containers are running. containers are running.
One known, non-fatal warning in the runner logs: `cannot init cache server, `compose.yml` sets `HOME: /data` on the runner container. Without it, the
it will be disabled: mkdir /.cache: permission denied` — the runner first real run failed every job, both times it was tried, with `Unable to
container runs as uid 1000 with no writable `$HOME`, so the `actions/cache` clone https://github.com/actions/checkout ...: mkdir /.cache: permission
action type is unavailable. Nothing in the current workflow uses it. Fixable denied` — this was initially misdiagnosed (both in the runner's own startup
later by setting `HOME=/data` in the runner's environment if caching log, which only calls it "cache server disabled", and in this doc's first
becomes worth adding. 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 ## WeatherTool workflow
@@ -120,7 +124,9 @@ proven for this project rather than bootstrapping a second language runtime
into a single shared container: into a single shared container:
- **`backend`** (`hseeberger/scala-sbt:17.0.2_1.6.2_2.13.8`, the same image - **`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 - **`frontend`** (`node:20-bookworm`): checkout, `npm ci`, `npm run
typecheck`, `npm run build`, `npm audit`, `npm audit --omit=dev` — the typecheck`, `npm run build`, `npm audit`, `npm audit --omit=dev` — the
same five checks already documented as the manual Rocky verification 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 suite (`FileNameServiceSpec`; `ParserSpec` has no live assertions) touches
`sys.env` or a live database. `sys.env` or a live database.
The first complete green run was verified on 2026-08-24 for commit `actions/checkout` is a JavaScript action — it needs a Node runtime inside
`5b88e69` (`ci.yml #1`, 33s). 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 ## 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 | `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 | `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 | *(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) | | 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) |