OSAPI is configured through a YAML file and optional environment variable
overrides.
Config File
By default OSAPI looks for /etc/osapi/osapi.yaml. Override the path with the
-f / --osapi-file flag:
osapi -f /path/to/osapi.yaml controller start
Validation
Configuration is validated at startup. If any field has an invalid value, OSAPI
exits with a clear error message before starting any component. Validation uses
the same go-playground/validator library as the API handlers.
- Required fields must be present and non-empty (e.g.,
signing_key,
bearer_token, stream and bucket names)
signing_key must also be at least 32 characters; generate one with
openssl rand -hex 32
- Port fields must be between 1 and 65535
- Duration fields must be valid Go durations (e.g.,
30s, 5m, 1h,
720h). The d (day) suffix is not supported — use hours instead (e.g.,
168h for 7 days)
- Condition thresholds must be within valid ranges (1–100 for percentages,
>0 for load multiplier)
Most fields have sensible defaults set via Viper and can be omitted from the
config file.
Environment Variables
Every config key can be overridden with an environment variable using the
OSAPI_ prefix. Dots and nested keys become underscores, and the name is
uppercased:
| Config Key | Environment Variable |
|---|
debug | OSAPI_DEBUG |
controller.api.port | OSAPI_CONTROLLER_API_PORT |
controller.api.nats.host | OSAPI_CONTROLLER_API_NATS_HOST |
controller.api.nats.port | OSAPI_CONTROLLER_API_NATS_PORT |
controller.api.nats.client_name | OSAPI_CONTROLLER_API_NATS_CLIENT_NAME |
controller.api.nats.namespace | OSAPI_CONTROLLER_API_NATS_NAMESPACE |
controller.api.nats.auth.type | OSAPI_CONTROLLER_API_NATS_AUTH_TYPE |
controller.api.job_timeout | OSAPI_CONTROLLER_API_JOB_TIMEOUT |
controller.api.security.signing_key | OSAPI_CONTROLLER_API_SECURITY_SIGNING_KEY |
controller.client.security.bearer_token | OSAPI_CONTROLLER_CLIENT_SECURITY_BEARER_TOKEN |
controller.metrics.enabled | OSAPI_CONTROLLER_METRICS_ENABLED |
controller.metrics.port | OSAPI_CONTROLLER_METRICS_PORT |
controller.pki.enabled | OSAPI_CONTROLLER_PKI_ENABLED |
controller.pki.key_dir | OSAPI_CONTROLLER_PKI_KEY_DIR |
controller.pki.auto_accept | OSAPI_CONTROLLER_PKI_AUTO_ACCEPT |
controller.pki.rotation_grace_period | OSAPI_CONTROLLER_PKI_ROTATION_GRACE_PERIOD |
nats.server.host | OSAPI_NATS_SERVER_HOST |
nats.server.port | OSAPI_NATS_SERVER_PORT |
nats.server.namespace | OSAPI_NATS_SERVER_NAMESPACE |
nats.server.auth.type | OSAPI_NATS_SERVER_AUTH_TYPE |
nats.server.metrics.enabled | OSAPI_NATS_SERVER_METRICS_ENABLED |
nats.server.metrics.port | OSAPI_NATS_SERVER_METRICS_PORT |
nats.stream.name | OSAPI_NATS_STREAM_NAME |
nats.kv.bucket | OSAPI_NATS_KV_BUCKET |
nats.kv.response_bucket | OSAPI_NATS_KV_RESPONSE_BUCKET |
nats.audit.stream | OSAPI_NATS_AUDIT_STREAM |
nats.audit.subject | OSAPI_NATS_AUDIT_SUBJECT |
nats.audit.max_age | OSAPI_NATS_AUDIT_MAX_AGE |
nats.audit.max_bytes | OSAPI_NATS_AUDIT_MAX_BYTES |
nats.audit.storage | OSAPI_NATS_AUDIT_STORAGE |
nats.audit.replicas | OSAPI_NATS_AUDIT_REPLICAS |
nats.registry.bucket | OSAPI_NATS_REGISTRY_BUCKET |
nats.registry.ttl | OSAPI_NATS_REGISTRY_TTL |
nats.registry.storage | OSAPI_NATS_REGISTRY_STORAGE |
nats.registry.replicas | OSAPI_NATS_REGISTRY_REPLICAS |
nats.facts.bucket | OSAPI_NATS_FACTS_BUCKET |
nats.facts.ttl | OSAPI_NATS_FACTS_TTL |
nats.facts.storage | OSAPI_NATS_FACTS_STORAGE |
nats.facts.replicas | OSAPI_NATS_FACTS_REPLICAS |
nats.state.bucket | OSAPI_NATS_STATE_BUCKET |
nats.state.storage | OSAPI_NATS_STATE_STORAGE |
nats.state.replicas | OSAPI_NATS_STATE_REPLICAS |
nats.objects.bucket | OSAPI_NATS_OBJECTS_BUCKET |
nats.objects.max_bytes | OSAPI_NATS_OBJECTS_MAX_BYTES |
nats.objects.storage | OSAPI_NATS_OBJECTS_STORAGE |
nats.objects.replicas | OSAPI_NATS_OBJECTS_REPLICAS |
nats.objects.max_chunk_size | OSAPI_NATS_OBJECTS_MAX_CHUNK_SIZE |
nats.file_state.bucket | OSAPI_NATS_FILE_STATE_BUCKET |
nats.file_state.storage | OSAPI_NATS_FILE_STATE_STORAGE |
nats.file_state.replicas | OSAPI_NATS_FILE_STATE_REPLICAS |
telemetry.tracing.enabled | OSAPI_TELEMETRY_TRACING_ENABLED |
telemetry.tracing.exporter | OSAPI_TELEMETRY_TRACING_EXPORTER |
telemetry.tracing.otlp_endpoint | OSAPI_TELEMETRY_TRACING_OTLP_ENDPOINT |
controller.notifications.enabled | OSAPI_CONTROLLER_NOTIFICATIONS_ENABLED |
controller.notifications.notifier | OSAPI_CONTROLLER_NOTIFICATIONS_NOTIFIER |
controller.notifications.renotify_interval | OSAPI_CONTROLLER_NOTIFICATIONS_RENOTIFY_INTERVAL |
agent.nats.host | OSAPI_AGENT_NATS_HOST |
agent.nats.port | OSAPI_AGENT_NATS_PORT |
agent.nats.client_name | OSAPI_AGENT_NATS_CLIENT_NAME |
agent.nats.namespace | OSAPI_AGENT_NATS_NAMESPACE |
agent.nats.auth.type | OSAPI_AGENT_NATS_AUTH_TYPE |
agent.hostname | OSAPI_AGENT_HOSTNAME |
agent.facts.interval | OSAPI_AGENT_FACTS_INTERVAL |
agent.conditions.memory_pressure_threshold | OSAPI_AGENT_CONDITIONS_MEMORY_PRESSURE_THRESHOLD |
agent.conditions.high_load_multiplier | OSAPI_AGENT_CONDITIONS_HIGH_LOAD_MULTIPLIER |
agent.conditions.disk_pressure_threshold | OSAPI_AGENT_CONDITIONS_DISK_PRESSURE_THRESHOLD |
agent.process_conditions.memory_pressure_bytes | OSAPI_AGENT_PROCESS_CONDITIONS_MEMORY_PRESSURE_BYTES |
agent.process_conditions.high_cpu_percent | OSAPI_AGENT_PROCESS_CONDITIONS_HIGH_CPU_PERCENT |
agent.metrics.enabled | OSAPI_AGENT_METRICS_ENABLED |
agent.metrics.port | OSAPI_AGENT_METRICS_PORT |
agent.privilege_escalation.enabled | OSAPI_AGENT_PRIVILEGE_ESCALATION_ENABLED |
agent.pki.enabled | OSAPI_AGENT_PKI_ENABLED |
agent.pki.key_dir | OSAPI_AGENT_PKI_KEY_DIR |
Environment variables take precedence over file values.
Required Fields
Seven fields carry a required validation tag. Five of them
(nats.stream.name, nats.stream.subjects, nats.kv.bucket,
nats.kv.response_bucket, and nats.registry.bucket) are given defaults via
viper.SetDefault(), so they satisfy validation without appearing in your
config file. Two have no default and must be set before the server or client
will start:
| Key | Purpose |
|---|
controller.api.security.signing_key | HS256 key for signing JWTs |
controller.client.security.bearer_token | JWT sent with client requests |
Generate a signing key with openssl rand -hex 32; the value must be at least
32 characters. Generate a bearer token with osapi token generate.
Authentication
Each NATS connection supports pluggable authentication. Set the auth.type
field in the relevant section (nats.server, controller.api.nats, or
agent.nats):
| Type | Description | Extra Fields |
|---|
none | No authentication (default) | — |
user_pass | Username and password | username, password |
nkey | NKey-based auth (server and clients) | See examples below |
Server-Side Auth
The embedded NATS server (nats.server.auth) accepts a list of users or nkeys:
nats:
api:
auth:
type: user_pass
users:
- username: osapi
password: '<secret>'
Client-Side Auth
API server and agent connections (controller.api.nats.auth, agent.nats.auth)
authenticate as a single identity:
controller:
api:
nats:
auth:
type: user_pass
username: osapi
password: '<secret>'
Permissions
OSAPI uses fine-grained resource:verb permissions for access control. Each API
endpoint requires a specific permission. Built-in roles expand to a default set
of permissions:
| Role | Permissions |
|---|
admin | agent:read, agent:write, node:read, node:write, network:read, network:write, job:read, job:write, health:read, audit:read, command:execute, command:shell, file:read, file:write, docker:read, docker:write, docker:execute, cron:read, cron:write, sysctl:read, sysctl:write, ntp:read, ntp:write, timezone:read, timezone:write, power:execute, process:read, process:execute, user:read, user:write, package:read, package:write, log:read, certificate:read, certificate:write, service:read, service:write |
write | agent:read, node:read, node:write, network:read, network:write, job:read, job:write, health:read, file:read, file:write, docker:read, docker:write, cron:read, cron:write, sysctl:read, sysctl:write, ntp:read, ntp:write, timezone:read, timezone:write, process:read, user:read, user:write, package:read, package:write, log:read, certificate:read, certificate:write, service:read, service:write |
read | agent:read, node:read, network:read, job:read, health:read, file:read, docker:read, cron:read, sysctl:read, ntp:read, timezone:read, process:read, user:read, package:read, log:read, certificate:read, service:read |
Custom Roles
You can define custom roles in the controller.api.security.roles section.
Custom roles override the default permission mapping for the same name, or
define entirely new role names:
controller:
api:
security:
roles:
ops:
permissions:
- node:read
- health:read
netadmin:
permissions:
- network:read
- network:write
- health:read
Direct Permissions
Tokens can carry a permissions claim that overrides role-based expansion. When
the claim is present, only the listed permissions are granted regardless of the
token's roles. Generate a token with direct permissions:
osapi token generate -r admin -u user@example.com \
-p node:read -p health:read
Namespace
The namespace field on NATS connections prefixes all subject names and
infrastructure names. This allows multiple OSAPI deployments to share a single
NATS cluster without collisions.
| Without namespace | With namespace: osapi |
|---|
jobs.query._any | osapi.jobs.query._any |
JOBS (stream) | osapi-JOBS |
job-queue (KV bucket) | osapi-job-queue |
Set the same namespace value in nats.server.namespace,
controller.api.nats.namespace, and agent.nats.namespace so all components
agree on naming. An empty string disables prefixing.
Full Reference
Below is a complete osapi.yaml with every supported field and inline comments.
Values shown are representative defaults from the repository's config file.
debug: true
controller:
client:
url: 'http://0.0.0.0:8080'
security:
bearer_token: '<jwt>'
ui:
enabled: true
api:
port: 8080
job_timeout: '30s'
nats:
host: 'localhost'
port: 4222
client_name: 'osapi-api'
namespace: 'osapi'
auth:
type: 'none'
security:
signing_key: '<secret>'
cors:
allow_origins:
- 'http://localhost:3001'
- 'https://osapi-io.github.io'
metrics:
enabled: true
port: 9090
notifications:
enabled: true
notifier: 'log'
renotify_interval: '5m'
pki:
enabled: false
key_dir: '/etc/osapi/pki'
auto_accept: false
rotation_grace_period: '24h'
nats:
api:
host: 'localhost'
port: 4222
store_dir: '.nats/jetstream/'
namespace: 'osapi'
auth:
type: 'none'
metrics:
enabled: true
port: 9092
stream:
name: 'JOBS'
subjects: 'jobs.>'
max_age: '24h'
max_msgs: 10000
storage: 'file'
replicas: 1
discard: 'old'
kv:
bucket: 'job-queue'
response_bucket: 'job-responses'
ttl: '1h'
max_bytes: 104857600
storage: 'file'
replicas: 1
audit:
stream: 'AUDIT'
subject: 'audit'
max_age: '720h'
max_bytes: 52428800
storage: 'file'
replicas: 1
registry:
bucket: 'agent-registry'
ttl: '30s'
storage: 'file'
replicas: 1
facts:
bucket: 'agent-facts'
ttl: '5m'
storage: 'file'
replicas: 1
state:
bucket: 'agent-state'
storage: 'file'
replicas: 1
objects:
bucket: 'file-objects'
max_bytes: 104857600
storage: 'file'
replicas: 1
max_chunk_size: 262144
file_state:
bucket: 'file-state'
storage: 'file'
replicas: 1
dlq:
max_age: '168h'
max_msgs: 1000
storage: 'file'
replicas: 1
telemetry:
tracing:
enabled: false
agent:
nats:
host: 'localhost'
port: 4222
client_name: 'osapi-agent'
namespace: 'osapi'
auth:
type: 'none'
consumer:
name: 'jobs-agent'
max_deliver: 5
ack_wait: '2m'
max_ack_pending: 1000
replay_policy: 'instant'
back_off:
- '30s'
- '2m'
- '5m'
- '15m'
- '30m'
facts:
interval: '60s'
conditions:
memory_pressure_threshold: 90
high_load_multiplier: 2.0
disk_pressure_threshold: 90
process_conditions:
memory_pressure_bytes: 0
high_cpu_percent: 0
queue_group: 'job-agents'
hostname: ''
max_jobs: 10
labels:
group: 'web.dev.us-east'
metrics:
enabled: true
port: 9091
privilege_escalation:
enabled: false
pki:
enabled: false
key_dir: '/etc/osapi/pki'
Section Reference
controller.client
| Key | Type | Description |
|---|
url | string | Base URL the CLI client targets |
security.bearer_token | string | JWT for client auth (required) |
controller.ui
| Key | Type | Description |
|---|
enabled | bool | Serve embedded UI at / (default true) |
controller.api
| Key | Type | Description |
|---|
port | int | Port the API server listens on |
nats.host | string | NATS server hostname |
nats.port | int | NATS server port |
nats.client_name | string | NATS client identification name |
nats.namespace | string | Subject namespace prefix |
nats.auth.type | string | Auth type: none, user_pass |
nats.auth.username | string | Username for user_pass auth |
nats.auth.password | string | Password for user_pass auth |
security.signing_key | string | HS256 JWT signing key (required) |
security.cors.allow_origins | []string | Allowed CORS origins |
security.roles | map | Custom roles with permissions lists |
job_timeout | string | Agent response timeout (default 30s) |
controller.metrics
| Key | Type | Description |
|---|
enabled | bool | Enable the metrics server (default: true) |
port | int | Port the metrics server listens on (default: 9090) |
When enabled, the port also serves /health (liveness) and /health/ready
(readiness) probes without authentication.
controller.pki
| Key | Type | Description |
|---|
enabled | bool | Enable PKI enrollment and job signing |
key_dir | string | Directory for controller keypair (default: /etc/osapi/pki) |
auto_accept | bool | Auto-accept agent enrollments (default: false) |
rotation_grace_period | string | Both keys accepted during rotation (default: 24h) |
nats.server
| Key | Type | Description |
|---|
host | string | Hostname the NATS server binds to |
port | int | Port the NATS server binds to |
store_dir | string | Directory for JetStream file storage |
namespace | string | Namespace prefix for infrastructure |
auth.type | string | Auth type: none, user_pass |
auth.users | list | Users for user_pass auth (see below) |
auth.nkeys | list | Public nkeys for nkey auth |
nats.server.metrics
| Key | Type | Description |
|---|
enabled | bool | Enable the metrics server (default: true) |
port | int | Port the metrics server listens on (default: 9092) |
When enabled, the port also serves /health (liveness) and /health/ready
(readiness) probes without authentication.
nats.stream
| Key | Type | Description |
|---|
name | string | JetStream stream name |
subjects | string | Subject filter for the stream |
max_age | string | Maximum message age (Go duration) |
max_msgs | int | Maximum number of messages |
storage | string | "file" or "memory" |
replicas | int | Number of stream replicas |
discard | string | Discard policy: "old" or "new" |
nats.kv
| Key | Type | Description |
|---|
bucket | string | KV bucket for job definitions and events |
response_bucket | string | KV bucket for agent results |
ttl | string | Entry time-to-live (Go duration) |
max_bytes | int | Maximum bucket size in bytes |
storage | string | "file" or "memory" |
replicas | int | Number of KV replicas |
nats.audit
| Key | Type | Description |
|---|
stream | string | JetStream stream name for audit log entries |
subject | string | Base subject prefix for audit messages |
max_age | string | Maximum entry age (Go duration) |
max_bytes | int | Maximum stream size in bytes |
storage | string | "file" or "memory" |
replicas | int | Number of stream replicas |
nats.registry
| Key | Type | Description |
|---|
bucket | string | KV bucket for agent heartbeat entries |
ttl | string | Entry time-to-live / liveness timeout |
storage | string | "file" or "memory" |
replicas | int | Number of KV replicas |
nats.facts
| Key | Type | Description |
|---|
bucket | string | KV bucket for agent facts entries |
ttl | string | Entry time-to-live (Go duration) |
storage | string | "file" or "memory" |
replicas | int | Number of KV replicas |
nats.state
| Key | Type | Description |
|---|
bucket | string | KV bucket for persistent agent state (no TTL) |
storage | string | "file" or "memory" |
replicas | int | Number of KV replicas |
nats.objects
| Key | Type | Description |
|---|
bucket | string | Object Store bucket for file uploads |
max_bytes | int | Maximum bucket size in bytes |
storage | string | "file" or "memory" |
replicas | int | Number of Object Store replicas |
max_chunk_size | int | Maximum chunk size for uploads |
nats.file_state
| Key | Type | Description |
|---|
bucket | string | KV bucket for file deploy state (no TTL) |
storage | string | "file" or "memory" |
replicas | int | Number of KV replicas |
nats.dlq
| Key | Type | Description |
|---|
max_age | string | Maximum message age (Go duration) |
max_msgs | int | Maximum number of messages |
storage | string | "file" or "memory" |
replicas | int | Number of DLQ replicas |
telemetry.tracing
| Key | Type | Description |
|---|
enabled | bool | Enable distributed tracing (default: false) |
exporter | string | "stdout", "otlp", or unset (log correlation only, no span export) |
otlp_endpoint | string | gRPC endpoint for OTLP exporter (required when exporter is "otlp") |
controller.notifications
| Key | Type | Description |
|---|
enabled | bool | Enable the condition watcher and notifier (default: false) |
notifier | string | Notification backend: "log" writes condition events to the server log |
renotify_interval | string | Re-fire interval for active conditions (Go duration, default: "0") |
agent
| Key | Type | Description |
|---|
nats.host | string | NATS server hostname |
nats.port | int | NATS server port |
nats.client_name | string | NATS client identification name |
nats.namespace | string | Subject namespace prefix |
nats.auth.type | string | Auth type: none, user_pass |
nats.auth.username | string | Username for user_pass auth |
nats.auth.password | string | Password for user_pass auth |
consumer.name | string | Durable consumer name |
consumer.max_deliver | int | Max redelivery attempts before DLQ |
consumer.ack_wait | string | ACK timeout (Go duration) |
consumer.max_ack_pending | int | Max outstanding unacknowledged msgs |
consumer.replay_policy | string | "instant" or "original" |
consumer.back_off | []string | Backoff durations between redeliveries |
queue_group | string | Queue group for load-balanced routing |
hostname | string | Agent hostname (defaults to OS hostname) |
max_jobs | int | Max concurrent jobs |
facts.interval | string | How often the agent collects facts |
conditions.memory_pressure_threshold | int | Memory pressure threshold percent (default 90) |
conditions.high_load_multiplier | float | Load multiplier over CPU count (default 2.0) |
conditions.disk_pressure_threshold | int | Disk pressure threshold percent (default 90) |
process_conditions.memory_pressure_bytes | int64 | Process RSS threshold in bytes (0 = disabled) |
process_conditions.high_cpu_percent | float | Process CPU usage threshold as a percentage (0 = disabled) |
labels | map[string]string | Key-value pairs for label-based routing (max 5) |
metrics.enabled | bool | Enable the metrics server (default: true) |
metrics.port | int | Port the metrics server listens on (default: 9091) |
privilege_escalation.enabled | bool | Activate sudo and capability checks (default false) |
pki.enabled | bool | Enable PKI enrollment and job verify (default false) |
pki.key_dir | string | Directory for agent keypair (default /etc/osapi/pki) |
When metrics.enabled is true, the port also serves /health (liveness) and
/health/ready (readiness) probes without authentication.