Add /health endpoint for system capability reporting
Unauthenticated endpoint that reports what's installed, what's missing, and what's degraded — without exposing secrets or key material. Reports: - Module status (stegasoo, verisoo) with versions - Optional capabilities: DCT, audio, video stego, LMDB, imagehash, USB monitoring, GPIO — each with actionable install hints - Key existence (identity, channel, trusted count, backup status) - Fieldkit status (killswitch, deadman, chain enabled) - System info (Python version, platform, available memory) Overall status is "ok" when core modules + keys are present, "degraded" otherwise. Memory reporting helps diagnose Argon2 OOM issues on constrained hardware (RPi). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
792254699c
commit
586aa50c95
@ -222,6 +222,154 @@ def create_app(config: SoosefConfig | None = None) -> Flask:
|
|||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
|
||||||
|
# ── Health check ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
@app.route("/health")
|
||||||
|
def health():
|
||||||
|
"""System health and capability report.
|
||||||
|
|
||||||
|
Unauthenticated — returns what's installed, what's missing,
|
||||||
|
and what's degraded. No secrets or key material exposed.
|
||||||
|
"""
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from flask import jsonify
|
||||||
|
|
||||||
|
from soosef.keystore.manager import KeystoreManager
|
||||||
|
|
||||||
|
ks = KeystoreManager()
|
||||||
|
|
||||||
|
# Core modules
|
||||||
|
modules = {}
|
||||||
|
for name, import_path in [
|
||||||
|
("stegasoo", "soosef.stegasoo"),
|
||||||
|
("verisoo", "soosef.verisoo"),
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
mod = __import__(import_path, fromlist=["__version__"])
|
||||||
|
modules[name] = {"status": "ok", "version": getattr(mod, "__version__", "unknown")}
|
||||||
|
except ImportError as e:
|
||||||
|
modules[name] = {"status": "missing", "error": str(e)}
|
||||||
|
|
||||||
|
# Optional capabilities
|
||||||
|
capabilities = {}
|
||||||
|
|
||||||
|
# DCT steganography
|
||||||
|
try:
|
||||||
|
from soosef.stegasoo import has_dct_support
|
||||||
|
capabilities["stego_dct"] = {
|
||||||
|
"status": "ok" if has_dct_support() else "unavailable",
|
||||||
|
"hint": None if has_dct_support() else "Install soosef[stego-dct] (scipy, jpeglib, reedsolo)",
|
||||||
|
}
|
||||||
|
except ImportError:
|
||||||
|
capabilities["stego_dct"] = {"status": "missing", "hint": "Install soosef[stego-dct]"}
|
||||||
|
|
||||||
|
# Audio steganography
|
||||||
|
try:
|
||||||
|
from soosef.stegasoo import HAS_AUDIO_SUPPORT
|
||||||
|
capabilities["stego_audio"] = {
|
||||||
|
"status": "ok" if HAS_AUDIO_SUPPORT else "unavailable",
|
||||||
|
"hint": None if HAS_AUDIO_SUPPORT else "Install soosef[stego-audio] (soundfile, numpy)",
|
||||||
|
}
|
||||||
|
except ImportError:
|
||||||
|
capabilities["stego_audio"] = {"status": "missing", "hint": "Install soosef[stego-audio]"}
|
||||||
|
|
||||||
|
# Video steganography
|
||||||
|
try:
|
||||||
|
from soosef.stegasoo.constants import VIDEO_ENABLED
|
||||||
|
capabilities["stego_video"] = {
|
||||||
|
"status": "ok" if VIDEO_ENABLED else "unavailable",
|
||||||
|
"hint": None if VIDEO_ENABLED else "Requires ffmpeg in PATH",
|
||||||
|
}
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
capabilities["stego_video"] = {"status": "missing", "hint": "Requires ffmpeg"}
|
||||||
|
|
||||||
|
# LMDB (verisoo storage)
|
||||||
|
try:
|
||||||
|
import lmdb # noqa: F401
|
||||||
|
capabilities["lmdb"] = {"status": "ok"}
|
||||||
|
except ImportError:
|
||||||
|
capabilities["lmdb"] = {"status": "missing", "hint": "Install soosef[attest]"}
|
||||||
|
|
||||||
|
# Perceptual hashing
|
||||||
|
try:
|
||||||
|
import imagehash # noqa: F401
|
||||||
|
capabilities["imagehash"] = {"status": "ok"}
|
||||||
|
except ImportError:
|
||||||
|
capabilities["imagehash"] = {"status": "missing", "hint": "Install soosef[attest]"}
|
||||||
|
|
||||||
|
# USB monitoring
|
||||||
|
try:
|
||||||
|
import pyudev # noqa: F401
|
||||||
|
capabilities["usb_monitor"] = {"status": "ok"}
|
||||||
|
except ImportError:
|
||||||
|
capabilities["usb_monitor"] = {"status": "unavailable", "hint": "Install soosef[fieldkit] (Linux only)"}
|
||||||
|
|
||||||
|
# GPIO (RPi killswitch)
|
||||||
|
try:
|
||||||
|
import gpiozero # noqa: F401
|
||||||
|
capabilities["gpio"] = {"status": "ok"}
|
||||||
|
except ImportError:
|
||||||
|
capabilities["gpio"] = {"status": "unavailable", "hint": "Install soosef[rpi] (Raspberry Pi only)"}
|
||||||
|
|
||||||
|
# Key status (existence only, no material)
|
||||||
|
keys = {
|
||||||
|
"identity": "ok" if ks.has_identity() else "missing",
|
||||||
|
"channel_key": "ok" if ks.has_channel_key() else "missing",
|
||||||
|
"trusted_keys": len(ks.get_trusted_keys()),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backup status
|
||||||
|
backup_info = ks.last_backup_info()
|
||||||
|
keys["last_backup"] = backup_info["timestamp"] if backup_info else "never"
|
||||||
|
keys["backup_overdue"] = ks.is_backup_overdue(config.backup_reminder_days)
|
||||||
|
|
||||||
|
# Fieldkit status
|
||||||
|
fieldkit = {
|
||||||
|
"killswitch_enabled": config.killswitch_enabled,
|
||||||
|
"deadman_enabled": config.deadman_enabled,
|
||||||
|
"chain_enabled": config.chain_enabled,
|
||||||
|
}
|
||||||
|
if config.deadman_enabled:
|
||||||
|
from soosef.fieldkit.deadman import DeadmanSwitch
|
||||||
|
dm = DeadmanSwitch()
|
||||||
|
dm_status = dm.status()
|
||||||
|
fieldkit["deadman_armed"] = dm_status["armed"]
|
||||||
|
fieldkit["deadman_overdue"] = dm_status.get("overdue", False)
|
||||||
|
|
||||||
|
# System info (no secrets)
|
||||||
|
system = {
|
||||||
|
"python": sys.version.split()[0],
|
||||||
|
"platform": platform.machine(),
|
||||||
|
"os": platform.system(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Memory (for Argon2 sizing awareness)
|
||||||
|
try:
|
||||||
|
import os as _os
|
||||||
|
mem_bytes = _os.sysconf("SC_PAGE_SIZE") * _os.sysconf("SC_PHYS_PAGES")
|
||||||
|
system["memory_mb"] = mem_bytes // (1024 * 1024)
|
||||||
|
except (ValueError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Overall status
|
||||||
|
all_ok = (
|
||||||
|
all(m["status"] == "ok" for m in modules.values())
|
||||||
|
and keys["identity"] == "ok"
|
||||||
|
and keys["channel_key"] == "ok"
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"status": "ok" if all_ok else "degraded",
|
||||||
|
"version": __import__("soosef").__version__,
|
||||||
|
"modules": modules,
|
||||||
|
"capabilities": capabilities,
|
||||||
|
"keys": keys,
|
||||||
|
"fieldkit": fieldkit,
|
||||||
|
"system": system,
|
||||||
|
})
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user