[server]
# "all" serves auth and API validation; "auth" only issues tokens; "api" only
# validates them against the shared database.
role = "all"
host = "127.0.0.1"
port = 3000
public_url = "http://127.0.0.1:3000"
# Keep 0 unless every request passes through exactly this many trusted proxies.
# A wrong nonzero value lets clients spoof rate-limit and access telemetry IPs.
trusted_proxy_hops = 0

[plugins]
# Immutable installed package snapshots live here. Keep this persistent so a
# bundled plugin continues to run until an explicit upgrade replaces it.
directory = "plugins"
# Dedicated capability-only listener reached by plugin containers through the
# happy2.host.internal container-host mapping.
# The cross-platform host-gateway path requires a non-loopback bind. Firewall
# this fixed port from untrusted LANs; it is not a public product API.
host_api_host = "0.0.0.0"
host_api_port = 3001

# Optional public agent preview routing. Point wildcard DNS and TLS for
# *.preview.example.com at the same public Happy listener.
# [port_sharing]
# public_domain = "preview.example.com"
# Optional; defaults to https://preview.example.com.
# public_url = "http://preview.example.com:8080"

[database]
# A local file works for one host. Use a shared libSQL endpoint when auth and
# API are independently deployed, so every instance sees the same sessions.
url = "file:happy2.db"
# auth_token_env = "HAPPY2_DATABASE_AUTH_TOKEN"

[agents]
# The product server owns Rig sessions. Clients never access this socket or token.
# By default, Server-started Rig keeps its configuration, durable state, socket,
# and token under .happy2/rig instead of reusing the user's global Rig daemon.
# "managed" makes Happy own, version-check, start, and stop this private daemon.
# Separately supervised server deployments may choose "attached"; desktop never does.
daemon_mode = "managed"
# Set the standard RIG_HOME environment variable to an absolute path to move
# that private Rig home.
enabled = true
# The bundled @slopus/rig executable is used unless command is explicitly set.
# Root for server-owned agent/user and agent/chat sandboxes. Clients cannot override it.
default_cwd = "/srv/happy2/workspace"

[files]
# Keep this outside the published package/image in production.
provider = "local"
directory = "files"
signed_url_expiry_seconds = 300
# Generic chat attachments are streamed to disk. Keep this aligned with proxy limits.
max_upload_bytes = 536870912
# Resumable uploads persist their offset across reconnects and restarts.
resumable_chunk_bytes = 8388608
# Zero disables a quota. Values include generated previews and thumbnails.
per_user_quota_bytes = 5368709120
server_quota_bytes = 107374182400
incomplete_upload_expiry_seconds = 86400
quarantine_retention_seconds = 2592000
# Optional scanner executable. It runs without a shell; {path} is replaced in
# each argument, or appended when no argument contains the placeholder.
# malware_scanner_command = "/usr/local/bin/clamscan"
# malware_scanner_arguments = ["--no-summary", "{path}"]
malware_scan_timeout_seconds = 120
# "deny" quarantines files when the scanner is unavailable; "allow" fails open.
malware_scan_failure_mode = "deny"

[security]
integration_secret_env = "HAPPY2_INTEGRATION_SECRET"

[security.rate_limit]
enabled = true
reads_per_minute = 1200
writes_per_minute = 300
auth_per_minute = 30

[security.idempotency]
enabled = true
lease_seconds = 30
retention_seconds = 86400

[jwt]
issuer = "https://happy2.example.com"
audience = "happy2-desktop"
key_id = "2026-01"
# Set paths to use mounted PEM key files. Otherwise the auth/all server
# generates an RS256 key pair on first start and persists it in its .env file.
# The API-only role needs the matching public key in HAPPY2_JWT_PUBLIC_KEY_B64
# or public_key_path.
# private_key_path = "/run/secrets/happy2-jwt-private.pem"
# public_key_path = "/run/secrets/happy2-jwt-public.pem"
expiry_days = 30

[auth.password]
enabled = true

# Desktop-owned account-free access. This is mutually exclusive with every
# account authentication method and is accepted only for an all-in-one server
# whose bind and public URL are loopback, trusted_proxy_hops is zero, and dev
# tokens are disabled. The named environment variable must contain a random
# 32-4096 character Bearer capability. Do not enable this for a hosted server.
[auth.local]
enabled = false
token_env = "HAPPY2_LOCAL_ACCESS_TOKEN"

# Allows an authenticated Settings session to create an opaque bearer token for
# local web development. Every token inherits its backing session's expiry and
# revocation; leave disabled on servers that do not need remote UI development.
[auth.dev_tokens]
enabled = false

[auth.magic_link]
enabled = false
from = "Happy (2) <noreply@happy2.example.com>"
redirect_url = "happy2://auth/magic-link"
# SMTP settings are only read from EMAIL_SMTP_HOST, EMAIL_SMTP_PORT,
# EMAIL_SMTP_USER, EMAIL_SMTP_PASSWORD, and EMAIL_FROM.

[auth.oidc.google]
enabled = false
discovery_url = "https://accounts.google.com/.well-known/openid-configuration"
client_id = "replace-me"
# Name of the environment variable holding this provider's client secret.
client_secret_env = "HAPPY2_OIDC_GOOGLE_CLIENT_SECRET"
scopes = ["openid", "email", "profile"]
redirect_path = "/v0/auth/oidc/google/callback"

# Cloudflare Access authenticates the request at the proxy and forwards a signed
# Cf-Access-Jwt-Assertion header. See README for the required Tunnel/origin setup.
[auth.cloudflare_access]
enabled = false
team_domain = "https://replace-me.cloudflareaccess.com"
# Copy the Application Audience (AUD) Tag from Cloudflare Zero Trust.
audience = "replace-me"
