navi/backend/services/navi_config/admin.py

49 lines
1.6 KiB
Python
Raw Normal View History

Add navi-config service (extraction #2 PR-B) 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>
2026-05-22 08:25:06 -06:00
"""navi-config admin-info endpoint (handoff §4.5).
``GET /api/admin/navi-config/info`` Authentik-gated, read-only.
"""
import os
import time
from flask import Blueprint, jsonify, current_app
from shared.auth import require_auth
from shared.admin_info import build_info_response
from .config_loader import profiles_dir, profile_name, active_profile_path
bp = Blueprint('admin', __name__)
PORT = 8422
@bp.route('/api/admin/navi-config/info')
@require_auth
def navi_config_info():
metrics = current_app.config['METRICS']
path = active_profile_path()
# NOTE: these env values are NOT secrets — a directory path and a profile
# name — so they are shown as-is. mask_key() is only for credential-bearing
# vars (cf. navi-traffic's TOMTOM_API_KEY); there are none here.
info = build_info_response(
service='navi-config',
version=current_app.config.get('VERSION', 'unknown'),
port=PORT,
config={},
env=[
{'name': 'NAVI_CONFIG_PROFILES_DIR', 'value': profiles_dir()},
{'name': 'RECON_PROFILE', 'value': profile_name()},
],
dependencies=[], # no upstream HTTP — config is a local file read
filesystem=[{
'path': path,
'exists': os.path.exists(path),
'readable': os.access(path, os.R_OK),
}],
runtime={
'uptime_s': round(time.time() - metrics['start_time'], 1),
'request_count': metrics['request_count'],
'last_error_at': metrics['last_error_at'],
},
)
return jsonify(info)