scaffold: project skeleton, schema, healthz/readyz, CI
Initial project structure for neuronetz-gateway per scope-docs/SPEC.md: - Python 3.12 / FastAPI / SQLAlchemy 2.0 (async) / Redis / Postgres stack managed by uv. Multi-stage non-root Dockerfile, prod + dev compose files (ollama service is NEVER published in either), Caddyfile + systemd unit, justfile, GitHub Actions CI (ruff, mypy --strict, pytest, bandit, pip-audit). - Pydantic-Settings config covering every env var from SPEC §7, including the MODEL_DISCOVERY_* keys for the dynamic-discovery feature (§4.6). - Alembic 0001_initial creates the full gateway schema (8 tables, 3 enums, notify_key_revoked() trigger), incl. allow_all_models on tenant_limits and key_limits for the per-tenant auto-grant toggle. - Working /healthz, /readyz (fail-closed when deps unreachable), and a Prometheus /metrics stub. Sanitizing error handlers that attach X-Request-ID to every response and never leak upstream internals. - SPEC + AGENT_PROMPT included under scope-docs/ (source of truth).
This commit is contained in:
59
ops/caddy/Caddyfile.example
Normal file
59
ops/caddy/Caddyfile.example
Normal file
@@ -0,0 +1,59 @@
|
||||
# neuronetz-gateway — Caddy reverse proxy (SPEC §4.1, §6.5).
|
||||
#
|
||||
# Caddy is the only public-facing component. It terminates TLS (HTTP/2 + HTTP/3),
|
||||
# obtains a Let's Encrypt certificate for api.neuronetz.ai automatically, applies
|
||||
# security headers, and reverse-proxies to the internal-only gateway:8080.
|
||||
#
|
||||
# Copy this file to `Caddyfile` and edit the site address / admin email.
|
||||
# The production docker-compose.yml mounts it at /etc/caddy/Caddyfile.
|
||||
{
|
||||
# Email for Let's Encrypt account + expiry notices. Replace before deploy.
|
||||
email ops@neuronetz.ai
|
||||
}
|
||||
|
||||
api.neuronetz.ai {
|
||||
# --- Reverse proxy to the internal gateway ---
|
||||
# `gateway` is the Docker service name on the internal network; it is never
|
||||
# published to the host. Caddy forwards plain HTTP/1.1 to it.
|
||||
reverse_proxy gateway:8080
|
||||
|
||||
# --- Security headers ---
|
||||
header {
|
||||
# HSTS: force HTTPS for two years, include subdomains, allow preload.
|
||||
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
|
||||
# Disable MIME sniffing.
|
||||
X-Content-Type-Options "nosniff"
|
||||
# Clickjacking defense (API has no UI, deny framing outright).
|
||||
X-Frame-Options "DENY"
|
||||
# Conservative referrer policy.
|
||||
Referrer-Policy "no-referrer"
|
||||
# Strip server-identifying headers so we don't advertise the stack.
|
||||
-Server
|
||||
-X-Powered-By
|
||||
}
|
||||
|
||||
# Structured access logs to stdout (collected by the container runtime).
|
||||
log {
|
||||
output stdout
|
||||
format json
|
||||
}
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# DEV / LOCAL note:
|
||||
#
|
||||
# For local testing without a public domain or real certificate, replace the
|
||||
# site block above with a localhost block that uses Caddy's internal self-signed
|
||||
# CA (no Let's Encrypt round-trip):
|
||||
#
|
||||
# localhost {
|
||||
# tls internal
|
||||
# reverse_proxy gateway:8080
|
||||
# }
|
||||
#
|
||||
# Caddy will install its local root CA; trust it or pass `-k` to curl. Note the
|
||||
# Phase 1 *dev* compose stack (docker-compose.dev.yml) ships WITHOUT Caddy and
|
||||
# exposes the gateway directly on localhost:8080 — this file is for the full
|
||||
# production stack only.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
58
ops/systemd/neuronetz-gateway.service
Normal file
58
ops/systemd/neuronetz-gateway.service
Normal file
@@ -0,0 +1,58 @@
|
||||
# neuronetz-gateway — systemd unit for non-Compose deployments.
|
||||
#
|
||||
# Assumes the project is installed into a virtualenv at /opt/neuronetz-gateway/venv
|
||||
# (e.g. `uv venv /opt/neuronetz-gateway/venv && uv pip install ...`) and that
|
||||
# configuration lives in /etc/neuronetz-gateway/gateway.env (same keys as
|
||||
# .env.example). Postgres, Redis and Ollama are reached over the network/loopback
|
||||
# per that env file — Ollama must remain bound to localhost / a private network
|
||||
# and never be published publicly.
|
||||
#
|
||||
# Install:
|
||||
# sudo cp neuronetz-gateway.service /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable --now neuronetz-gateway
|
||||
|
||||
[Unit]
|
||||
Description=neuronetz-gateway — secure API gateway in front of Ollama
|
||||
Documentation=https://github.com/neuronetz/neuronetz-gateway
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
# Dedicated unprivileged service account (create with: useradd --system gateway).
|
||||
User=gateway
|
||||
Group=gateway
|
||||
|
||||
WorkingDirectory=/opt/neuronetz-gateway
|
||||
EnvironmentFile=/etc/neuronetz-gateway/gateway.env
|
||||
|
||||
# Apply migrations before starting (idempotent; no-op when already at head).
|
||||
ExecStartPre=/opt/neuronetz-gateway/venv/bin/alembic upgrade head
|
||||
ExecStart=/opt/neuronetz-gateway/venv/bin/python -m neuronetz_gateway
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
|
||||
# --- Hardening ---
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
# Allow writing only where the app legitimately needs to (none by default).
|
||||
ReadWritePaths=
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user