ADR-0023: Configuration Management — YAML Seed, DB Settings, Users Table¶
Status: Accepted • Date: 2026-02-23
1. Context & Problem¶
Silvasonic services need configuration at three levels:
- Infrastructure — paths, hostnames, sockets (must exist before any container starts).
- Application Settings — latitude/longitude, polling intervals, retention thresholds, bandwidth limits (tunable at runtime via Web-Interface).
- Authentication — user credentials for the Web-Interface login.
Without a clear strategy, configuration is scattered across .env, hardcoded Python defaults, and undocumented database rows. New deployments have no reproducible initial state, and there is no single place to define what "factory defaults" look like.
2. Decision¶
We chose: A three-tier configuration architecture with a YAML seed file for reproducible initial state.
2.1. Configuration Tiers¶
| Tier | Storage | Examples | Set By | Runtime Changeable? |
|---|---|---|---|---|
| Infrastructure | .env file |
SILVASONIC_WORKSPACE_PATH, POSTGRES_HOST, SILVASONIC_PODMAN_SOCKET |
Admin (SSH / Ansible) | No — requires container rebuild/restart |
| Application Settings | system_config table (JSONB) |
latitude, max_recorders, confidence_threshold, bandwidth_limit |
YAML Seed → Web-Interface | Yes — via State Reconciliation (ADR-0017) |
| Authentication | users table |
username, password_hash |
YAML Seed → Web-Interface | Yes — Frontend settings page |
2.2. Application Settings — system_config Table¶
The existing system_config table (key TEXT, value JSONB) stores all application settings as namespaced JSON blobs:
| Key | Pydantic Schema | Used By |
|---|---|---|
system |
SystemSettings |
All services (latitude, longitude, station name, resource limits, auto_enrollment) |
processor |
ProcessorSettings |
Processor (Janitor thresholds, Indexer intervals) |
uploader |
UploaderSettings |
Uploader (polling, bandwidth, schedule) |
birdnet |
BirdnetSettings |
BirdNET (confidence threshold) |
Each key maps to a Pydantic BaseModel that defines field types and default values. Services read their settings once on startup (Immutable Container pattern, ADR-0019). The Web-Interface writes changes to system_config, then triggers a silvasonic:nudge so the Controller restarts the affected service.
2.3. YAML Seed File — config/defaults.yml¶
A version-controlled YAML file defines factory defaults for all system_config keys:
# config/defaults.yml — seeded into system_config on Controller startup
system:
latitude: 53.55
longitude: 9.99
max_recorders: 5
max_uploaders: 3
station_name: "Silvasonic Dev"
auto_enrollment: true # Auto-enroll devices with exact profile match (runtime-changeable)
auth:
default_username: "admin"
default_password: "silvasonic" # MUST be changed in production!
# Service settings are added here when each service is implemented:
# processor: # v0.5.0
# janitor_threshold_warning: 70.0
# ...
# uploader: # v0.6.0
# ...
# birdnet: # v0.8.0
# confidence_threshold: 0.25
Seeding behavior:
1. On every Controller startup, the YAML is loaded and validated against Pydantic schemas.
2. The Controller inserts each key into system_config via Python (INSERT ... ON CONFLICT DO NOTHING). No SQL seed files.
3. Existing user-modified values are never overwritten.
4. After a database reset, all defaults are restored automatically on the next Controller startup.
5. Service settings are only seeded once the corresponding service is implemented and uncommented in the YAML.
2.4. Authentication — users Table¶
Authentication credentials are stored in a dedicated users table, separate from system_config:
CREATE TABLE users (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Rationale: Credentials have a fundamentally different structure (username + bcrypt hash) and lifecycle (RBAC readiness for v1.9.0+) than key-value settings. A separate table keeps the schema clean and enables future multi-user support without schema changes.
Seeding: A default admin account is seeded from the auth section of config/defaults.yml by the Controller on startup. The password is hashed with bcrypt before insertion. Production deployments must change the default password via the Web-Interface settings page.
2.5. Pydantic Validation — config_schemas.py¶
All configuration blobs are parsed and validated via Pydantic BaseModel classes in silvasonic.core.config_schemas. This ensures:
- Type safety (latitude must be a float, max_recorders must be an int).
- Defaults are defined in one place (the schema), mirrored in the YAML seed.
- Invalid configuration is caught at startup, not at runtime.
2.6. What Does NOT Go in system_config¶
| Category | Where | Why |
|---|---|---|
Host paths (WORKSPACE_PATH) |
.env |
Required before DB is reachable |
| DB credentials | .env |
Required to connect to DB |
| Redis host | .env |
Required before Redis is reachable |
| Podman socket path | .env |
Infrastructure prerequisite |
| User passwords | users table |
Different structure, RBAC-ready |
| Cloud storage credentials | storage_remotes table |
Already has its own table |
3. Options Considered¶
- SQL-only seed (
INSERTstatements): Rejected. SQL is unwieldy for nested JSON defaults, hard to review/diff, and creates a second seeding path alongside YAML. All seeding goes through Python for consistency. - Pydantic defaults only (no YAML file): Rejected. Defaults would be scattered across multiple Python files. A single YAML is the canonical source.
- Flat columns instead of JSONB: Rejected. Adding a new setting would require a schema migration. JSONB + Pydantic validation gives the same type safety with zero migration cost.
- Auth in
system_config: Rejected. Credentials need their own table structure (username, hash, is_active) and will grow into RBAC later.
4. Consequences¶
- Positive:
- One YAML file defines all factory defaults — reproducible, version-controlled, diff-friendly.
- Pydantic ensures type safety without relying on database constraints.
- User modifications survive Controller restarts (seed uses
ON CONFLICT DO NOTHING). userstable is RBAC-ready for v1.9.0+ without schema changes..envstays minimal — only true infrastructure prerequisites.
- Negative:
- Two sources of truth for defaults (YAML + Pydantic). Must stay in sync manually. Mitigation: CI test validates YAML against schemas.
- Adding a new config key requires updating YAML, Pydantic schema, and the Controller seed logic.