Commit graph

24 commits

Author SHA1 Message Date
379b8ee357
fix(docker): use BuildKit cache mounts for apt so .debs skip the layer
The last attempt still blew past the CI runner's /var cap because
apt's pre-download free-space check runs against the archives dir,
and the image overlay is where that sits. Moving /var/cache/apt and
/var/lib/apt onto BuildKit cache mounts puts the .deb storage on the
builder's cache volume (outside the image overlay), so the free-space
check runs against whatever disk backs the cache — which on the CI
runner has room.

Requires the `syntax=docker/dockerfile:1.6` directive at the top for
the --mount flag. Also removes /etc/apt/apt.conf.d/docker-clean so
the Debian base's post-invoke hook doesn't wipe the cache mount on
every apt run.
2026-04-18 11:07:45 -05:00
6e72c6811d
fix(docker): keep apt cache from filling /var disk during build
The cdo install pulls ~1 GB of transitive deps (netCDF/HDF5/ecCodes/
Fortran) and was exhausting CI's /var/cache/apt partition. Two fixes:

- `APT::Keep-Downloaded-Packages=false` deletes each .deb immediately
  after it's extracted, so the archive cache doesn't retain every
  downloaded package through the end of the install.

- Split cdo into its own RUN layer so the base runtime deps finish
  installing and clean before cdo's cache starts growing.
2026-04-18 11:00:06 -05:00
b6caffea3e
feat(nav): show last-deployed timestamp
Docker final stage bakes BUILD_TIMESTAMP into /app/BUILD_TIMESTAMP via
build-arg (only consumed after mix compile, so earlier layer caching is
preserved). Application.build_timestamp/0 reads the file, falls back to
DEPLOY_TIMESTAMP env, then to compile time. Navbar renders a compact
"Deployed Xm ago" with the full UTC time in the tooltip.
2026-04-17 13:08:51 -05:00
c5aab2bc16
fix(docker): copy priv/agent-skills before mix compile
AgentSkillsController reads priv/agent-skills/*.md at compile time via
@external_resource + File.read! to bake sha256 digests into the module.
priv/ is copied after compile to keep the compile cache stable, so add
a targeted COPY for the agent-skills subdir before compile — same
pattern as algo.md.
2026-04-17 12:35:03 -05:00
1faeb60e3c
Add cdo to runtime image for NARR GRIB1 decoding
NARR is GRIB1 and wgrib2 doesn't decode GRIB1 messages
(rd_grib2_seq_msg, grib1 message ignored, use wgrib). cdo handles
GRIB1 + the Lambert conformal projection + nearest-neighbor remap
that NARR's Grid 221 needs. Pulls in libnetcdf/libhdf5/libudunits2
transitively, ~30-60 MB image growth.
2026-04-15 18:54:02 -05:00
7352d06ade
Use NCEPLIBS-g2c v2.1.0 + enable wgrib2 PNG decoder
The linker error from the previous attempt was actual symbol
references, not a flag-combination oddity:

  undefined reference to `g2c_enc_jpeg2000'
  undefined reference to `g2c_dec_jpeg2000'

Those live in g2c's "new" file-based API, built only when
BUILD_G2C=ON. In v1.9.0 the option defaulted OFF and was marked
"experimental until 2.0.0 release", so I had explicitly disabled
it. In v2.1.0 (released 2025-01-13, before wgrib2 v3.6.0 on
2025-02-19) it defaults ON and the API is stable. Bump
G2C_VERSION to 2.1.0 and drop the BUILD_G2C override.

With the new-API symbols now exported, wgrib2 can be compiled
with every decoder we care about in one shot:

  -DUSE_PNG=ON -DUSE_OPENJPEG=ON -DUSE_AEC=ON

`wgrib2 -config` in the final runtime image now reports:

  Supported decoding: simple, complex, rle, ieee, png, jpeg2000,
                      CCSDS AEC

Verified end-to-end by building the full image with podman and
running wgrib2 -config inside it. MrmsFetchWorker's packing
type 41 (PNG) messages will decode once the rolling deploy lands.
2026-04-14 11:06:04 -05:00
4167eb54c9
Switch wgrib2 to OpenJPEG flag to match upstream CI matrix
Last attempt (-DUSE_PNG=ON -DUSE_AEC=ON on wgrib2) reached 100 %
linking libwgrib2.so and then died with "make: Error 2" on the
parallel wgrib2_exe link. wgrib2's own CI workflow
(.github/workflows/Linux_options.yml) never tests USE_PNG alone —
its matrix uses -DUSE_OPENJPEG=ON, and its NCEPLIBS-g2c is built
with -DUSE_OpenJPEG=ON -DUSE_Jasper=OFF. Copying that known-good
combination:

  * g2c now builds with USE_PNG + USE_AEC + USE_OpenJPEG (Jasper
    still disabled — libjasper is out of Debian anyway).
  * wgrib2 flips to -DUSE_OPENJPEG=ON -DUSE_AEC=ON.
  * libopenjp2-7-dev added to the builder apt list, libopenjp2-7
    added to the runtime apt list so libg2c.so + wgrib2 can
    resolve OpenJPEG symbols at exec time.

wgrib2 delegates actual GRIB2 decompression to g2c, which is
built with PNG support, so DRT 5.41 (MRMS PrecipRate) still
decodes through this link — wgrib2's own USE_PNG knob isn't
required for decoding, only for encoding we don't do.
2026-04-14 10:56:42 -05:00
ffe2034869
Build wgrib2 with NCEPLIBS-g2c for PNG + AEC decoder support
The previous attempt to flip `-DUSE_PNG=ON -DUSE_AEC=ON` on wgrib2
broke the image build because wgrib2 v3.6.0's CMakeLists.txt
line 94 forces `find_package(g2c 1.9.0 REQUIRED)` whenever PNG or
JPEG2000 is enabled, and NCEPLIBS-g2c wasn't installed in the
wgrib2-builder stage.

Install NCEPLIBS-g2c v1.9.0 (the version wgrib2 asks for) in the
wgrib2-builder stage ahead of wgrib2 itself, with:

  - USE_PNG=ON           (needs libpng-dev, already present)
  - USE_AEC=ON           (needs libaec-dev, already present)
  - USE_Jasper=OFF       (default ON — would need libjasper-dev
                          and another runtime dep we don't need)
  - USE_OpenJPEG=OFF     (same rationale)
  - BUILD_G2C=OFF        (new experimental API, not needed here)

Then flip wgrib2's PNG/AEC flags back on. wgrib2's binary links
dynamically against libg2c.so so the final stage copies
/usr/local/lib/libg2c.so* from the builder and refreshes the
loader cache with ldconfig.

With the decoder restored, re-add MrmsFetchWorker to the prod
cron in runtime.exs. That worker pulls NOAA MRMS PrecipRate grids
every 2 minutes, which use GRIB2 DRT 5.41 (PNG-compressed) — the
whole reason we needed the PNG decoder in the first place. ASOS
nudges will now have a live radar overlay again.
2026-04-14 10:48:21 -05:00
a950fe4d93
Back out wgrib2 PNG/AEC flags + drop MrmsFetchWorker from prod
The previous commit that added -DUSE_PNG=ON -DUSE_AEC=ON to the
wgrib2 cmake invocation broke the image build: wgrib2 3.6.0 turns
find_package(g2c 1.9.0 REQUIRED) on when those flags are enabled,
and NCEPLIBS-g2c isn't installed in the wgrib2-builder stage.

Revert to the stock wgrib2 cmake config so CI is green again and
pull MrmsFetchWorker out of the prod cron instead. Without the
PNG decoder the worker can't read MRMS PrecipRate (GRIB2 DRT
5.41) and was logging "packing type 41 not supported" every two
minutes. AsosAdjustmentWorker's MRMS path already handles an
empty cache gracefully — the ASOS nudge still runs, it just
drops the live radar overlay.

Re-add MrmsFetchWorker once the Dockerfile installs NCEPLIBS-g2c
and rebuilds wgrib2 with PNG/AEC support.
2026-04-14 10:44:00 -05:00
0a1e7ea57e
Build wgrib2 with PNG + AEC decoders
Prod wgrib2 3.6.0 was built without PNG support (cmake had no
-DUSE_PNG flag) so MrmsFetchWorker crashed on every tick with
'packing type 41 not supported' — MRMS PrecipRate tiles are
PNG-compressed (GRIB2 DRT 5.41). libpng-dev was already in the
builder stage; the cmake invocation just never asked wgrib2 to
use it.

Also turn on -DUSE_AEC=ON since libaec-dev is already installed
and some HRRR native-grid variables ship with AEC/CCSDS 121
compression.
2026-04-14 10:26:29 -05:00
caad7d90a7 Mount Oban Web dashboard at /admin/oban
Vendor oban_web 2.12.1 and oban_met 1.1.0 (taken from the
towerops-web2 vendor tree), bump oban to ~> 2.21, and mount the Oban
dashboard at /admin/oban behind the existing require_admin on_mount
hook. Adds an admin-only "Oban" nav link and extends the Dockerfile
to copy vendor/ before deps.get so production builds pick up the
path dependencies.
2026-04-09 14:07:33 -05:00
59de7e2d73 Bump Docker image to Elixir 1.19.5 / OTP 28.4.1 2026-04-09 14:02:31 -05:00
6fbec7ea9b
Fix Dockerfile: copy algo.md before mix compile (read at compile time) 2026-04-01 10:38:35 -05:00
7e65327e84
Optimize Dockerfile layer caching
Move COPY priv after mix compile so ML model retraining doesn't bust
the compile cache. Create empty priv dirs before compile for
:code.priv_dir. Move algo.md after compile. Add docs/plans and
analysis outputs to .dockerignore.
2026-04-01 10:32:56 -05:00
bfd9ed6339
Copy algo.md into Docker build before mix compile 2026-03-31 13:09:27 -05:00
6e3b8d62de
Fix wgrib2: use v3.6.0 (v3.6.1 doesn't exist) and follow redirects 2026-03-31 09:20:26 -05:00
bb514112bf
Clean up Dockerfile: remove unused deps, comments, and redundant lines 2026-03-31 09:18:30 -05:00
9aef372985
Build wgrib2 v3.6.1 from GitHub source with cmake in Docker 2026-03-31 09:17:10 -05:00
4e2d74e21c
Build wgrib2 in Docker with cached build stage
Add a separate wgrib2-builder stage that compiles wgrib2 from source.
Docker layer caching means this only builds once — subsequent deploys
reuse the cached layer. The binary is copied into the final runtime
image. Also fixed wgrib2 path lookup to be runtime instead of
compile-time so it works in the Docker build pipeline.
2026-03-31 08:55:09 -05:00
10e8feb486
Add commercial link monitoring via SNMP polling
Poll UBNT AirFiber radios (AF11X + AF60-LR) every 5 minutes via
net-snmp CLI, storing signal metrics in commercial_samples. Fetches
ASOS weather alongside each cycle for propagation correlation.

Includes 7 seeded link definitions, Oban cron worker, and net-snmp
in the Docker image.
2026-03-30 13:02:59 -05:00
e5b65ca2bc
Remove wgrib2 from Docker build — replaced by pure Elixir decoder
Drops gfortran, wget, cmake from builder and libgomp1 from runner.
Eliminates the slowest step in the Docker build (~5 min wgrib2 compile).
2026-03-30 08:03:20 -05:00
45e2e69361
Add SRTM terrain path profiles for QSOs
Fetch elevation data along the path between two stations via the
Open-Meteo Elevation API (with Open-Topo-Data fallback), compute
Fresnel zone clearance, earth bulge, and knife-edge diffraction loss,
and store the results per QSO.

- terrain_profiles table with migration and TerrainProfile schema
- ElevationClient with batched API calls and fallback
- TerrainAnalysis with Fresnel/diffraction physics (ITU-R P.526-15)
- TerrainProfileWorker on Oban :terrain queue
- QsoWeatherEnqueueWorker enqueues terrain jobs automatically
- QSO show page displays verdict badge and collapsible elevation table
- Reorder show page: terrain, soundings, solar, HRRR, surface obs
- Fix Dockerfile wgrib2 build (add cmake dependency)
2026-03-29 16:22:29 -05:00
8bd0989fbe
Add HRRR model profile fetching for QSOs
HRRR provides hourly 3km-resolution atmospheric profiles, filling the
temporal gaps (12-hourly soundings) and spatial gaps (only 9 sounding
stations) in our current weather data.

- Add hrrr_profiles table and hrrr_queued flag on QSOs
- HrrrClient fetches GRIB2 data via HTTP Range requests + wgrib2
- HrrrFetchWorker derives refractivity/ducting params via SoundingParams
- QsoWeatherEnqueueWorker now also enqueues HRRR jobs
- QSO show page displays HRRR section with collapsible profile
- Dockerfile builds wgrib2 from source for production
2026-03-29 15:54:22 -05:00
e0ab6560d0
docker 2026-03-28 11:41:31 -05:00