Vendors the three deployment profile YAMLs (home, minimal_pi, regional_pi)
into navi-backend's own repo at config/profiles/. Copied verbatim from
/opt/recon/config/profiles/ post-PR-A, so home.yaml carries the auth block and
the two Pi profiles carry the TODO(matt) logout-host note.
Architectural reason: services in this monorepo should depend on each other
only through their HTTP APIs, never each other's filesystem. navi-config
currently defaults to reading /opt/recon/config/profiles/ — a cross-service
filesystem coupling. With the profiles vendored here, navi-config will be
pointed at this repo's copy via NAVI_CONFIG_PROFILES_DIR in
/etc/navi-backend/navi-config.env at deploy time, so it no longer reaches into
recon's tree.
recon keeps its own /opt/recon/config/profiles/ for its internal use; the two
copies are independent for now. Reconciling them — ideally by having recon
HTTP-fetch /api/config from navi-config rather than re-parsing YAML — is a
tracked follow-up, out of scope for extraction #2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New services/navi_config/ on :8422, mirroring recon's /api/config contract:
- config_route.py: GET /api/config -> jsonify(get_deployment_config())
with Cache-Control: public, max-age=300 (byte-for-byte recon's response).
- config_loader.py: faithful port of recon lib/deployment_config.py. Reads
RECON_PROFILE (default "home") and NAVI_CONFIG_PROFILES_DIR (default
/opt/recon/config/profiles, so it serves the SAME files recon does during
cutover). yaml.safe_load, module-level cache. Lazy load (vs recon's eager
import-time load) so the module imports cleanly off-VM and a missing
profile surfaces as HTTP 500 at request time rather than a failed import.
- admin.py: /api/admin/navi-config/info per handoff §4.5, require_auth gated.
env values (NAVI_CONFIG_PROFILES_DIR, RECON_PROFILE) are non-secret paths/
names, shown as-is (no mask_key); dependencies=[]; filesystem reports the
active profile path + exists/readable.
- app.py: create_app() factory mirroring navi_traffic, same metrics wiring;
resets the loader cache per instance so each worker/test reloads fresh.
Deploy artifacts: systemd unit (:8422) and an nginx snippet using
`location ^~ /api/config` (the ^~ convention from extraction #1 so the asset
.png/.css regex can't shadow it). No proxy_cache zone — the response is
already cached in-process and via Cache-Control: max-age=300; emits a literal
X-Cache-Status: BYPASS for parity with navi-traffic.
Adds PyYAML>=6 to deps. Tests (services/navi_config/tests): 200 + parsed dict,
Cache-Control header, RECON_PROFILE override, default=home, missing profile=500.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>