Document the Gitea Actions CI setup and the follow-up security review
CI / backend (push) Failing after 1s
CI / frontend (push) Failing after 1s

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:
b0txec
2026-08-24 21:55:48 +03:00
parent 5b88e69c40
commit a989d2a6a8
3 changed files with 144 additions and 6 deletions
+132
View File
@@ -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.
+3 -1
View File
@@ -16,7 +16,8 @@ This directory contains the working documentation for the WeatherTool modernizat
- **Brīdinājumi** renders current LVĢMC warning polygons over a production border overlay with feathered severity fills, plus draggable/resizable per-warning weather-symbol placement. Its lon/lat-to-pixel projection is an affine fit calibrated against the same validated city pixel positions Kartes/Faktiskā already use, replacing an earlier bounding-box calibration that drifted up to ~200px on the 3840 canvas. - **Brīdinājumi** renders current LVĢMC warning polygons over a production border overlay with feathered severity fills, plus draggable/resizable per-warning weather-symbol placement. Its lon/lat-to-pixel projection is an affine fit calibrated against the same validated city pixel positions Kartes/Faktiskā already use, replacing an earlier bounding-box calibration that drifted up to ~200px on the 3840 canvas.
- Confirmed local Monda Regular/Bold files provide interface and generated-graphic typography; weather symbols use normalized transparent image assets. - Confirmed local Monda Regular/Bold files provide interface and generated-graphic typography; weather symbols use normalized transparent image assets.
- Release `b0b58d2` is deployed as immutable image `weathertool:b0b58d2f1e0ed47ca13795b64595386ec2f0e0c7`; release `3eddf95` remains the immediate application rollback. On top of the earlier frontend design pass and the timezone/upsert/scheduler-split fixes, this release replaces the hardcoded per-route static-file list with a general SPA fallback — a real, user-reported bug where refreshing `/faktiska` or `/udens-temperatura` 404ed instead of loading the app (those two routes were never added to the old list). A missing `/assets` file still 404s properly rather than silently serving HTML. - Release `b0b58d2` is deployed as immutable image `weathertool:b0b58d2f1e0ed47ca13795b64595386ec2f0e0c7`; release `3eddf95` remains the immediate application rollback. On top of the earlier frontend design pass and the timezone/upsert/scheduler-split fixes, this release replaces the hardcoded per-route static-file list with a general SPA fallback — a real, user-reported bug where refreshing `/faktiska` or `/udens-temperatura` 404ed instead of loading the app (those two routes were never added to the old list). A missing `/assets` file still 404s properly rather than silently serving HTML.
- An independent fresh-eyes security review (requested against the local Rocky version only) found a live-verified CRITICAL path-traversal vulnerability plus several HIGH/MEDIUM findings — see the roadmap changelog for `6b9c7cf`/`8c45d8d` for the full list. All are fixed, compiled, live-verified on Rocky, and deployed to the VPS as release `0be325f` (image `weathertool:0be325fbbbf92b03bc8d574dc6f7b9449ef310ea`); release `b0b58d2` remains the immediate application rollback. Rocky's `POSTGRES_PASSWORD` was rotated afterward since the old value had been exposed into the review's own output via `/proc/self/environ`. `LVGMC_PASSWORD` rotation is not yet done — that needs the user's own action via the LVGMC contact. - An independent fresh-eyes security review (requested against the local Rocky version only) found a live-verified CRITICAL path-traversal vulnerability plus several HIGH/MEDIUM findings — see the roadmap changelog for `6b9c7cf`/`8c45d8d` for the full list. All are fixed, compiled, live-verified on Rocky, and deployed to the VPS as release `0be325f` (image `weathertool:0be325fbbbf92b03bc8d574dc6f7b9449ef310ea`); release `b0b58d2` remains the immediate application rollback. Rocky's `POSTGRES_PASSWORD` was rotated afterward since the old value had been exposed into the review's own output via `/proc/self/environ`. `LVGMC_PASSWORD` rotation is not yet done — that needs the user's own action via the LVGMC contact. A follow-up independent review then caught that the FTP auth-bypass fix was incomplete (a sibling route, `/api/show/lvgmc-forecast`, had the same live-FTP-trigger issue but had only gotten the traversal fix); fixed and deployed as `9bab93d`. That same review flagged several open architectural/testability findings (the `parMapN` scheduler crash-loop pattern, per-request `System.gc()`, an unenforced field-routing invariant, no JDBC connection pooling, and several files under `src/main/scala` that look like tests but aren't) — not yet acted on, tracked for a future session.
- Gitea Actions CI is live: a repository-scoped runner on Rocky (isolated behind its own Docker-in-Docker daemon, same pattern as HOP's Forgejo runner) runs `sbt test` plus the frontend typecheck/build/audit routine on every push/PR to `codex/staging-baseline`. See [Continuous integration](CONTINUOUS_INTEGRATION.md). CI-only for now — it does not deploy anywhere or touch any real credential.
- FTP (`ENABLE_LVGMC_FTP_JOBS`) is currently `false` on both Rocky and the VPS — re-testing the morning after enabling it turned up a second, unexplained failure (Rocky alone, VPS confirmed off, ~4 minutes after a successful manual test) that doesn't fit the original two-machine-collision theory. Decided to stop self-testing via trial and error and wait for the user to ask the LVGMC contact directly about the `ltv` account's connection/rate policy, rather than risk repeatedly tripping an unknown limit. See the roadmap Phase 7 for the full diagnosis timeline. - FTP (`ENABLE_LVGMC_FTP_JOBS`) is currently `false` on both Rocky and the VPS — re-testing the morning after enabling it turned up a second, unexplained failure (Rocky alone, VPS confirmed off, ~4 minutes after a successful manual test) that doesn't fit the original two-machine-collision theory. Decided to stop self-testing via trial and error and wait for the user to ask the LVGMC contact directly about the `ltv` account's connection/rate policy, rather than risk repeatedly tripping an unknown limit. See the roadmap Phase 7 for the full diagnosis timeline.
- The isolated VPS UAT stack is running and healthy: WeatherTool is bound to `127.0.0.1:8002`, Authelia to `127.0.0.1:9091`, and PostgreSQL has no host port. Public access is routed through Cloudflare, Nginx, and Authelia. - The isolated VPS UAT stack is running and healthy: WeatherTool is bound to `127.0.0.1:8002`, Authelia to `127.0.0.1:9091`, and PostgreSQL has no host port. Public access is routed through Cloudflare, Nginx, and Authelia.
- Cloudflare delegation is active, strict origin TLS covers only `laikapstak.li` and `auth.laikapstak.li`, and the public Nginx/Authelia login flow is operational without changing the existing HOP site. - Cloudflare delegation is active, strict origin TLS covers only `laikapstak.li` and `auth.laikapstak.li`, and the public Nginx/Authelia login flow is operational without changing the existing HOP site.
@@ -34,6 +35,7 @@ This directory contains the working documentation for the WeatherTool modernizat
- [Development and staging](DEVELOPMENT_AND_STAGING.md) — Windows source/Git workflow, Rocky development and verification, VPS deployment, synthetic data, and rollback. - [Development and staging](DEVELOPMENT_AND_STAGING.md) — Windows source/Git workflow, Rocky development and verification, VPS deployment, synthetic data, and rollback.
- [Product workflows](PRODUCT_WORKFLOWS.md) — the intended purpose and current status of each visible workspace. - [Product workflows](PRODUCT_WORKFLOWS.md) — the intended purpose and current status of each visible workspace.
- [Update roadmap](UPDATE_ROADMAP.md) — phased technical, security, dependency, testing, and UI work. - [Update roadmap](UPDATE_ROADMAP.md) — phased technical, security, dependency, testing, and UI work.
- [Continuous integration](CONTINUOUS_INTEGRATION.md) — Gitea Actions topology, isolation boundary, and the current CI workflow.
- [Third-party notices](THIRD_PARTY_NOTICES.md) — licenses and attribution for adapted interface components. - [Third-party notices](THIRD_PARTY_NOTICES.md) — licenses and attribution for adapted interface components.
- [Temporary VPS staging plan](VPS_STAGING_PLAN.md) — isolation, authentication, prepared deployment bundle, release, backup, verification, and rollback model for external user testing. - [Temporary VPS staging plan](VPS_STAGING_PLAN.md) — isolation, authentication, prepared deployment bundle, release, backup, verification, and rollback model for external user testing.
+9 -5
View File
@@ -57,9 +57,10 @@ Status: in progress
- [ ] Add database integration tests for aggregation and export behavior. - [ ] Add database integration tests for aggregation and export behavior.
- [ ] Restore and expand CSV parser tests. - [ ] Restore and expand CSV parser tests.
- [ ] Add GRIB parser boundary and malformed-file tests. - [ ] Add GRIB parser boundary and malformed-file tests.
- [ ] Add security tests for invalid fields, filenames, offsets, lengths, and date ranges. - [ ] Add security tests for invalid fields, filenames, offsets, lengths, and date ranges. Highest-priority target per an independent review: `ValidateFileName`/`ValidateField`/`ValidateInt` are pure functions with zero I/O and are currently the entire path-traversal/SQL-injection security boundary, verified only by manual curl.
- [ ] Add frontend type checking and critical workflow smoke tests. - [ ] `PostgresService.query`'s "list" branch (`byField` match, ~13 hardcoded literal cases, no `case _ =>`) is only safe today because `ValidateField`'s allowlist and `WeatherData`'s case class fields happen to stay in sync with it — nothing enforces that. Add a field to `WeatherData` without updating this match and any request for it with `granularity=hour` throws an uncaught `MatchError` (a real risk given the DMI/open-data provider work in progress). A test iterating every `WeatherData.getKeys` value through this path would catch it before it ships; needs either a test-container Postgres or refactoring SQL-fragment-building apart from execution so it's testable without a DB.
- [ ] Run tests automatically before staging deployment. - [x] Add frontend type checking (`npm run typecheck`, part of the dependency-maintenance work in Phase 3) — now also runs automatically in CI (see below). Critical workflow smoke tests remain manual/headless-browser only, not automated.
- [ ] Run tests automatically before staging deployment. Partial: Gitea Actions CI (see `CONTINUOUS_INTEGRATION.md`) now runs `sbt test` and the frontend typecheck/build/audit routine automatically on every push/PR to `codex/staging-baseline` — this is CI, not CD; it isn't yet wired as a required gate before Rocky/VPS deployment, which remains a manual decision independent of CI status.
## Phase 3 — Dependency modernization ## Phase 3 — Dependency modernization
@@ -103,8 +104,9 @@ Status: pending
Status: pending Status: pending
- [ ] Manage custom executors as resources and close them cleanly. - [ ] Manage custom executors as resources and close them cleanly.
- [ ] Remove explicit `System.gc()` calls. - [ ] Remove explicit `System.gc()` calls. Confirmed still present in `DataService.getBinaryChunk` (runs on every GRIB binary-chunk request — likely a latency source on what's probably a hot path; a commented-out `logMemory` block next to it suggests leftover debugging code, not an intentional design choice), per a 2026-08-24 independent review.
- [ ] Supervise scheduled jobs independently instead of recursively restarting the application. - [ ] Supervise scheduled jobs independently instead of recursively restarting the application. Confirmed still fully live by a 2026-08-24 independent review: `Scheduler.scheduleTask` has zero `.attempt`/error handling on any task, none of the task bodies wired into it (FTP fetch, open-data fetch, cleanup, GRIB fetch) handle their own errors either, and `(serverTask, scheduledTasks).parMapN(...)` means any one failure cancels the HTTP server itself — this is the same defect that caused the actual VPS/Rocky crash-loop incidents earlier this session, patched around with feature flags rather than closed. `OpenDataStationService` also has no client timeout configured, unlike the DMI fetch service. `WarningService`/`WaterTemperatureService` already show the correct pattern (`.attempt` + stale-cache fallback) the scheduler tasks should follow.
- [ ] Add a pooled JDBC transactor. `DBConnection.scala` uses `Transactor.fromDriverManager`, which opens a brand-new physical connection for every query — doobie's documented behavior for scripts/tests, not production services. Not urgent at current traffic, but an easy, well-known swap (`HikariTransactor`) worth doing before real load.
- [ ] Add application health and readiness endpoints. - [ ] Add application health and readiness endpoints.
- [ ] Add structured logging without leaking secrets. - [ ] Add structured logging without leaking secrets.
- [ ] Define PostgreSQL and GRIB-data backup/restore procedures. - [ ] Define PostgreSQL and GRIB-data backup/restore procedures.
@@ -446,4 +448,6 @@ Record completed work here by date and commit after the Git workflow is establis
| 2026-08-24 | *(operational, no commit)* | Rotated Rocky's `POSTGRES_PASSWORD` following the security review, since the old value had been exposed into this session's context multiple times (two of my own sloppy shell commands, plus the `/proc/self/environ` read the review used as its traversal proof). Changed the role's actual password via `ALTER ROLE` against the running container (editing `.env` alone has no effect on an already-initialized PostgreSQL data directory), then updated `.env` and recreated the `scala` service. The Postgres container itself was also recreated as a side effect (its own `POSTGRES_PASSWORD` env interpolation changed too), which is harmless — that variable only takes effect on a fresh, empty data directory, not an existing one — but is worth knowing about | Yes — clean scala container startup with no auth errors, and a real query (`/api/query/latest-temperatures/Rīga`) returning live data over the new password | | 2026-08-24 | *(operational, no commit)* | Rotated Rocky's `POSTGRES_PASSWORD` following the security review, since the old value had been exposed into this session's context multiple times (two of my own sloppy shell commands, plus the `/proc/self/environ` read the review used as its traversal proof). Changed the role's actual password via `ALTER ROLE` against the running container (editing `.env` alone has no effect on an already-initialized PostgreSQL data directory), then updated `.env` and recreated the `scala` service. The Postgres container itself was also recreated as a side effect (its own `POSTGRES_PASSWORD` env interpolation changed too), which is harmless — that variable only takes effect on a fresh, empty data directory, not an existing one — but is worth knowing about | Yes — clean scala container startup with no auth errors, and a real query (`/api/query/latest-temperatures/Rīga`) returning live data over the new password |
| 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 | `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 | `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) |