diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 0000000..f07ce36 --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,362 @@ +# Configuration Reference + +**Last Updated:** 2026-02-06 + +--- + +## Overview + +Silo is configured via a YAML file. Copy the example and edit for your environment: + +```bash +cp config.example.yaml config.yaml +``` + +The server reads the config file at startup: + +```bash +./silod -config config.yaml # default: config.yaml +go run ./cmd/silod -config config.yaml +``` + +YAML values support environment variable expansion using `${VAR_NAME}` syntax. Environment variable overrides (listed per-key below) take precedence over YAML values. + +--- + +## Server + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `server.host` | string | `"0.0.0.0"` | Bind address | +| `server.port` | int | `8080` | HTTP port | +| `server.base_url` | string | — | External URL (e.g. `https://silo.example.com`). Used for OIDC callback URLs and session cookie domain. Required when OIDC is enabled. | + +```yaml +server: + host: "0.0.0.0" + port: 8080 + base_url: "https://silo.example.com" +``` + +--- + +## Database + +| Key | Type | Default | Env Override | Description | +|-----|------|---------|-------------|-------------| +| `database.host` | string | — | `SILO_DB_HOST` | PostgreSQL host | +| `database.port` | int | `5432` | — | PostgreSQL port | +| `database.name` | string | — | `SILO_DB_NAME` | Database name | +| `database.user` | string | — | `SILO_DB_USER` | Database user | +| `database.password` | string | — | `SILO_DB_PASSWORD` | Database password | +| `database.sslmode` | string | `"require"` | — | SSL mode: `disable`, `require`, `verify-ca`, `verify-full` | +| `database.max_connections` | int | `10` | — | Connection pool size | + +**SSL mode guidance:** +- `disable` — development only, no encryption +- `require` — encrypted but no certificate verification (default) +- `verify-ca` — verify server certificate is signed by trusted CA +- `verify-full` — verify CA and hostname match (recommended for production) + +```yaml +database: + host: "localhost" + port: 5432 + name: "silo" + user: "silo" + password: "" # use SILO_DB_PASSWORD env var + sslmode: "require" + max_connections: 10 +``` + +--- + +## Storage (MinIO/S3) + +| Key | Type | Default | Env Override | Description | +|-----|------|---------|-------------|-------------| +| `storage.endpoint` | string | — | `SILO_MINIO_ENDPOINT` | MinIO/S3 endpoint (`host:port`) | +| `storage.access_key` | string | — | `SILO_MINIO_ACCESS_KEY` | Access key | +| `storage.secret_key` | string | — | `SILO_MINIO_SECRET_KEY` | Secret key | +| `storage.bucket` | string | — | — | S3 bucket name (created automatically if missing) | +| `storage.use_ssl` | bool | `false` | — | Use HTTPS for MinIO connections | +| `storage.region` | string | `"us-east-1"` | — | S3 region | + +```yaml +storage: + endpoint: "localhost:9000" + access_key: "" # use SILO_MINIO_ACCESS_KEY env var + secret_key: "" # use SILO_MINIO_SECRET_KEY env var + bucket: "silo-files" + use_ssl: false + region: "us-east-1" +``` + +--- + +## Schemas + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `schemas.directory` | string | `"/etc/silo/schemas"` | Path to directory containing YAML schema files | +| `schemas.default` | string | — | Default schema name for part number generation | + +Schema files define part numbering formats, category codes, and property definitions. See `schemas/kindred-rd.yaml` for an example. + +```yaml +schemas: + directory: "./schemas" + default: "kindred-rd" +``` + +--- + +## FreeCAD + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `freecad.uri_scheme` | string | `"silo"` | URI scheme for "Open in FreeCAD" links in the web UI | +| `freecad.executable` | string | — | Path to FreeCAD binary (for CLI operations) | + +```yaml +freecad: + uri_scheme: "silo" + executable: "/usr/bin/freecad" +``` + +--- + +## Odoo ERP Integration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `odoo.enabled` | bool | `false` | Enable Odoo integration | +| `odoo.url` | string | — | Odoo server URL | +| `odoo.database` | string | — | Odoo database name | +| `odoo.username` | string | — | Odoo username | +| `odoo.api_key` | string | — | Odoo API key | + +The Odoo integration currently supports configuration and sync-log CRUD. Push/pull sync operations are stubs. + +```yaml +odoo: + enabled: false + url: "https://odoo.example.com" + database: "odoo" + username: "silo-service" + api_key: "" +``` + +--- + +## Authentication + +Authentication has a master toggle and three independent backends. When `auth.enabled` is `false`, all routes are accessible without login and a synthetic admin user (`dev`) is injected into every request. + +| Key | Type | Default | Env Override | Description | +|-----|------|---------|-------------|-------------| +| `auth.enabled` | bool | `false` | — | Master toggle. Set `true` for production. | +| `auth.session_secret` | string | — | `SILO_SESSION_SECRET` | Secret for signing session cookies. Required when auth is enabled. | + +### Local Auth + +Built-in username/password accounts stored in the Silo database with bcrypt-hashed passwords. + +| Key | Type | Default | Env Override | Description | +|-----|------|---------|-------------|-------------| +| `auth.local.enabled` | bool | — | — | Enable local accounts | +| `auth.local.default_admin_username` | string | — | `SILO_ADMIN_USERNAME` | Default admin account created on first startup | +| `auth.local.default_admin_password` | string | — | `SILO_ADMIN_PASSWORD` | Password for default admin (bcrypt-hashed on creation) | + +The default admin account is only created if both username and password are set and the user does not already exist. This is idempotent. + +### LDAP / FreeIPA + +| Key | Type | Default | Env Override | Description | +|-----|------|---------|-------------|-------------| +| `auth.ldap.enabled` | bool | `false` | — | Enable LDAP authentication | +| `auth.ldap.url` | string | — | — | LDAP server URL (e.g. `ldaps://ipa.example.com`) | +| `auth.ldap.base_dn` | string | — | — | Base DN for the LDAP tree | +| `auth.ldap.user_search_dn` | string | — | — | DN under which to search for users | +| `auth.ldap.bind_dn` | string | — | — | Service account DN for user lookups (optional; omit for direct user bind) | +| `auth.ldap.bind_password` | string | — | `SILO_LDAP_BIND_PASSWORD` | Service account password | +| `auth.ldap.user_attr` | string | `"uid"` | — | LDAP attribute for username | +| `auth.ldap.email_attr` | string | `"mail"` | — | LDAP attribute for email | +| `auth.ldap.display_attr` | string | `"displayName"` | — | LDAP attribute for display name | +| `auth.ldap.group_attr` | string | `"memberOf"` | — | LDAP attribute for group membership | +| `auth.ldap.role_mapping` | map | — | — | Maps LDAP group DNs to Silo roles (see example below) | +| `auth.ldap.tls_skip_verify` | bool | `false` | — | Skip TLS certificate verification (testing only) | + +**Role mapping** maps LDAP group DNs to Silo roles. Groups are checked in priority order: admin, then editor, then viewer. The first match wins. + +```yaml +auth: + ldap: + enabled: true + url: "ldaps://ipa.example.com" + base_dn: "dc=example,dc=com" + user_search_dn: "cn=users,cn=accounts,dc=example,dc=com" + role_mapping: + admin: + - "cn=silo-admins,cn=groups,cn=accounts,dc=example,dc=com" + editor: + - "cn=silo-users,cn=groups,cn=accounts,dc=example,dc=com" + - "cn=engineers,cn=groups,cn=accounts,dc=example,dc=com" + viewer: + - "cn=silo-viewers,cn=groups,cn=accounts,dc=example,dc=com" +``` + +### OIDC / Keycloak + +| Key | Type | Default | Env Override | Description | +|-----|------|---------|-------------|-------------| +| `auth.oidc.enabled` | bool | `false` | — | Enable OIDC authentication | +| `auth.oidc.issuer_url` | string | — | — | OIDC provider issuer URL (e.g. Keycloak realm URL) | +| `auth.oidc.client_id` | string | — | — | OAuth2 client ID | +| `auth.oidc.client_secret` | string | — | `SILO_OIDC_CLIENT_SECRET` | OAuth2 client secret | +| `auth.oidc.redirect_url` | string | — | — | OAuth2 callback URL (typically `{base_url}/auth/callback`) | +| `auth.oidc.scopes` | []string | `["openid", "profile", "email"]` | — | OAuth2 scopes to request | +| `auth.oidc.admin_role` | string | — | — | Keycloak realm role that grants admin access | +| `auth.oidc.editor_role` | string | — | — | Keycloak realm role that grants editor access | +| `auth.oidc.default_role` | string | `"viewer"` | — | Fallback role when no role claim matches | + +Roles are extracted from the Keycloak `realm_access.roles` claim. If the user has the `admin_role`, they get admin. Otherwise if they have `editor_role`, they get editor. Otherwise `default_role` applies. + +```yaml +auth: + oidc: + enabled: true + issuer_url: "https://keycloak.example.com/realms/silo" + client_id: "silo" + client_secret: "" # use SILO_OIDC_CLIENT_SECRET env var + redirect_url: "https://silo.example.com/auth/callback" + admin_role: "silo-admin" + editor_role: "silo-editor" + default_role: "viewer" +``` + +### CORS + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `auth.cors.allowed_origins` | []string | — | Origins allowed for cross-origin requests from browser-based clients | + +FreeCAD and other non-browser clients use direct HTTP and are not affected by CORS. This setting is for browser-based tools running on different origins. + +```yaml +auth: + cors: + allowed_origins: + - "https://silo.example.com" +``` + +--- + +## Environment Variables + +All environment variable overrides. These take precedence over values in `config.yaml`. + +| Variable | Config Key | Description | +|----------|-----------|-------------| +| `SILO_DB_HOST` | `database.host` | PostgreSQL host | +| `SILO_DB_NAME` | `database.name` | PostgreSQL database name | +| `SILO_DB_USER` | `database.user` | PostgreSQL user | +| `SILO_DB_PASSWORD` | `database.password` | PostgreSQL password | +| `SILO_MINIO_ENDPOINT` | `storage.endpoint` | MinIO endpoint | +| `SILO_MINIO_ACCESS_KEY` | `storage.access_key` | MinIO access key | +| `SILO_MINIO_SECRET_KEY` | `storage.secret_key` | MinIO secret key | +| `SILO_SESSION_SECRET` | `auth.session_secret` | Session cookie signing secret | +| `SILO_ADMIN_USERNAME` | `auth.local.default_admin_username` | Default admin username | +| `SILO_ADMIN_PASSWORD` | `auth.local.default_admin_password` | Default admin password | +| `SILO_LDAP_BIND_PASSWORD` | `auth.ldap.bind_password` | LDAP service account password | +| `SILO_OIDC_CLIENT_SECRET` | `auth.oidc.client_secret` | OIDC client secret | + +Additionally, YAML values can reference environment variables directly using `${VAR_NAME}` syntax, which is expanded at load time via `os.ExpandEnv()`. + +--- + +## Deployment Examples + +### Development (no auth) + +```yaml +server: + host: "0.0.0.0" + port: 8080 + base_url: "http://localhost:8080" + +database: + host: "localhost" + port: 5432 + name: "silo" + user: "silo" + password: "silodev" + sslmode: "disable" + +storage: + endpoint: "localhost:9000" + access_key: "minioadmin" + secret_key: "minioadmin" + bucket: "silo-files" + use_ssl: false + +schemas: + directory: "./schemas" + default: "kindred-rd" + +auth: + enabled: false +``` + +### Local Auth Only + +```yaml +auth: + enabled: true + session_secret: "change-me-to-a-random-string" + local: + enabled: true + default_admin_username: "admin" + default_admin_password: "change-me" +``` + +### LDAP / FreeIPA + +```yaml +auth: + enabled: true + session_secret: "${SILO_SESSION_SECRET}" + local: + enabled: false + ldap: + enabled: true + url: "ldaps://ipa.example.com" + base_dn: "dc=example,dc=com" + user_search_dn: "cn=users,cn=accounts,dc=example,dc=com" + role_mapping: + admin: + - "cn=silo-admins,cn=groups,cn=accounts,dc=example,dc=com" + editor: + - "cn=engineers,cn=groups,cn=accounts,dc=example,dc=com" + viewer: + - "cn=silo-viewers,cn=groups,cn=accounts,dc=example,dc=com" +``` + +### OIDC / Keycloak + +```yaml +auth: + enabled: true + session_secret: "${SILO_SESSION_SECRET}" + local: + enabled: false + oidc: + enabled: true + issuer_url: "https://keycloak.example.com/realms/silo" + client_id: "silo" + client_secret: "${SILO_OIDC_CLIENT_SECRET}" + redirect_url: "https://silo.example.com/auth/callback" + admin_role: "silo-admin" + editor_role: "silo-editor" + default_role: "viewer" +```