Record the SPA fallback fix and the morning FTP re-test finding

The SPA fallback (2c44888/b0b58d2) is deployed to VPS as b0b58d2,
verified via curl and headless-browser on real routes, a missing
asset, and /api, both locally and through the public domain.

Also updated the FTP saga: re-testing this morning found a Rocky-only
failure with the VPS confirmed off, which the two-machine-collision
theory from last night can't explain. Decided to stop self-testing
and wait for a real answer from LVGMC about the account's connection
policy rather than keep guessing through trial and error.
This commit is contained in:
b0txec
2026-08-24 10:42:12 +03:00
parent b0b58d2f1e
commit 546ad46546
3 changed files with 26 additions and 14 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ docker compose run --rm --no-deps node npm audit --omit=dev
At commit `de6f279`, all five checks pass and both audit scopes report zero known vulnerabilities. The npm major-version availability notice is informational and does not require changing the npm version independently of the pinned Node build image. At commit `de6f279`, all five checks pass and both audit scopes report zero known vulnerabilities. The npm major-version availability notice is informational and does not require changing the npm version independently of the pinned Node build image.
Known limitation: direct browser refreshes on newer client-side routes such as `/faktiska` can return 404 because the backend static-route list does not yet provide a general SPA fallback. Navigate from the home page until that backend behavior is fixed. Fixed 2026-08-24 (`2c44888`): direct browser refreshes on any client-side route now correctly serve the app via a general SPA fallback, replacing the old hardcoded per-route static-file list.
## Authenticated API requests ## Authenticated API requests
+4 -3
View File
@@ -6,7 +6,7 @@ This directory contains the working documentation for the WeatherTool modernizat
- Windows is restricted to source editing, review, and Git operations. Rocky is the sole compile, build, development-runtime, and test environment. The Ubuntu VPS is a deployment target only. - Windows is restricted to source editing, review, and Git operations. Rocky is the sole compile, build, development-runtime, and test environment. The Ubuntu VPS is a deployment target only.
- A production-like staging copy runs through Docker Compose on Rocky Linux at `http://192.168.1.101:9190`. - A production-like staging copy runs through Docker Compose on Rocky Linux at `http://192.168.1.101:9190`.
- Rocky now ingests real data from two independent LVĢMC sources at once: the free `data.gov.lv` open-data feed and the private FTP feed (`ftp.meteo.lv`, real credentials obtained and verified 2026-08-23). The VPS currently runs open-data only — FTP was briefly enabled there too but reverted the same night after the `ltv` account failed to authenticate from the VPS specifically (see below). The two providers are gated independently (`ENABLE_LVGMC_FTP_JOBS`, `ENABLE_HARMONIE_JOBS`) since real credentials for FTP and DMI HARMONIE arrive on different timelines. HARMONIE's credentials turned out not to be needed at all (DMI dropped its API key requirement — confirmed live, see the roadmap changelog) but wiring it up is deferred to a dedicated verification session, since the GRIB-parsing and map-rendering code has real, untested risk (a hardcoded crop/rotation calibration that may not match the current model grid). - Both Rocky and the VPS currently run on the free `data.gov.lv` open-data feed only. Real private FTP credentials (`ftp.meteo.lv`) were obtained 2026-08-23 and the code path is proven working (manual tests succeeded repeatedly, including once from Rocky as recently as 2026-08-24 morning), but enabling the *scheduled* fetch has failed unpredictably on both machines at different times, for reasons not yet understood — see the roadmap Phase 7 for the full timeline. `ENABLE_LVGMC_FTP_JOBS` stays `false` everywhere until the LVGMC contact can explain the account's actual connection/rate policy. DMI HARMONIE's credentials turned out not to be needed at all (DMI dropped its API key requirement — confirmed live, see the roadmap changelog) but wiring it up is deferred to a dedicated verification session, since the GRIB-parsing and map-rendering code has real, untested risk (a hardcoded crop/rotation calibration that may not match the current model grid).
- A real UTC-vs-local timezone mismatch between the two sources (open-data's timestamps were UTC, FTP's already local, both stored in the same column with no conversion) was found and fixed before enabling both together; both `weather` tables were backed up and wiped for a clean, consistently-timestamped restart. - A real UTC-vs-local timezone mismatch between the two sources (open-data's timestamps were UTC, FTP's already local, both stored in the same column with no conversion) was found and fixed before enabling both together; both `weather` tables were backed up and wiped for a clean, consistently-timestamped restart.
- The safe scheduled jobs (open-data station ingestion, GRIB cleanup) always run; the FTP job now runs too since real credentials exist; HARMONIE's job stays off pending its own implementation work. - The safe scheduled jobs (open-data station ingestion, GRIB cleanup) always run; the FTP job now runs too since real credentials exist; HARMONIE's job stays off pending its own implementation work.
- PostgreSQL is private to the project Compose network; only the Scala application publishes a host port. - PostgreSQL is private to the project Compose network; only the Scala application publishes a host port.
@@ -15,14 +15,15 @@ This directory contains the working documentation for the WeatherTool modernizat
- **Ūdens** auto-populates its six ranges on load with real per-zone water-temperature min/max (65 LVĢMC stations classified into the 6 named zones), with manual override and reset still available. Uses separate authoritative 1920×1080 and 3840×1440 production templates; both exports have been visually validated. - **Ūdens** auto-populates its six ranges on load with real per-zone water-temperature min/max (65 LVĢMC stations classified into the 6 named zones), with manual override and reset still available. Uses separate authoritative 1920×1080 and 3840×1440 production templates; both exports have been visually validated.
- **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 `3eddf95` is deployed as immutable image `weathertool:3eddf95008b103f34d50dbc86c5634d3c0fa3523`; release `e446f1ff` remains the immediate application rollback. On top of the earlier frontend design pass (visual palette rework, Faktiskā decluttering, header nav, weather-icon CSS consolidation), this release fixes the UTC-vs-local timezone mismatch described above, hardens the `weather` table upsert to be non-destructive across sources (`COALESCE` instead of a blind overwrite), and splits the scheduler flag so FTP and HARMONIE can be enabled independently. `deploy/vps/compose.yml` was also fixed to actually read `LVGMC_*` from `.env.staging` (it was hardcoding inert placeholders directly in the file before, so real credentials alone wouldn't have taken effect) — real credentials and `ENABLE_LVGMC_FTP_JOBS=true` are live on the VPS as of 2026-08-23. - 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.
- 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.
- The VPS `weather` table was wiped twice on 2026-08-23: first to remove the original 14-day synthetic dataset (backed up to `/srv/weathertool/backups/pre-real-data-release/`), then again after the UTC/local timezone fix (backed up to `/srv/weathertool/backups/pre-timezone-fix-wipe/`) so the two real sources it now holds — open-data (minutes 15/45) and FTP (minutes 11/13/23/30) — are consistently timestamped from a clean start. - The VPS `weather` table was wiped twice on 2026-08-23: first to remove the original 14-day synthetic dataset (backed up to `/srv/weathertool/backups/pre-real-data-release/`), then again after the UTC/local timezone fix (backed up to `/srv/weathertool/backups/pre-timezone-fix-wipe/`) so the two real sources it now holds — open-data (minutes 15/45) and FTP (minutes 11/13/23/30) — are consistently timestamped from a clean start.
- Approved 1920×1080 and 3840×1440 PNG production bases are now the rendering source for Faktiskā and Ūdens temperatūra; code draws only the changing values, selected weather symbols, and wind data over those fixed newsroom graphics. - Approved 1920×1080 and 3840×1440 PNG production bases are now the rendering source for Faktiskā and Ūdens temperatūra; code draws only the changing values, selected weather symbols, and wind data over those fixed newsroom graphics.
- Browser branding assets and Latvian Open Graph/Twitter metadata are included for favicon, Apple home-screen icon, and link-preview support. Public crawler access still depends on the Nginx/Authelia policy used for the metadata and preview image. - Browser branding assets and Latvian Open Graph/Twitter metadata are included for favicon, Apple home-screen icon, and link-preview support. Public crawler access still depends on the Nginx/Authelia policy used for the metadata and preview image.
- Browser and API verification is complete for the deployed `3eddf95` release: exact release image smoke-tested on Rocky before transfer (bundle hash and correct-local-time API responses matched the known-good local build), checksum verified on both ends, container health/loopback/public HTTPS confirmed. - Browser and API verification is complete for the deployed `3eddf95` release: exact release image smoke-tested on Rocky before transfer (bundle hash and correct-local-time API responses matched the known-good local build), checksum verified on both ends, container health/loopback/public HTTPS confirmed.
- **VPS FTP is currently disabled again** (`ENABLE_LVGMC_FTP_JOBS=false`), despite being enabled earlier tonight. Watching for its first scheduled fetch (rather than assuming it was safe just because Rocky worked) caught a real problem: the `ltv` account fails to authenticate from the VPS specifically, and because a scheduled-task failure cascades through `parMapN` up to the app's top-level error handler, this was actively crash-looping the entire app in-process (not a Docker-level restart, but the whole app — DB, HTTP server, schedulers — tearing down and rebuilding every ~5s, repeating every ~2 minutes as the schedule re-fired). Reverted immediately; confirmed stable afterward. VPS network connectivity to `ftp.meteo.lv:21` itself is fine (verified with a raw connection test); this is specifically the `ltv` account failing from the VPS's IP, not a firewall issue — see the roadmap changelog for the full diagnosis and next steps (check the password transcription, or ask LVGMC whether the account is IP-restricted). FTP stays enabled on Rocky, where it's confirmed working. - Every FTP failure so far has cascaded through `parMapN` into the app's top-level error handler, crash-looping the *entire* app in-process (DB, HTTP server, schedulers all torn down and rebuilt every ~5s) rather than just failing the one scheduled task — caught twice by watching logs after enabling the flag rather than assuming it was safe, reverted both times within minutes.
- Frontend dependency maintenance is complete: Solid runtime and Vite tooling were updated, obsolete packages were removed, TypeScript checking was added, and a clean Rocky `npm ci`, typecheck, production build, full audit, and production-only audit all pass with zero known vulnerabilities. - Frontend dependency maintenance is complete: Solid runtime and Vite tooling were updated, obsolete packages were removed, TypeScript checking was added, and a clean Rocky `npm ci`, typecheck, production build, full audit, and production-only audit all pass with zero known vulnerabilities.
- This is not yet approved or hardened for workplace production. - This is not yet approved or hardened for workplace production.
+21 -10
View File
@@ -7,7 +7,7 @@ This document tracks proposed WeatherTool improvements. Work should be delivered
- **Windows source workspace:** source editing, review, and Git operations only; do not install dependencies, compile, build, run, or test here. - **Windows source workspace:** source editing, review, and Git operations only; do not install dependencies, compile, build, run, or test here.
- **Rocky development and staging:** the sole compile, build, development-runtime, and test environment, with production-like Docker staging at `http://192.168.1.101:9190`. - **Rocky development and staging:** the sole compile, build, development-runtime, and test environment, with production-like Docker staging at `http://192.168.1.101:9190`.
- **Git over SSH:** Windows pushes reviewed commits to a private bare repository on Rocky; the Rocky staging checkout pulls those commits and rebuilds. - **Git over SSH:** Windows pushes reviewed commits to a private bare repository on Rocky; the Rocky staging checkout pulls those commits and rebuilds.
- **Ubuntu VPS deployment:** release `3eddf95` is publicly operational behind Cloudflare strict TLS, Nginx, and Authelia, ingesting real LVĢMC open-data station/water-temperature observations on a schedule; the `weather` table was wiped and re-populated fresh once the UTC/local timezone mismatch between sources was fixed. FTP (`ENABLE_LVGMC_FTP_JOBS`) was briefly enabled 2026-08-23 but reverted to `false` the same night after the `ltv` account failed to authenticate from the VPS specifically (crash-looped the app in-process until caught and reverted) — real credentials work fine from Rocky, not yet from the VPS; see the changelog for full diagnosis. The VPS does not compile or build the project. - **Ubuntu VPS deployment:** release `b0b58d2` is publicly operational behind Cloudflare strict TLS, Nginx, and Authelia, ingesting real LVĢMC open-data station/water-temperature observations on a schedule; the `weather` table was wiped and re-populated fresh once the UTC/local timezone mismatch between sources was fixed. FTP (`ENABLE_LVGMC_FTP_JOBS`) stays `false` on both Rocky and the VPS pending a real answer from the LVGMC contact about the `ltv` account's connection/rate policy — see Phase 7 for the full, still-unresolved diagnosis. The VPS does not compile or build the project.
- **Workplace production:** remains separate until changes are reviewed, tested, and explicitly approved for workplace use. - **Workplace production:** remains separate until changes are reviewed, tested, and explicitly approved for workplace use.
Do not synchronize `.env`, database directories, generated dependencies, build output, or provider credentials between machines. Do not synchronize `.env`, database directories, generated dependencies, build output, or provider credentials between machines.
@@ -272,13 +272,24 @@ running in parallel until each real source is proven, not cut over in one step.
IP isn't on that list even though Rocky's apparently is. The VPS's IP isn't on that list even though Rocky's apparently is. The VPS's
outbound IP is `57.128.250.38`, in case it needs to be given to the outbound IP is `57.128.250.38`, in case it needs to be given to the
LVGMC contact for allowlisting. LVGMC contact for allowlisting.
**Next step (morning, user): double-check the password in **2026-08-24 morning update: the two-machine-collision theory above
`/srv/weathertool/.env.staging` on the VPS character-for-character does not fully hold.** Independently confirmed the `ltv` account
against the decoded value; if it's correct, ask the LVGMC contact itself isn't hard-locked (a FileZilla login from a separate PC on the
whether the `ltv` account is IP-restricted and whether the VPS's same network succeeded, browsed `/ltv/tabulas`, saw
outbound IP needs adding.** FTP stays on for Rocky (working there); `Latvija_faktiskais_laiks.csv` freshly modified — LVGMC's feed is
VPS runs open-data only until this is resolved and re-verified the alive). A manual isolated fetch from Rocky then succeeded too. But
same careful way — watch-and-confirm, not assume-and-move-on. re-enabling Rocky's *scheduler* (`ENABLE_LVGMC_FTP_JOBS=true`, VPS
confirmed still `false` the whole time) failed on its very next
scheduled attempt, ~4 minutes after the successful manual test, on
Rocky alone with nothing else touching the account. Three attempts in
~10 minutes, two succeeded and one didn't, with no pattern found yet
(not simultaneity, not which machine, not obviously attempt spacing).
Reverted `ENABLE_LVGMC_FTP_JOBS` to `false` on Rocky again immediately.
**Decision: stop self-testing this via trial and error — every
attempt might be feeding an unknown rate/session limit — and wait for
the user to ask the LVGMC contact directly what the account's
connection policy actually is.** Both Rocky and the VPS run open-data
only until there's a real answer.
- [x] Researched DMI HARMONIE credentials while waiting on the above and - [x] Researched DMI HARMONIE credentials while waiting on the above and
confirmed live (not just from search results, which claimed a specific confirmed live (not just from search results, which claimed a specific
date that wasn't independently verified): DMI's Forecast Data EDR and date that wasn't independently verified): DMI's Forecast Data EDR and
@@ -369,9 +380,8 @@ Status: in progress
- Both the VPS and Rocky `weather` tables now hold only real open-data station observations; their original synthetic rows were backed up and wiped 2026-08-23. - Both the VPS and Rocky `weather` tables now hold only real open-data station observations; their original synthetic rows were backed up and wiped 2026-08-23.
- LVGMC forecast CSV fixtures are not yet available. - LVGMC forecast CSV fixtures are not yet available.
- HARMONIE GRIB fixtures are not yet available. - HARMONIE GRIB fixtures are not yet available.
- The private LVGMC FTP feed has real credentials as of 2026-08-23 and is enabled on Rocky (`ENABLE_LVGMC_FTP_JOBS=true`); the DMI HARMONIE forecast feed remains gated (`ENABLE_HARMONIE_JOBS`, default off) pending real credentials. - The private LVGMC FTP feed has real credentials as of 2026-08-23, but `ENABLE_LVGMC_FTP_JOBS` stays `false` on both Rocky and the VPS pending a real answer from LVGMC about the `ltv` account's connection/rate policy (see Phase 7). The DMI HARMONIE forecast feed remains gated (`ENABLE_HARMONIE_JOBS`, default off) pending real credentials — turned out not to need any (DMI dropped its key requirement), but wiring it up is deferred to a dedicated verification session (see Phase 7).
- Existing automated test coverage is minimal. - Existing automated test coverage is minimal.
- Direct refreshes on newer frontend routes can return 404 until the backend gains a general SPA fallback.
- Full Docker build context scanning on Rocky can fail on the container-owned `postgres/` bind directory; do not loosen its permissions. - Full Docker build context scanning on Rocky can fail on the container-owned `postgres/` bind directory; do not loosen its permissions.
## Change log ## Change log
@@ -431,3 +441,4 @@ Record completed work here by date and commit after the Git workflow is establis
| 2026-08-23 | `3315f00` | Fix the same UTC-vs-local mismatch in Ūdens's `observedAt` display (`WaterTemperatureService`) — same open-data portal, same root cause, display-only (internal recency filtering was already self-consistent either way) | Yes — Scala tests; verified live via `/api/water-temperatures`: `observedAt` now matches real local time | | 2026-08-23 | `3315f00` | Fix the same UTC-vs-local mismatch in Ūdens's `observedAt` display (`WaterTemperatureService`) — same open-data portal, same root cause, display-only (internal recency filtering was already self-consistent either way) | Yes — Scala tests; verified live via `/api/water-temperatures`: `observedAt` now matches real local time |
| 2026-08-23 | `3eddf95` | Split `ENABLE_LEGACY_PROVIDER_JOBS` into independent `ENABLE_LVGMC_FTP_JOBS`/`ENABLE_HARMONIE_JOBS` — real LVGMC FTP credentials arrived today, real DMI HARMONIE credentials haven't, and the combined flag would have enabled both together, crash-looping the app on HARMONIE's still-placeholder values via `parMapN` | Yes — Scala tests; caught before it could happen (the Grib job was ~15 min from its first scheduled run when noticed) and reverted within under a minute; re-verified after the fix that only "Fetch Weather Stations" scheduled, not "Fetch Grib" | | 2026-08-23 | `3eddf95` | Split `ENABLE_LEGACY_PROVIDER_JOBS` into independent `ENABLE_LVGMC_FTP_JOBS`/`ENABLE_HARMONIE_JOBS` — real LVGMC FTP credentials arrived today, real DMI HARMONIE credentials haven't, and the combined flag would have enabled both together, crash-looping the app on HARMONIE's still-placeholder values via `parMapN` | Yes — Scala tests; caught before it could happen (the Grib job was ~15 min from its first scheduled run when noticed) and reverted within under a minute; re-verified after the fix that only "Fetch Weather Stations" scheduled, not "Fetch Grib" |
| 2026-08-23 | `dc04f66` | Fix `deploy/vps/compose.yml` hardcoding `LVGMC_USER`/`PASSWORD`/`URL` to inert placeholder strings directly in the file (unlike `POSTGRES_*`, which already read from `.env.staging`) — real credentials added to `.env.staging` alone would have had no effect until this switched to the same `${VAR}` substitution pattern. Enabled `ENABLE_LVGMC_FTP_JOBS` on the VPS | Yes — `docker compose config` syntax valid; VPS `app` container came up healthy with real credentials loaded (would have crashed immediately on missing-config if not) | | 2026-08-23 | `dc04f66` | Fix `deploy/vps/compose.yml` hardcoding `LVGMC_USER`/`PASSWORD`/`URL` to inert placeholder strings directly in the file (unlike `POSTGRES_*`, which already read from `.env.staging`) — real credentials added to `.env.staging` alone would have had no effect until this switched to the same `${VAR}` substitution pattern. Enabled `ENABLE_LVGMC_FTP_JOBS` on the VPS | Yes — `docker compose config` syntax valid; VPS `app` container came up healthy with real credentials loaded (would have crashed immediately on missing-config if not) |
| 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) |