Skip to content

OPERATIONS

Operations

Backup

Stop the single control-plane writer or take a storage snapshot. Back up the Nodra data directory, including state.snapshot.json, state.wal, deliveries/, deadletters/, audit/ and optional pki/.

./scripts/backup-state.sh /var/lib/nodra /var/backups/nodra-$(date -u +%Y%m%d).tar.gz

The live console's Activity/Logs tail is an in-memory ring (cap 2000) and is not part of the durable backup set — but every action it shows that has an identifiable actor is also durably written to audit/ (daily-rotated NDJSON, or the nodra_audit_log Postgres table with NODRA_STORE=postgres) and is captured by backup-state.sh since it lives under the data directory. Query it live via GET /api/v1/audit or export it with nodractl audit export --out FILE.

Recovery

Restore the complete data directory and start the same or newer compatible Nodra version. WAL replay reconstructs live state and pending queues. Re-run nodra-sim (or wait for continuous sim) if you need demo activity lines again.

# writer stopped; empty target or NODRA_RESTORE_FORCE=1
./scripts/restore-state.sh /var/backups/nodra-….tar.gz /var/lib/nodra
curl -fsS http://127.0.0.1:PORT/readyz   # must report store ready

Never dual-mount the same WAL on two live control-plane processes. See PRODUCTION.md.

Readiness

GET /readyz pings the fleet store (file WAL open / Postgres Ping) and confirms delivery + DLQ queues are available. Prefer it over /healthz for load balancer gates.

Disk pressure

Monitor nodra_delivery_queue_bytes, nodra_pending_deliveries and nodra_dead_letters. On agents, heartbeat metrics expose queue_bytes, queue_depth and configured queue maximum.

Use spool_policy=reject for telemetry that must never be silently discarded.

Ports

Shared resolution: CLI --port → NODRA_PORT → .deploy-last → random high port for remote deploy.

Surface Variables / flags
Control plane listen nodra-server --listen
Remote systemd ./scripts/deploy-remote.sh HOST USER --port N
Compose NODRA_PORT, NODRA_AGENT_PORT, NODRA_MQTT_PORT
Helm service.port; optional service.type=NodePort + service.nodePort
kind demo PF ./scripts/demo-k8s.sh --port N
Local smoke NODRA_SMOKE_SERVER_PORT, NODRA_SMOKE_AGENT_PORT

Credentials

Env Role
NODRA_ADMIN_TOKEN Management API bearer (also default console password)
NODRA_ADMIN_USER Console username (default admin)
NODRA_ADMIN_PASSWORD Console password (default = admin token)
NODRA_VIEWER_TOKEN Optional read-only bearer
NODRA_VIEWER_USER / NODRA_VIEWER_PASSWORD Optional viewer console login (password defaults to viewer token)
NODRA_ENROLLMENT_TOKEN Site bootstrap
NODRA_STORE file (default) or postgres
NODRA_DATABASE_URL Postgres DSN when store is postgres

Viewer tokens can list fleet data; mutating methods return 403. Rotate admin/enrollment tokens after exposure. Revoke lost sites from the control plane.

Fleet store

Default: file WAL under the data directory. For Postgres:

export NODRA_STORE=postgres
export NODRA_DATABASE_URL='postgres://user:pass@host:5432/nodra?sslmode=require'
./bin/nodra-server --listen :8080 --data ./data

Delivery and DLQ queues stay on the local WAL when NODRA_STORE=file. With NODRA_STORE=postgres they live in Postgres and every replica claims work concurrently. Fleet documents (sites, devices, twins, routes, deployments, alerts, events) are read from Postgres on every call.

CI covers the Postgres path with go test ./internal/store/ -run Postgres against a Postgres 16 service (NODRA_DATABASE_URL). Locally the same test skips unless the DSN is set.

Upgrades

File mode and the default Helm install stay at one replica and use Recreate, because the data volume is ReadWriteOnce. Postgres mode may set replicaCount above 1, which selects RollingUpdate. Edge agents continue local operation and spool cloud-bound events during a control-plane interruption. Schema changes apply as numbered migrations at startup; a database newer than the binary refuses to start.

Demo / simulation

See DEMO.md. Enable Helm simulation.enabled=true or run nodra-sim against a live control plane. Continuous simulation posts heartbeats and activity; --once seeds and exits.

Failure domains

A slow webhook does not block unrelated routes because delivery is concurrent and bounded. Repeatedly failing deliveries enter the DLQ after their configured retry count.

Verification

./scripts/smoke.sh
./scripts/smoke-remote.sh --port 20059
./scripts/test-all.sh --port 20059 --skip-deploy
make qualify   # software matrix → evidence/qualification/software-matrix.json

See QUALIFICATION.md and INTEGRATIONS.md.