arizuko

arizuko › reference › Environment variables

environment variables

The environment variables of the arizuko daemons: the file and line that read each one, the type, the default and the effect. The last section names the variables that the code reads and that this page does not describe. Test-only variables are left out.

Four helpers read most variables. chanlib.EnvOr(k, d) returns d when the variable is empty or unset. chanlib.MustEnv(k) calls os.Exit(1) when the variable is unset. chanlib.EnvDur(k, d) reads a Go duration such as 30s or 5m, and returns d when the value is empty or does not parse. The code default of an adapter LISTEN_ADDR differs for each daemon, but every service fragment in template/services/ sets :8080. The numbers 9001 to 9010 below are the source defaults for local development with several daemons.

core — consumed by every Go daemon via core.LoadConfig

These are read in core/config.go through chanlib.EnvOr / EnvInt / EnvDur and the resolveTimezone() helper. Every daemon that calls core.LoadConfig() (routd, onbod, webd, …) inherits this surface.

API_PORT

Listening port for routd's HTTP API.

Read bycore / core/config.go:256 → routd
Typeint
Default8080
Example8080

ARIZUKO_DEV

Dev mode — shares ant/src into every turn's guest for live-reload of the in-guest runner.

Read byruned / runed/mounts.go:94
Typebool ("true" or "1")
Defaultunset = production mode
Example1

ASSISTANT_NAME

Bot display name and mention trigger; also baked into the data-dir basename and compose container_name. Must contain no whitespace or shell metachars.

Read bycore / core/config.go:218, webd / webd/main.go:46, teled / teled/main.go:97, discd / discd/main.go:88, whapd / whapd/src/main.ts:45
Typestring
Defaultarizuko in core and routd; assistant in webd; empty in teled, discd and slakd
Examplearizuko

AUTH_BASE_URL

Public base URL used to build OAuth redirect callbacks and onboarding invite links. HTTPS prefix toggles secure-cookie flag.

Read bycore / core/config.go:231
TypeURL
Default""
Examplehttps://demo.example.com

AUTH_SECRET

Keys the OAuth CSRF state of authd and dashd, and switches the auth of proxyd on. It signs no cookie and seals no stored value: the refresh cookie of authd is an opaque token stored as a sha256 digest, access tokens are ES256, and SECRETS_KEY seals the secrets. arizuko create writes a random value.

Read bycore / core/config.go:229
Typestring (hex/random)
Default""
Examplea3f1...c0de

CONTAINER_TIMEOUT

Per-turn guest ceiling, in milliseconds. It is an input to the one deadline, not a timer of its own: runed/manager.go turnTTL takes the lower of RUNED_RUN_TIMEOUT and this value, and a group's own container_config timeout overrides it for that folder. The name kept its Docker-era spelling; what it bounds is a KVM guest.

Read bycore / core/config.go:225 → runed
Typeint (milliseconds, via EnvDur)
Default3600000 (60 min)
Example1800000

DATA_DIR

Root of this instance's data directory (store/, groups/, ipc/, web/). When set in compose, also passes through to dashd and several adapters as their working/storage root.

Read bycore / core/config.go:161, dashd / dashd/main.go:167, teled / teled/main.go:89, whapd / whapd/src/main.ts:46
Typepath
Defaultcwd (core); /srv/data/<daemon> per adapter
Example/srv/data/arizuko_demo

DEX_ISSUER

OIDC issuer URL of a Dex instance. With DEX_CLIENT_ID it mounts /auth/dex on authd: Dex authenticates the person against its connectors, authd reads federated_claims off the ID token and mints the session for <connector_id>:<user_id> — the principal the native callback for that provider would mint. The folder claim and scope snapshot stay authd's. Unset leaves the login page as it is.

Read byauthd / core/config.go
TypeURL
Default""
Examplehttps://auth.example.com/dex

DEX_CLIENT_ID

The static client id authd presents to Dex; the ID token's audience must equal it. Pair with DEX_CLIENT_SECRET for a confidential client; a public client with PKCE needs no secret.

Read byauthd / core/config.go
Typestring
Default""
Examplearizuko

DEX_CLIENT_SECRET

Secret for DEX_CLIENT_ID, sent on the code exchange. Optional.

Read byauthd / core/config.go
Typestring
Default""
Examplea-long-random-secret

DISCORD_CLIENT_ID

OAuth client id for the Discord login provider in proxyd's auth flow (distinct from the bot token used by discd).

Read bycore / core/config.go:234
Typestring
Default""
Example1234567890

DISCORD_CLIENT_SECRET

OAuth client secret for the Discord login provider.

Read bycore / core/config.go:235
Typestring
Default""
Exampleabc...def

GITHUB_ALLOWED_ORG

If set, restricts GitHub OAuth sign-in to members of this org.

Read bycore / core/config.go:239
Typestring
Default""
Examplekronael

GITHUB_CLIENT_ID

OAuth client id for the GitHub login provider.

Read bycore / core/config.go:232
Typestring
Default""
ExampleIv1.abc123

GITHUB_CLIENT_SECRET

OAuth client secret for the GitHub login provider.

Read bycore / core/config.go:233
Typestring
Default""
Exampleghs_xxxxx

GOOGLE_ALLOWED_EMAILS

CSV allowlist of email addresses permitted to complete Google OAuth.

Read bycore / core/config.go:238
TypeCSV string
Default""
Examplealice@example.com,bob@example.com

GOOGLE_CLIENT_ID

OAuth client id for the Google login provider.

Read bycore / core/config.go:236
Typestring
Default""
Exampleabc.apps.googleusercontent.com

GOOGLE_CLIENT_SECRET

OAuth client secret for the Google login provider.

Read bycore / core/config.go:237
Typestring
Default""
ExampleGOCSPX-xxx

HOST_APP_DIR

Path on the host that contains the arizuko binaries and ant/src; used when bind-mounting the dev runner.

Read bycore / core/config.go:46, compose / compose/compose.go:66
Typepath
Defaultdirectory of the running executable
Example/home/operator/app/arizuko

HOST_CODEX_DIR

The operator's own codex login state, so the oracle skill can reuse it. Two files are overmounted read-only into the guest's /home/node/.codex/ — auth.json and config.toml — not the whole directory. The overmount carries the host file's owner and mode, so a credential at 0600 owned by the operator is unreadable to the agent's uid and codex reports "not logged in"; runed names the offending file in its log. compose also gives runed its own read-only mount of this path, at the same absolute path, because runed can share in only what runed can open.

Read bycore / core/config.go:253 → runed/mounts.go buildMounts
Typepath
Default"" (mount disabled; falls back to the CODEX_API_KEY/OPENAI_API_KEY env-profile keys)
Example/home/operator/.codex

HOST_DATA_DIR

The data dir as the host sees it, when that differs from the path the daemon sees. It does not reach a turn: runed does not read it, because virtiofsd is runed's own child in runed's own mount namespace, so a mount source is the path runed sees and is never translated (runed/mounts.go buildMounts). compose emits it to every daemon, and the one production reader is proxyd, which uses it as the directory for its audit files (proxyd/main.go → audit.LoadConfig).

Read bycore / core/config.go:215
Typepath
DefaultDATA_DIR
Example/srv/data/arizuko_demo

IDLE_TIMEOUT

The floor under the guest ceiling, in milliseconds. Nothing is kept around to recycle: a guest is powered off at the end of its turn. What this does is stop CONTAINER_TIMEOUT cutting in before the idle timer has given up — turnTTL raises the guest ceiling to this value plus a 30s grace whenever it sits below (runed/manager.go turnTTL, runed/runtime.go containerGracePeriod).

Read bycore / core/config.go:226 → runed
Typeint (milliseconds)
Default3600000 (60 min)
Example600000

MAX_CONCURRENT_CONTAINERS

How many guests may run at once before runed queues the next spawn. It is also the size of everything a guest draws from, so runed refuses to start when the numbers do not fit. This many guests at RUNED_GUEST_MEM must fit the host's MemTotal; RUNED_TAP_SUBNET must hold this many /30s; the CID window starting at RUNED_VSOCK_CID_BASE is this wide (runed/qemuhost.go qemuPreconditions, runed/vmrecord.go allocateCID). routd reads the same value as its own dispatch cap, so the two cannot drift.

Read bycore / core/config.go:227; routd / routd/cmd/routd/main.go:178
Typeint
Default5
Example10

MAX_TURN_RETRY

How many times routd runs a turn again when its guest stopped without any bot message: SIGKILL, out of memory, or the RUNED_RUN_TIMEOUT limit. routd does not advance the failed batch, so the next poll gives it to a new guest with a <system-note> that names the retry number and asks the agent to use fewer resources. A turn that already replied is never retried, also not in part. -1 turns retries off. 0 and an unset value both mean 3. The setting applies to the whole instance, with no override for a folder. See retries.

Read byroutd / routd/cmd/routd/main.go:214
Typeint
Default3
Example5

MEDIA_ENABLED

Enables inbound media download and forwarding to the agent. Adapters fall back to URL-only references when off.

Read bycore / core/config.go:258
Typebool ("true")
Defaultfalse
Exampletrue

MEDIA_MAX_FILE_BYTES

Per-attachment byte cap on inbound media; adapter ProxyFile streams up to this size before truncation.

Read bycore / core/config.go:259, teled / teled/main.go:100, discd / discd/main.go:89, emaid / emaid/main.go:81, slakd / slakd/main.go:107
Typeint (bytes)
Default20971520 (20 MiB)
Example52428800

ONBOARDING_ENABLED

Master switch for the onboarding flow. Affects both routd behaviour and whether compose generates the onbod service.

Read byroutd / routd/cmd/routd/main.go:203, onbod / onbod/main.go:60, compose / compose/compose.go:747
Typebool (core: "true"; onbod: "0" disables)
Defaultfalse
Exampletrue

ONBOARDING_PLATFORMS

CSV of platforms (e.g. telegram,discord) for which onboarding admissions are enabled.

Read byroutd / routd/cmd/routd/main.go:204
TypeCSV string
Default""
Exampletelegram,whatsapp

OBSERVE_WINDOW_MESSAGES

How many trailing #observe-mode messages routd folds into context when a trigger turn fires. Per-route override via routes.observe_window_messages.

Read byroutd / routd/cmd/routd/main.go:187
Typeint
Default10
Example25

OBSERVE_WINDOW_CHARS

Character cap on the #observe context window for a single trigger turn. Per-route override via routes.observe_window_chars.

Read byroutd / routd/cmd/routd/main.go:188
Typeint (chars)
Default4000
Example8000

ENGAGEMENT_TTL

How long a (chat, topic) stays “engaged” after the bot’s last reply. Engaged pairs fire a turn on every message from a person without needing a fresh @mention, and override #observe routing. Reactions, edits and CI events follow their route. Each bot reply pushes the deadline out again; user messages do not. Explicit 0 disables automatic opening and renewal of windows. Existing windows expire or end with disengage. See engagement.

Read byroutd / routd/cmd/routd/main.go:234
Typeduration (e.g. 10m, 30m)
Default30m
Example30m

SECRETS_KEY

AES-256-GCM key for the secrets table — secrets are encrypted at rest. Required: routd refuses to start without it, and there is no plaintext mode and no AUTH_SECRET fallback. The value is a comma-separated keyring: the first key seals new writes, the rest are decrypt-only, so a value sealed under an old key still reads. To rotate, put the new key first and keep the old one after it; re-write each secret so the new key seals it; remove the old key only after every row is re-written. A v2: value that no key opens is an error, never plaintext. arizuko create generates a random key into the new instance’s .env; the arizuko secret CLI uses the same keyring. Stored values carry a v2: prefix; a row without it is read back as stored, and arizuko secret seal-legacy seals such rows in place.

Read bycore / core/config.go:264 → routd, arizuko secret CLI
Typestring (comma-separated keyring; first = active seal key)
Defaultnone — routd exits if unset; arizuko create generates one
Example9d4f...new,2a17...retired

SEND_DISABLED_CHANNELS

CSV of channel names where outbound sends are dropped (read-only mode).

Read byroutd / routd/cmd/routd/main.go:157
TypeCSV string
Default""
Exampleslack,discord

SEND_DISABLED_GROUPS

CSV of group folders where outbound sends are dropped (read-only mode).

Read byroutd / routd/cmd/routd/main.go:254
TypeCSV string
Default""
Examplesolo/inbox,corp/eng

TELEGRAM_BOT_TOKEN

Bot token for the Telegram channel adapter.

Read byteled / teled/main.go:93 (MustEnv)
Typestring
Default"" (teled refuses to start)
Example12345:ABC-DEF1234ghIkl-zyx57W2v1u123ew11

TTS_BASE_URL

OpenAI-compatible /v1/audio/speech base URL the agent's send_voice path hits.

Read byroutd / routd/cmd/routd/main.go:259
TypeURL
Defaulthttp://ttsd:8880
Examplehttp://ttsd:8880

TTS_ENABLED

Enables TTS rendering of agent replies as voice messages.

Read byroutd / routd/cmd/routd/main.go:258
Typebool ("true")
Defaultfalse
Exampletrue

TTS_MODEL

Default model name passed in the TTS request body.

Read byroutd / routd/cmd/routd/main.go:261
Typestring
Defaultkokoro
Examplekokoro

TTS_TIMEOUT

Timeout for one request to the TTS backend, as a Go duration such as 15s. A bare number does not parse, and routd then uses the default.

Read byroutd / routd/cmd/routd/main.go:262
Typeduration, Go syntax
Default15s
Example30s

TTS_VOICE

Instance-default voice id; agent may override per-call via send_voice args or per-group via PERSONA.md frontmatter.

Read byroutd / routd/cmd/routd/main.go:260
Typestring
Defaultaf_bella
Exampleaf_sky

TZ

IANA timezone for log timestamps, cron evaluation in timed, and Config.Timezone consumers.

Read bycore / core/cron.go:24, runed / runed/render.go:103, compose / compose/compose.go:65
Typestring (IANA name)
DefaultUTC
ExampleEurope/Bratislava

VIDEO_TRANSCRIPTION_ENABLED

Enables Whisper transcription of inbound video.

Read bycore / core/config.go:261
Typebool ("true")
Defaultfalse
Exampletrue

VOICE_TRANSCRIPTION_ENABLED

Enables Whisper transcription of inbound voice messages.

Read bycore / core/config.go:260
Typebool ("true")
Defaultfalse
Exampletrue

WEB_HOST

Public hostname the instance serves; used to build chat-link, webhook, and onboarding URLs in agent prompts.

Read bycore / core/config.go:230
Typestring
Default""
Exampledemo.example.com

WHISPER_BASE_URL

Base URL of the Whisper-compatible transcription endpoint.

Read byroutd / routd/cmd/routd/main.go:224
TypeURL
Defaulthttp://localhost:8080
Examplehttp://whisper:9000

WHISPER_MODEL

Model name passed to Whisper.

Read bycore / core/config.go:262
Typestring
Defaultturbo
Examplelarge-v3

agent spawn — what runed forwards into the turn's guest

Model keys are env-profile keys. ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, OPENAI_API_KEY, and CODEX_API_KEY pick which model a turn talks to, so they are user-scoped credentials, not folder secrets — the store rejects them at folder scope. A user sets their own in a dedicated dashboard section at /dash/me/env, and that value overrides the operator default for their spawns. The host .env value below is the instance-wide default forwarded when a user has set nothing. See secrets. With the litellm package the forwarding stops: the gateway reads the credential from .env and the guest gets MODEL_GATEWAY_KEY instead.

ANTHROPIC_API_KEY

Instance default forwarded into every turn's guest so Claude Code can authenticate, unless the triggering user has set their own at /dash/me/env.

Read byruned / runed/secrets.go mergeSecrets
Typestring
Scopeuser (env-profile key; rejected at folder scope)
Defaultnot forwarded if unset
Examplesk-ant-...

CLAUDE_CODE_OAUTH_TOKEN

Alternative auth for Claude Code; forwarded into the guest with ANTHROPIC_API_KEY, under the same user-override rule. runed forwards every env-profile model key the same way: also ANTHROPIC_OAUTH_TOKEN, OPENAI_API_KEY and CODEX_API_KEY. When MODEL_GATEWAY_URL is set, runed withholds all of them, and the guest gets MODEL_GATEWAY_KEY.

Read byruned / runed/secrets.go mergeSecrets
Typestring
Scopeuser (env-profile key; rejected at folder scope)
Defaultnot forwarded if unset
Examplecc-oauth-...

LOG_LEVEL

Sets the stderr slog level (debug < info < warn < error) for every daemon, applied by obs.Setup at startup. In runed, debug/trace writes the guest's whole envelope (mounts, exit, stderr) beside the group's other logs after every turn, not only the summary a clean turn gets.

Read byevery daemon via obs.Setup; turn-log verbosity / runed/turnlog.go
Typestring (debug/info/warn/error/trace)
Defaultinfo
Exampledebug

chanreg

CHANNEL_REGISTER_ALLOW_PUBLIC

Dev override that lets channel adapters register with public IPs. By default chanreg rejects non-private adapter URLs as SSRF risk.

Read bychanreg / chanreg/chanreg.go:326
Typebool ("1")
Defaultunset = enforce private-only
Example1

arizuko CLI (cmd/arizuko)

ARIZUKO_DATA_DIR

Override the base directory used to construct instance data paths. When set, instance dir is $ARIZUKO_DATA_DIR/arizuko_<name>.

Read bycli / cmd/arizuko/main.go:220
Typepath
Default"" (falls back to PREFIX)
Example/var/lib/arizuko

PREFIX

Filesystem prefix for instance data dirs; final path is $PREFIX/data/arizuko_<name>.

Read bycli / cmd/arizuko/main.go:222
Typepath
Default/srv
Example/srv

arizuko run (compose generation)

Read by compose/compose.go at arizuko run time when it generates the docker-compose YAML. These are not consumed by any running daemon — only by the generator that decides which services to emit, which ports to expose, and which proxyd routes to gate.

DAV_PORT

Host port to expose for davd. Empty ⇒ davd not exposed at the docker level.

Read bycompose / compose/compose.go:1415
Typestring (port)
Default""
Example8096

COMPOSE_PROFILES

Comma-separated docker compose profiles that decide which optional daemons run. Generated, not hand-set: activeProfiles derives it from the instance's feature flags (WEB_PORT → web, WEBDAV_ENABLED → davd, ONBOARDING_ENABLED → onbod; timed is always on) and writes it into the compose-managed block of .env. Every optional service fragment carries a matching profiles: [<name>], so this list is the single gate docker reads.

Read bydocker compose, written by compose/compose.go:734
TypeCSV string
Defaulttimed (always on)
Exampledavd,onbod,timed,web

WEB_PORT

Host port the proxyd service is published on; the public entry point for the instance.

Read bycompose / compose/compose.go:738
Typestring (port)
Default8095
Example443

WEB_PORT_ALIASES

CSV of additional host ports also published for proxyd (e.g. expose 80 alongside 443).

Read bycompose / compose/compose.go:1385
TypeCSV string
Default""
Example80,8080

WEBDAV_ENABLED

Whether to emit the davd service and gate its /dav/ proxyd route.

Read bycompose / compose/compose.go:742, and as the route gate at compose/compose.go:603
Typebool ("true")
Defaulttrue
Examplefalse

routd

Apart from HITL_WAIT and the PROACTIVE_* set below, routd takes every value from core.LoadConfig above. Egress isolation is not a setting on routd or anywhere else: runed starts one egress proxy per turn and refuses to start if the binary is missing (RUNED_EGRESS_BIN).

The PROACTIVE_* vars tune proactive interjection — the agent speaking without being addressed. They are read once at startup by routd.LoadProactiveConfig, called from routd/cmd/routd/main.go. All of them do nothing unless PROACTIVE_ENABLED is on. Per-group opt-in is separate: a proactive: block in the group’s CLAUDE.md frontmatter, not an env var.

HITL_WAIT

How long a held tool call waits for a human verdict before it closes as expired and the agent hears NOT RUN. Keep it under RUNED_RUN_TIMEOUT: past that the guest is killed and the wait ends as a dead turn instead of an answer. See holding a call for a human.

Read byroutd / routd/pending_actions.go:437
Typeduration (Go syntax)
Default10m
Example15m

PROACTIVE_ENABLED

Master switch for proactive interjection. Unset or false and routd builds no scanner at all — the whole feature is absent, not idle. No shipped template sets it, so every instance is off until an operator opts in. Turning it on starts the scan; each group still has to declare mode: lurk before anything fires.

Read byroutd / routd/proactive.go:36
Typebool (1, true, yes, on)
Defaultunset — off
Exampletrue

PROACTIVE_SCAN_INTERVAL

Minimum gap between proactive sweeps. The sweep is driven by routd’s own loop, not a timer, so a long turn simply delays the next one — missed ticks are dropped, never replayed as a burst.

Read byroutd / routd/proactive.go:37
Typeduration (Go syntax, e.g. 30s, 2m)
Default30s
Example1m

PROACTIVE_SILENCE_MIN

How long a chat must have been quiet before an interjection is considered. Below this the conversation is still live and cutting in is rude. Measured from the last real inbound — bot replies and prior proactive messages do not reset the clock.

Read byroutd / routd/proactive.go:38
Typeduration
Default90s
Example5m

PROACTIVE_SILENCE_MAX

Upper edge of the same band. Past this the chat is dormant — nobody is there to read the message — so the scan skips it.

Read byroutd / routd/proactive.go:39
Typeduration
Default12h
Example4h

PROACTIVE_COOLDOWN

How long a chat is barred from a second interjection after one fires. Mandatory — state-triggered firing without a cooldown loops. Recorded per chat in routd’s chat_proactive table, written in the same transaction that queues the turn, so a crash mid-dispatch costs at most one missed turn and never a double-fire. There is no per-group override.

Read byroutd / routd/proactive.go:40
Typeduration
Default24h
Example48h

PROACTIVE_BOT_QUIET

How long after its own last message the bot stays out of a chat. Stops a proactive turn from landing on the heels of a reply the agent just made.

Read byroutd / routd/proactive.go:41
Typeduration
Default15m
Example30m

PROACTIVE_RECENT_ACTIVITY_MIN

How many inbound messages must have arrived in the last hour for the room to count as busy enough to be worth speaking into. Below the threshold the scan skips the chat.

Read byroutd / routd/proactive.go:42
Typeint (messages per hour)
Default3
Example5

runed — the turn substrate

A turn is one ephemeral KVM guest: qemu-system-x86_64, direct kernel boot, two virtio-fs shares, a vsock control channel, and one tap with its own rule set and its own egress proxy on the host side. Every value below is a setting in the generated env, never something runed derives from a runtime path. The kernel and the rootfs are release artifacts whose names carry a version and a config hash; a daemon that went looking for them would find whichever was newest.

runed resolves and checks all of them once, at start, in runed/qemuhost.go qemuPreconditions — and refuses to start when any is unmet, naming every one in a single message. Checking per turn instead would give you a fleet that reports healthy and answers nothing. There is no substrate selector: one substrate, one legal value, no setting.

RUNED_KERNEL_DIR

The make kernel artifact directory, holding bzImage and initrd.gz. The directory name carries the kernel version and a hash of the build's inputs, so it is configuration and never a guess. runed boots the guest from both in place and copies neither. Required: unset, or either file missing, and runed refuses to start.

Read byruned / runed/qemuhost.go qemuPreconditions
Typepath
Defaultnone — required
Example/srv/data/arizuko_demo/images/kernel-6.1.112-40e1508980d0

RUNED_ROOTFS_DIR

The unpacked release rootfs, exported once per release by make images, shared into the guest read-only. The directory name carries the arizuko-ant image id it was exported from, so a guest cannot boot last week's tree against this week's agent. Beside the tree sits .image-env.json — the agent image's own ENV set, which a directory cannot carry and which runed applies in the guest instead. A rootfs with no readable copy of that file is a refusal, not a guess: without the image's PATH the agent's runtime is not on it.

Read byruned / runed/qemuhost.go qemuPreconditions, readImageEnv
Typepath
Defaultnone — required
Example/srv/data/arizuko_demo/images/rootfs-a35045d89e3e

RUNED_QEMU

The VMM. runed runs -version against it at start, so a name that is on PATH but cannot execute is caught at deploy rather than on the first turn.

Read byruned / runed/qemuhost.go
Typebinary name or path
Defaultqemu-system-x86_64
Example/usr/bin/qemu-system-x86_64

RUNED_VIRTIOFSD

The daemon that exports the guest's two shares. It must be the Rust build. runed greps the binary's --help for --uid-map and --gid-map and refuses QEMU's C build, which advertises neither and needs real root. A root daemon writes host-root-owned files into the group folder on a guest's say-so, measured 2026-09-24. Alpine's virtiofsd package installs the Rust daemon at this default path; on Debian the same second path (/usr/lib/qemu/virtiofsd) holds the C one instead, which is why the check names the flags and not the file.

Read byruned / runed/qemuhost.go checkClamp
Typepath
Default/usr/libexec/virtiofsd
Example/usr/libexec/virtiofsd

RUNED_EGRESS_BIN

The forward proxy binary. runed starts one process per turn on the host side of that guest's tap and kills it with the guest — there is no proxy container and no shared proxy. See components/egress.

Read byruned / runed/vmnet.go startEgress
Typebinary name or path
Defaultegress
Example/usr/local/bin/egress

RUNED_FIREWALL_BIN

What writes the tap's rule set: one accept for the guest's own proxy port above drops for everything else. The rules are IPv4 only, which is why a tap carries one address family.

Read byruned / runed/vmnet.go
Typebinary name or path
Defaultiptables
Example/sbin/iptables

RUNED_IP_BIN

What creates and addresses the tap. It must be iproute2: BusyBox ships an ip applet with no tuntap subcommand, so a PATH lookup passes and ip tuntap add then fails once per turn while runed reports healthy. runed therefore runs ip tuntap show at start and refuses the applet by the capability, not the filename.

Read byruned / runed/qemuhost.go checkTuntap
Typebinary name or path
Defaultip
Example/sbin/ip

RUNED_GUEST_MEM

QEMU's -m for each guest. This times MAX_CONCURRENT_CONTAINERS is what the host must hold, so the pair is checked against MemTotal at start. Five guests at 4G on a 15 GB host is refused there, not discovered when the second turn touches its pages.

Read byruned / runed/qemuhost.go memoryFits
TypeQEMU size (bare number is MiB; M and G suffixes)
Default2G
Example4G

RUNED_GUEST_SMP

vCPUs per guest.

Read byruned / runed/qemuhost.go
Typeint
Default2
Example4

RUNED_VSOCK_CID_BASE

The first guest CID. runed allocates upward from here, MAX_CONCURRENT_CONTAINERS wide. The CID and vsock port space belongs to the host, not to an instance, so two instances on one box each need their own window. Leave both at the default and both ask for CID 3 and the same derived ports.

Read byruned / runed/vmrecord.go allocateCID
Typeint (3 is the lowest legal guest CID)
Default3
Example20

RUNED_VSOCK_PORT_BASE

Base of the two vsock ports each guest slot takes — the control channel and the MCP channel. Slot n gets base+2n and base+2n+1. Both are bound on the host before QEMU starts, so a squatter is an error at spawn instead of a guest that boots into nothing. Same host-wide space as the CID: one window per instance.

Read byruned / runed/vmrecord.go vsockPorts
Typeint
Default5555
Example5600

RUNED_TAP_SUBNET

The range each guest's link is carved out of: one /30 per slot, the guest on the low address and the host side on the high one. Two usable addresses and no neighbour — a guest has nobody to talk to on its own link, so there is no bridge and no DHCP. runed checks every slot the cap allows at start, not the first: a subnet that holds the first guest and not the last is a fleet that fails on its second concurrent turn.

Read byruned / runed/vmnet.go tapNet
TypeIPv4 CIDR
Default10.97.0.0/24
Example10.98.0.0/22

RUNED_PROXY_PORT

The port this turn's egress proxy listens on, on the host side of the tap. It is the one port the tap's rule set accepts; everything else the guest sends is dropped below the kernel it runs on.

Read byruned / runed/qemuhost.go, runed/vmnet.go
Typeint
Default3128
Example3128

RUNED_VM_DIR

Where each live guest's own state sits: one directory per turn holding the record that names every pid a restarted runed must reap, the QEMU and virtiofsd pid files, the vhost-user sockets, and the turn's staging tree. A directory left behind after a turn means the reap could not finish — it is reported by path for a person, because a recursive delete over a live bind would take the group's own files.

Read byruned / runed/qemuhost.go
Typepath
DefaultDATA_DIR/vms
Example/srv/data/arizuko_demo/vms

RUNED_BOOT_TIMEOUT

How long runed waits for the booted guest to connect back on its control port before giving up on the spawn.

Read byruned / runed/qemu.go
Typeduration (Go syntax, e.g. 30s)
Default30s
Example60s

RUNED_REAP_GRACE

How long each process gets to exit on its own before SIGKILL at teardown — QEMU first and alone, then the rule set and the tap, then the host processes that served the guest. runed reuses the same value as its short bound elsewhere in a turn: waiting for a virtiofsd socket to appear, and writing a steer into a running guest.

Read byruned / runed/vmrecord.go reapVM, runed/qemu.go
Typeduration (Go syntax)
Default5s
Example10s

RUNED_RUN_TIMEOUT

The run ceiling. runed kills the guest here; the effective deadline is the lower of this and the guest ceiling CONTAINER_TIMEOUT sets. routd reads the same var and sizes its dispatch deadline at this plus two minutes, so runed's kill always lands first. Equal deadlines let routd time out first, leave the turn marked running, and re-feed the same message into a second guest.

Read byruned / runed/cmd/runed/main.go:92; routd / routd/cmd/routd/main.go:110; adapters size their typing TTL under it (chanlib/typing.go)
Typeduration (Go syntax)
Default20m
Example30m

RUNED_SHUTDOWN_GRACE

How long a graceful stop waits for runs in progress after runed stops accepting POST /v1/runs. Compose generation writes this key into the env file of runed.

Read byruned / runed/cmd/runed/main.go:201
Typeduration (Go syntax)
DefaultRUNED_RUN_TIMEOUT
Example5m

proxyd

proxyd shares no secret with its backends. It proves itself to each one with an authd-minted ES256 bearer pinned to service:proxyd, verified by auth.ProxydTransit in auth/middleware.go; see the auth concept.

PROXYD_LISTEN

Port proxyd binds to. Plain numbers get a : prefix.

Read byproxyd / proxyd/main.go:44
Typestring (port or :port)
Default8080
Example8080

PUB_REDIRECT_URL

If set, proxyd redirects /pub/* requests to this base URL instead of serving them locally — lets a CDN front the static docs.

Read byproxyd / proxyd/main.go:51
TypeURL
Default"" (proxyd serves /pub/ itself)
Examplehttps://docs.arizuko.com

TRUSTED_PROXIES

CSV of CIDRs (or bare IPs) trusted to set X-Forwarded-For. Empty ⇒ no client trusted, XFF always replaced with the connection peer.

Read byproxyd / proxyd/main.go:53
TypeCSV string (CIDRs / IPs)
Default""
Example10.0.0.0/8,172.16.0.0/12

VITE_ADDR

Backend address proxyd forwards /pub/* (and chat widget) traffic to.

Read byproxyd / proxyd/main.go:50
TypeURL
Defaulthttp://vited:8080
Examplehttp://vited:8080

webd

ROUTER_URL

Base URL of routd; webd posts inbound web chat and webhook messages to $ROUTER_URL/v1/messages.

Read bywebd / webd/main.go — see also ROUTER_URL in adapters
TypeURL
Defaulthttp://routd:8080
Examplehttp://routd:8080

WEBD_LISTEN

Address webd binds to.

Read bywebd / webd/main.go:41
Typelisten addr (:port or host:port)
Default:8080
Example:8080

WEBD_URL

Public URL webd advertises to routd when registering as a channel.

Read bywebd / webd/main.go:42
TypeURL
Defaulthttp://webd:8080
Examplehttp://webd:8080

onbod

ONBOARD_POLL_INTERVAL

Cadence at which onbod scans for unprompted users / admission queue entries.

Read byonbod / onbod/main.go:273
TypeGo duration string (time.ParseDuration)
Default10s
Example30s

ONBOARDING_GREETING

Custom greeting string injected into onboarding prompts in place of the default copy.

Read byonbod / onbod/main.go:268
Typestring
Default""
ExampleWelcome to the workspace.

ONBOD_LISTEN_ADDR

Address onbod binds to.

Read byonbod / onbod/main.go:270
Typelisten addr
Default:8080
Example:8080

onbod also reads ONBOARDING_ENABLED ("0" → clean exit) and ROUTER_URL via core. It trusts X-User-* headers only behind the proxyd transit bearer (auth.ProxydTransit); on its mixed public/authed invite routes an unproven request is treated as anonymous rather than rejected.

dashd

DASH_PORT

Port dashd binds to. Plain numbers get a : prefix.

Read bydashd / dashd/main.go:170, compose / compose/compose.go:206
Typestring
Default:8080
Example:8080

SURROGATE_<NAME>_CLIENT_ID

OAuth app client id for the surrogate “Connect <name>” flow at /dash/me/connections — arizuko authenticating as the user against a third-party API, not a user signing in to arizuko. <NAME> is the provider name upper-cased (-→_); a pair exists for every registered provider, built-in or dropped in at <datadir>/surrogate/*.toml (see how-to: add an OAuth provider). SURROGATE_GITHUB_CLIENT_ID feeds the built-in GitHub connector; it is distinct from GITHUB_CLIENT_ID, the login provider. Unset hides that provider's connection option.

Read byroutd + dashd / auth/surrogate/engine.go (NewEngine)
Typestring
Default""
ExampleIv1.def456

SURROGATE_<NAME>_CLIENT_SECRET

OAuth app client secret paired with SURROGATE_<NAME>_CLIENT_ID. One pair per provider.

Read byroutd + dashd / auth/surrogate/engine.go (NewEngine)
Typestring
Default""
Exampleghs_yyyyy

timed

ROUTER_URL

routd's address. timed opens no database — it reads due tasks and records runs over routd's HTTP API, so scheduled_tasks has exactly one writer path.

Read bytimed / timed/main.go
TypeURL
Defaultnone — required, timed refuses to start without it
Examplehttp://routd:8080

timed also reads AUTHD_URL + AUTHD_SERVICE_KEY (exchanged at boot for the service:timed token routd's gate requires) and TZ (for cron evaluation).

ttsd

TTSD_ADDR

Address ttsd binds to.

Read byttsd / ttsd/main.go:35
Typelisten addr
Default:8880
Example:8880

TTS_BACKEND_URL

Upstream Kokoro/OpenAI-compatible TTS server ttsd proxies to.

Read byttsd / ttsd/main.go:36
TypeURL
Defaulthttp://kokoro:8880
Examplehttp://kokoro:8880

ttsd also reads LOG_LEVEL.

litellm (model gateway package)

A third-party image, so it has no generated env file: template/services/litellm/litellm.yml names the .env keys docker interpolates into it, and compose/fragments_test.go pins that set. Component page: components/litellm.

MODEL_GATEWAY_KEY

The gateway master key. The litellm package declares it as generated, and arizuko package add and package sync provision it in the instance .env. routd uses it to mint a separate virtual key for a turn; runed passes that virtual key to the guest. The fragment requires the master key with ${MODEL_GATEWAY_KEY:?}.

Read bylitellm via template/services/litellm/litellm.yml (LITELLM_MASTER_KEY); routd for minting turn keys
Typestring
Defaultnone — required once the package is installed
Example$(openssl rand -hex 24)

MODEL_GATEWAY_URL

Where runed points every spawn’s model client (ANTHROPIC_BASE_URL for claude). Unset means the container keeps the credential and talks to the vendor directly, which is the default.

Read byruned for the spawn env
TypeURL
Default"" = no gateway
Examplehttp://litellm:4000

litellm also reads ANTHROPIC_API_KEY (or, when that is empty, CLAUDE_CODE_OAUTH_TOKEN) as the credential it forwards upstream, TZ, and the three OTLP keys under observability.

Adapter common surface

Every channel adapter shares the same set of plumbing env vars:

CHANNEL_NAME

Name the adapter registers with routd; routing uses this verbatim. Has a per-daemon default.

Read byteled :55, discd :64, emaid :55, slakd :56
Typestring
Defaultmatches the platform (telegram, discord, slack, …)
Exampletelegram-prod

LISTEN_ADDR

Address the adapter binds for its outbound-send HTTP server (routd → adapter).

Read byteled :61 (:9001), discd :68 (:9002), whapd :156 (:9002), emaid :64 (:9003), slakd :61 (:8080)
Typelisten addr
Defaultvaries (see column above; :8080 when pinned by the service fragment)
Example:8080

LISTEN_URL

URL the adapter advertises to routd on registration so routd can reach it.

Read byteled :62, discd :69, whapd :157, emaid :65, slakd :62
TypeURL
Defaulthttp://<daemon>:<port>
Examplehttp://teled:9001

ROUTER_URL

Base URL of routd where the adapter posts inbound messages.

Read byteled :59 (MustEnv), discd :66 (MustEnv), whapd :154, emaid :62 (MustEnv), slakd :59 (MustEnv), webd :38, onbod :143
TypeURL
Defaultrequired in most adapters (MustEnv); webd/onbod default to http://routd:8080
Examplehttp://routd:8080

All adapters also read AUTHD_SERVICE_KEY and AUTHD_URL — they exchange that key for a service:<daemon> ES256 token and present it on every routd call. Most also read MEDIA_MAX_FILE_BYTES.

teled (Telegram)

Reads TELEGRAM_BOT_TOKEN (MustEnv), ASSISTANT_NAME, DATA_DIR, plus the adapter common surface. No teled-specific env vars beyond these.

discd (Discord)

DISCORD_BOT_TOKEN

Bot token. Required unless DISCORD_USER_TOKEN is set instead.

Read bydiscd / discd/main.go:71
Typestring
Default"" (refuses to start if both bot/user tokens empty)
ExampleMTAxNTM4...

DISCORD_USER_TOKEN

User-account token; selects user-mode operation, bypassing bot intents. Mutually exclusive with the bot token.

Read bydiscd / discd/main.go:72
Typestring
Default""
Examplemfa.xxxxx

whapd (WhatsApp)

WHATSAPP_AUTH_DIR

Directory where Baileys multi-file auth state (creds, keys) is persisted.

Read bywhapd / whapd/src/main.ts:49
Typepath
Default$DATA_DIR/store/whatsapp-auth or /srv/data/store/whatsapp-auth
Example/srv/data/arizuko_demo/store/whatsapp-auth

whapd also reads ASSISTANT_NAME, DATA_DIR, ROUTER_URL, LISTEN_ADDR, LISTEN_URL.

emaid (Email)

EMAIL_ACCOUNT

Email address / IMAP+SMTP login username.

Read byemaid / emaid/main.go:73 (MustEnv)
Typestring
Defaultrequired
Examplebot@example.com

EMAIL_IMAP_HOST

IMAP server hostname.

Read byemaid / emaid/main.go:71 (MustEnv)
Typestring
Defaultrequired
Exampleimap.gmail.com

EMAIL_IMAP_PORT

IMAP server port.

Read byemaid / emaid/main.go:76
Typestring (port)
Default993
Example993

EMAIL_PASSWORD

Login password / app password for IMAP+SMTP.

Read byemaid / emaid/main.go:74 (MustEnv)
Typestring
Defaultrequired
Examplexxxx xxxx xxxx xxxx

EMAIL_SMTP_HOST

SMTP server hostname.

Read byemaid / emaid/main.go:72 (MustEnv)
Typestring
Defaultrequired
Examplesmtp.gmail.com

EMAIL_SMTP_PORT

SMTP server port.

Read byemaid / emaid/main.go:77
Typestring (port)
Default587
Example587

EMAIL_STRICT_AUTH

Reject inbound mail outright when SPF/DKIM/DMARC fails (fail-closed). When unset (default), failed-auth messages still reach the agent but flagged untrusted in the envelope.

Read byemaid / emaid/auth.go:65
Typebool (truthy strings: 1, true, yes, on)
Defaultfalse
Exampletrue

EMAIL_TRUSTED_AUTHSERV

Hostname of the upstream MTA whose Authentication-Results header emaid will trust (e.g. mx.google.com for Gmail). When unset, emaid classifies every inbound mail as untrusted. Available since v0.40.5.

Read byemaid / emaid/auth.go:64
Typestring (hostname, lowercased)
Defaultunset
Examplemx.google.com

EMAIL_TRUSTED_DOMAINS

Comma-separated allowlist of sender domains that count as trusted (matched against the From-address domain after DMARC alignment). Empty entries ignored. Both this and EMAIL_TRUSTED_AUTHSERV must agree for a message to be trusted. Added v0.40.5.

Read byemaid / emaid/auth.go:69
Typecomma-separated list
Defaultunset (no domain allowlist)
Exampleexample.com, partner.org

slakd (Slack)

SLACK_BOT_TOKEN

Slack xoxb- bot token used by the Web API and Events API client.

Read byslakd / slakd/main.go:100 (MustEnv)
Typestring
Defaultrequired
Examplexoxb-...

SLACK_SIGNING_SECRET

Shared secret Slack uses to sign incoming event webhooks; slakd verifies X-Slack-Signature against it.

Read byslakd / slakd/main.go:101 (MustEnv)
Typestring
Defaultrequired
Example8f742231...

SLAKD_USERS_CACHE_TTL

TTL (seconds) of the Slack user-info cache; lower values cost more API quota.

Read byslakd / slakd/main.go:108
Typeint (seconds)
Default900 (15 min)
Example3600

split daemons (routd / runed / authd)

Three daemons share the work: routd routes messages and runs the message loop, runed starts the turn guests, and authd handles auth and identity. These variables connect them.

AUTHD_URL

In-network base URL of authd. routd, runed, webd, and proxyd reach it for service-token exchange and JWKS. dashd uses it for both of those and as the backend for its /dash/authd/ page — the signing-key and session tables it reads over HTTP rather than by opening auth.db.

Read bycompose / compose/compose.go:99 → routd, runed, webd, proxyd, dashd
TypeURL
Defaulthttp://authd:8080
Examplehttp://authd:8080

AUTHD_SERVICE_KEY

Per-daemon shared secret a service daemon presents to authd to authenticate. Provisioned once per daemon and persisted to env/<daemon>.env; authd recognises them via AUTHD_SERVICE_KEYS.

Read bycompose / compose/compose.go:38 → routd, runed
Typestring (secret)
Defaultauto-provisioned at compose time
Example<random hex>

AUTHD_SERVICE_KEYS

The principal=secret pairs authd accepts, one per service daemon. Rotating a key here invalidates that daemon's in-flight service tokens.

Read bycompose / compose/compose.go:104 → authd
Typestring (comma-separated principal=secret)
Defaultauto-built from the provisioned service keys
Exampleservice:routd=...,service:runed=...

RUNED_URL

In-network base URL of runed (the container-runner plane). routd dispatches runs to it.

Read byroutd / routd/cmd/routd/main.go:43
TypeURL
Defaulthttp://runed:8080
Examplehttp://runed:8080

ROUTD_SERVICE_TOKEN

A static service token that routd sends to runed only when the token exchange at AUTHD_URL with AUTHD_SERVICE_KEY fails.

Read byroutd / routd/cmd/routd/main.go:148
Typestring (token)
Defaultempty
Example<jwt>

the store server

Every owner's tables live on one DynamoDB-protocol server per instance — ExtendDB over PostgreSQL, shipped as the extenddb package. Each daemon reads the five keys below and signs as itself, so the server's policy can bound what each one reaches. A daemon without DYNAMODB_URL refuses to start: there is no file to fall back to. Bring-up is in INSTALL.md § The store server: one extenddb init run prints the admin password, and extenddb manage mints one access key per daemon.

The key is minted by hand, so it can be missing. A daemon that has DYNAMODB_URL and no key refuses to dial (dynstore: endpoint ... takes an access key and a secret) and exits at boot. Because docker compose up runs under systemd, that one exit restart-loops the whole instance (BUGS L406). arizuko generate logs store enrolment is partial at ERROR, names those daemons and then continues (compose/compose.go warnPartialEnrolment, over store.StoreReach). Enabling a package that adds a daemon, gitd for one, without minting its key puts the whole instance in a restart loop.

DYNAMODB_URL

The endpoint every daemon opens its owner’s tables on. Required: unset, each daemon refuses to start with names no endpoint.

Read byevery daemon / objstore/dynstore/env.go
TypeURL (TLS; the server refuses to start without it)
Defaultnone — required; the extenddb package manifest sets https://extenddb:18443
Examplehttps://extenddb:18443

STORE_CA

The endpoint's certificate, base64-encoded, because a daemon has its environment and no file of its own. extenddb init generates it; a value that is not base64 is refused at boot rather than surfacing later as a handshake failure against a name nobody typed.

Read byevery daemon / objstore/dynstore/env.go
Typestring (base64 PEM)
Default"" = the system roots
ExampleLS0tLS1CRUdJTiBDRVJUSUZJ...

AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY

One key pair per daemon, written into env/<daemon>.env and carried forward by every later arizuko generate. The pair is what the server's policy binds: routd reaches routd.*, dashd reaches routd's and onbod's, an adapter reaches its own prefix (store.StoreReach is the full table). A shared pair would make that boundary meaningless.

Read byevery daemon / objstore/dynstore/env.go, carried by compose/compose.go
Typestring (secret)
Defaultnone — an endpoint with no credential is refused at boot
Example<minted by extenddb manage>

AWS_REGION

The region the request is signed for. Any value works against a self-hosted server as long as every daemon uses the same one; it matters only if the endpoint is Amazon's own.

Read byevery daemon / objstore/dynstore/env.go
Typestring
Defaultus-east-1
Exampleus-east-1

STORE_DB_PASSWORD / STORE_APP_PASSWORD

The first password belongs to the instance's PostgreSQL role. The second belongs to the role used by the store server. The ExtendDB package declares both in its manifest.

Read bythe extenddb compose fragment / template/services/extenddb/extenddb.yml
Typestring (secret)
Defaultgenerated by arizuko package add or package sync when the package is installed
Example<random hex>

observability — OTLP export

slog to journald is always on. Setting the OTLP endpoint also fans every slog event out to an OpenTelemetry collector, through the library obs/. Unset = zero overhead. The litellm gateway reads the same three keys (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_HEADERS) and, when the endpoint is set, sends one span per model request to <endpoint>/v1/traces with token counts and cost — message content excluded.

OTEL_EXPORTER_OTLP_ENDPOINT

OTLP/HTTP collector endpoint. When set, every daemon's obs.Setup sends slog events to stderr and the collector; records carrying turn_id get a deterministic TraceID so one turn's events group. When empty, only the stderr JSON handler runs.

Read byobs / obs/obs.go:36 (every daemon's main)
TypeURL
Default"" = OTLP disabled
Examplehttp://collector:4318

Some variables live elsewhere