Files
meshcore-hub/src/meshcore_hub/api/cli.py
Louis King 5a20da3afa Add Prometheus metrics endpoint, Alertmanager, and 1h stats window
Add /metrics endpoint with Prometheus gauges for nodes, messages,
advertisements, telemetry, trace paths, events, and members. Include
per-node last_seen timestamps for alerting. Add Alertmanager service
to Docker Compose metrics profile with default blackhole receiver.
Add NodeNotSeen alert rule (48h threshold). Add 1h time window to
all windowed metrics alongside existing 24h/7d/30d windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 23:06:07 +00:00

207 lines
5.3 KiB
Python

"""API CLI commands."""
import click
@click.command()
@click.option(
"--host",
type=str,
default="0.0.0.0",
envvar="API_HOST",
help="API server host",
)
@click.option(
"--port",
type=int,
default=8000,
envvar="API_PORT",
help="API server port",
)
@click.option(
"--data-home",
type=str,
default=None,
envvar="DATA_HOME",
help="Base data directory (default: ./data)",
)
@click.option(
"--database-url",
type=str,
default=None,
envvar="DATABASE_URL",
help="Database connection URL (default: sqlite:///{data_home}/collector/meshcore.db)",
)
@click.option(
"--read-key",
type=str,
default=None,
envvar="API_READ_KEY",
help="Read-only API key (optional, enables read-level auth)",
)
@click.option(
"--admin-key",
type=str,
default=None,
envvar="API_ADMIN_KEY",
help="Admin API key (optional, enables admin-level auth)",
)
@click.option(
"--mqtt-host",
type=str,
default="localhost",
envvar="MQTT_HOST",
help="MQTT broker host for commands",
)
@click.option(
"--mqtt-port",
type=int,
default=1883,
envvar="MQTT_PORT",
help="MQTT broker port",
)
@click.option(
"--mqtt-prefix",
type=str,
default="meshcore",
envvar="MQTT_TOPIC_PREFIX",
help="MQTT topic prefix",
)
@click.option(
"--mqtt-tls",
is_flag=True,
default=False,
envvar="MQTT_TLS",
help="Enable TLS/SSL for MQTT connection",
)
@click.option(
"--cors-origins",
type=str,
default=None,
envvar="CORS_ORIGINS",
help="Comma-separated list of allowed CORS origins",
)
@click.option(
"--metrics-enabled/--no-metrics",
default=True,
envvar="METRICS_ENABLED",
help="Enable Prometheus metrics endpoint at /metrics",
)
@click.option(
"--metrics-cache-ttl",
type=int,
default=60,
envvar="METRICS_CACHE_TTL",
help="Seconds to cache metrics output (reduces database load)",
)
@click.option(
"--reload",
is_flag=True,
default=False,
help="Enable auto-reload for development",
)
@click.pass_context
def api(
ctx: click.Context,
host: str,
port: int,
data_home: str | None,
database_url: str | None,
read_key: str | None,
admin_key: str | None,
mqtt_host: str,
mqtt_port: int,
mqtt_prefix: str,
mqtt_tls: bool,
cors_origins: str | None,
metrics_enabled: bool,
metrics_cache_ttl: int,
reload: bool,
) -> None:
"""Run the REST API server.
Provides REST API endpoints for querying mesh network data and sending
commands to devices via MQTT.
Examples:
# Run with defaults (no auth)
meshcore-hub api
# Run with authentication
meshcore-hub api --read-key secret --admin-key supersecret
# Run with CORS for web frontend
meshcore-hub api --cors-origins "http://localhost:8080,http://localhost:3000"
# Development mode with auto-reload
meshcore-hub api --reload
"""
import uvicorn
from meshcore_hub.common.config import get_api_settings
from meshcore_hub.api.app import create_app
# Get settings to compute effective values
settings = get_api_settings()
# Override data_home if provided
if data_home:
settings = settings.model_copy(update={"data_home": data_home})
# Use effective database URL if not explicitly provided
effective_db_url = database_url if database_url else settings.effective_database_url
effective_data_home = data_home or settings.data_home
click.echo("=" * 50)
click.echo("MeshCore Hub API Server")
click.echo("=" * 50)
click.echo(f"Host: {host}")
click.echo(f"Port: {port}")
click.echo(f"Data home: {effective_data_home}")
click.echo(f"Database: {effective_db_url}")
click.echo(f"MQTT: {mqtt_host}:{mqtt_port} (prefix: {mqtt_prefix})")
click.echo(f"Read key configured: {read_key is not None}")
click.echo(f"Admin key configured: {admin_key is not None}")
click.echo(f"CORS origins: {cors_origins or 'none'}")
click.echo(f"Metrics enabled: {metrics_enabled}")
click.echo(f"Metrics cache TTL: {metrics_cache_ttl}s")
click.echo(f"Reload mode: {reload}")
click.echo("=" * 50)
# Parse CORS origins
origins_list: list[str] | None = None
if cors_origins:
origins_list = [o.strip() for o in cors_origins.split(",")]
if reload:
# For development, use uvicorn's reload feature
# We need to pass app as string for reload to work
click.echo("\nStarting in development mode with auto-reload...")
click.echo("Note: Using default settings for reload mode.")
uvicorn.run(
"meshcore_hub.api.app:create_app",
host=host,
port=port,
reload=True,
factory=True,
)
else:
# For production, create app directly
app = create_app(
database_url=effective_db_url,
read_key=read_key,
admin_key=admin_key,
mqtt_host=mqtt_host,
mqtt_port=mqtt_port,
mqtt_prefix=mqtt_prefix,
mqtt_tls=mqtt_tls,
cors_origins=origins_list,
metrics_enabled=metrics_enabled,
metrics_cache_ttl=metrics_cache_ttl,
)
click.echo("\nStarting API server...")
uvicorn.run(app, host=host, port=port)