English · 中文
This document is the operator's guide to the mega2 configuration system: load order, secret management, hot reload, a per-section tour, startup validation, and config validate. The fully commented per-key sample is authoritative in config/config.toml and is not duplicated here. Repository behavior is covered by the User Guide; deployment steps are in the Deployment Guide.
The config module lives in src/config/ (loader / model / source / expand / secret / validate / reload). Global flags are --config <path> and --profile <name> (equivalent environment variables MEGA_CONFIG / MEGA_PROFILE).
The base config file is the first hit in this order (src/config/loader.rs):
--config <path>(CLI; source namecli)MEGA_CONFIGenvironment variable (env)./config/config.toml(current working directory; only if it exists,cwd)$MEGA_BASE_DIR/etc/config.toml(global)- None of the above → a default config is generated and written to
$MEGA_BASE_DIR/etc/config.toml(default_generated)
Read-only / existing-config commands (e.g. config validate, service init) use load_readonly / load_existing: a file named via rules 1–2 that does not exist is an immediate error, and they never generate a default config — no side effects on the very system they were asked to observe.
Profiles: --profile prod (or MEGA_PROFILE=prod) selects the sibling file config.prod.toml next to the base config (derived from the base file name: <stem>.<profile>.<ext>). Profile names allow only ASCII letters / digits / - / _; a missing profile file is an error. CLI --profile wins over MEGA_PROFILE. Merge order: base file → profile file → environment variables (the profile is merged before env; env always wins last).
Environment overrides: the pattern is MEGA_<SECTION>__<KEY>; the double underscore is the hierarchy separator (src/config/source.rs). Example:
MEGA_DATABASE__DB_URL='postgres://mono-pg:5432/mono' \
MEGA_OAUTH__ALLOWED_CORS_ORIGINS='https://app.example.com,https://app2.example.com' \
mega2 --config /etc/mega2/config.toml service httpStrict mode: unknown and removed fields are rejected at parse time (reject_unknown_fields), never silently ignored — a leftover [mail] section or [monorepo].merge_writer key fails startup. config validate reports ignored compatibility fields as warnings (without printing secret values); --deny-warnings escalates them to failure in automation.
Credentials are never committed in config.toml. Two injection mechanisms:
- SecretRef:
vault://secret/<name>#<field>, resolved through the embedded Vault (libvaultcrate +src/contract/vault/).SecretRef'sDebug/Displayare always the redactedvault://secret/***#***; error text contains neither the real path nor the value. - File mounts:
${file:/run/secrets/xxx}expands to the file contents at load time (src/config/expand.rs). A value containing${file:...}must consist only of placeholders and literal text — it cannot be mixed with${var}; an unterminated${file:is rejected as well.
Only these config secrets can be stored in the mega2 Vault through config secret (see src/commands/config.rs): redis.url, notification.webhook.token, object_storage.s3.access_key_id, object_storage.s3.secret_access_key, and storage_events.targets.<id>.secret_ref. Database credentials are deliberately excluded because Vault needs them during bootstrap; keep them in deployment or environment secrets. Each field uses a fixed namespace such as config/prod/redis/url. config validate checks the SecretRef format but does not resolve it.
# Print the canonical URI (does not touch the Vault)
mega2 --config /etc/mega2/config.toml config secret ref \
--vault-path config/prod/redis/url --field value
# Store / rotate (the value is read from stdin only; --value-stdin is required)
printf '%s' "$REDIS_URL" | mega2 --config /etc/mega2/config.toml \
config secret set redis.url \
--vault-path config/prod/redis/url --field value --value-stdin
mega2 --config /etc/mega2/config.toml config secret rotate redis.url \
--vault-path config/prod/redis/url --field value --value-stdin < /run/secrets/new-redis-url
# Verify resolvability (does not print the value)
mega2 --config /etc/mega2/config.toml config secret check redis.url \
--vault-path config/prod/redis/url --field valueAfter rotate, restart services that consume the secret; new resolutions and config validate --resolve-secrets use the rotated value immediately.
Vault operations (config vault *, all load via VaultBootstrap and never start a service): reset (rebuild; destructive, requires --force; the previous core key is backed up automatically), rekey (re-splits the unseal shares, requires --force; note it only re-splits the current KEK — previously exported share sets still unseal the vault, and invalidating them requires a full KEK rotation), backup <destination> (exports the core key plus .meta.json), restore <source> (overwrites core_key.json, requires --force, verifies the vault unlocks after restore). --key-path defaults to core_key.json in the standard vault data directory.
[vault.audit]: secret-access auditing, enabled by default. sink = "tracing" (default; the vault_audit target, infallible) or "file" (append-only JSONL, fsync'd per record, requires file_path); with fail_closed = true, a failed audit write fails the secret operation itself (default false, fail-open). Audit records contain only the operation, logical name, outcome, and caller — never the secret value.
While a service runs, ConfigReloadWatcher polls the base config and profile file mtimes/sizes every 5 seconds (CONFIG_RELOAD_POLL_INTERVAL in src/commands/service/mod.rs). A candidate config first passes full Config::validate; an invalid one is rejected and the current snapshot is kept. A valid candidate is split per field (src/config/reload.rs):
Applied live (applied_fields; if a subscriber's apply fails, everything rolls back and the new snapshot is not published):
log.level/log.print_std/log.with_ansiartifacts_gc.interval_secs/grace_secs/batch_limit;artifacts_gc.enableapplies live only true → false — enabling from false requires a restartbuck.cleanup_interval/completed_retention_days;buck.enable_session_cleanupapplies live only true → false — enabling from false requires a restartnotification.enabled(the notification section snapshot is swapped live; an enabled flip counts as applied)views.worker_interval_secs/batch_size/max_append_walk/sync_catch_up_commits/max_filters/max_concurrent_cold_starts/register_rate_per_token
All other fields (database.*, redis.url, base_dir, monorepo.*, git.*, pack.*, lfs.*, blame.*, object_storage.*, oauth.*, storage_events.*, github_sync.*, views.enabled, views.allow_anonymous_register, buck upload limits, vault.audit, cedar, etc.) are only recorded in restart_required_fields — visible in the logs, snapshot unchanged — and take effect on process restart. Hot reload never writes new secret values back into the snapshot, and reports never contain secret values.
One line of purpose and the key entries per section; full comments and defaults are authoritative in config/config.toml. Everything is restart-required except the whitelist in §3.
base_dir(top level): data root (logs, local objects, LFS, caches), overridable viaMEGA_BASE_DIR; the${base_dir}placeholder expands inside path fields in the sample.[log]: tracing logs.level(trace..error),print_std(off in production),with_ansi(stdout only). All hot-reloadable.[database]: PostgreSQL only (db_type = "postgres").db_url, poolmax_connection/min_connection,acquire_timeout/connect_timeout,sqlx_logging. Inject credentials viaMEGA_DATABASE__DB_URL, not via Vault SecretRef.[monorepo]:import_dir(default/third-party, the ImportRepo multi-branch exception),admin,root_dirs(directory initialization),object_format(sha1by default;sha256/blake3require Libra),push_policy(trunkby default, which requires an explicitgit.push_auth; the review sample isconfig-review.toml), andmax_push_commits(the trunk push-chain limit). User-visible path and push behavior is in the User Guide; the full key list and defaults are inconfig.toml.- Path validation:
config validate, service startup, and hot-reload candidates use the same checks and report the invalid field. Eachroot_dirsentry must be a unique single path component: no/,\, or NUL; not empty,.or..; no leading or trailing spaces; and not a reserved root entry (.cedar,.mega_cedar.json,.buckroot,.buckconfig, or.git, case-insensitive)..viewand.filteralso cannot beroot_dirsentries or the firstimport_dircomponent, and match exact case.import_dirmust be a canonical absolute non-root path with no trailing slash,//,.or..segments, NUL, or\; its first component must appear inroot_dirs. Paths underimport_diralways use ImportRepo semantics, even whenroot_dirscontains that name.root_dirscreates directories only during initial setup; changing it later requires a restart and does not add or remove directories in an initialized repository. See the initialization manual. - View reserved names:
.viewand.filterare view URL names reserved by complete, exact-case first-component matching;.viewerand.Vieware unaffected, and this rule is independent of[views].enabled. Provisioning and product writes returnMONO_PATH_INVALIDfor paths beginning with either name, and trunk Git pushes that create those paths are also rejected; review pushes do not use this creation classification, and existingmega_refsrows for those paths are caught by the startup check below. Once the view URL entry point ships, those Git URLs are answered by it and do not enter Monorepo dispatch. Withenabled = true, HTTP and SSH check root-tree entries and everymega_refsrow before binding (regardless of ref name, CL status, or a leading slash in the path); a conflict or unreadable root tree rejects startup, whileenabled = falseskips the check. Conflict errors list eachroot tree entry "<name>"ormega_refs path "<path>"with Rust{:?}quoting, include[views] enabled = false,docs/configuration.md, andView reserved names, and contain no value read from configuration. No supported removal path exists for an existing root-tree ormega_refsconflict: keepenabled = false; this plan provides no cleanup SQL. After upgrade, aroot_dirsvalue containing.viewor.filteris rejected and must be renamed or removed; changing configuration does not alter an initialized root tree.
- Path validation:
[monorepo.rename]: move/rename detection for diff classification:similarity_threshold(0-100),rename_limit(0 = unlimited).[pack]: receive-pack decode resources.pack_decode_mem_size/pack_decode_disk_size(K/M/G, KiB/MiB, and percentages supported),pack_decode_cache_path,clean_cache_after_decode,channel_message_size,save_entry_concurrency.[lfs]:[lfs.ssh].http_url(href base for SSH transfer; LFS payloads still move over HTTP) and[lfs.local].lfs_file_path. In trunk mode, LFS authorization followsgit.push_auth; see the Deployment Guide.[object_storage]: global backend for Git blobs, LFS, and artifacts.storage_typecan belocal,s3,s3compatible, orgcs; provider-specific settings live under[object_storage.s3],[object_storage.gcs], and[object_storage.local].config validaterejectsvault://values in S3 access keys; inject bootstrap credentials through the environment, a profile, or deployment secrets. Optionalobject_storage.s3.public_endpoint_url(s3compatibleonly) sets the host that presigned URLs are signed for, so clients that cannot reachendpoint_url(for example a compose-internal address) can still follow artifact and LFS presigned URLs; it must be a bare http(s) origin (no credentials, query, fragment or path) and changing it requires a restart.[oauth]: website Better Auth session validation.website_api_base_url,session_cookie_names(omitted = Better Auth default cookie names),allowed_cors_origins(empty = built-in development defaults; env override is a comma-separated list). See the Architecture Guide for the service boundary.[blame]: large-file thresholds and traversal resources:max_lines_threshold,max_size_threshold,default_chunk_size,max_commits_in_memory,enable_caching.[redis]:url. Cache / distributed locks / snowflake worker leases, etc.; may be Vault-backed via SecretRef.[buck]: Buck upload API: session and file limits (session_timeout/max_file_size/max_files/max_concurrent_uploads), server-side rate limiting (upload_concurrency_limit/large_file_concurrency_limit/large_file_threshold), session cleanup task (enable_session_cleanup/cleanup_interval/completed_retention_days, partially hot-reloadable — see §3).[artifacts_gc]: GC of unreferenced artifact blobs (enable/interval_secs/grace_secs/batch_limit), default off; tuning hot-reloads, enabling from false requires a restart.[views]: experimental deterministic history projections, default off.enabled = truerequiresmonorepo.push_policy = "trunk"andmonorepo.object_format = "sha1"; seeconfig/config.tomlfor fields and defaults.[notification]:enabledglobal kill switch (hot-reloadable); optional[notification.webhook]outbound channel (urlis non-secret,token_refis a SecretRef). The sample config documents every supported field.[vault.audit]: see §2.[cedar]:enforcement = "off" | "shadow" | "enforce"(ADR-UN-01; default off). Trunk mode requiresoff. The Architecture Guide summarizes authorization behavior.[git]: authentication for the Git protocol and product API writes.push_auth(omitted = OAuth/UserStorage chain, review only;"token"/"none"require trunk),[[git.push_tokens]](name / token / paths, component-boundary prefix authorization; tokens via${file:...}or SecretRef),ssh_receive_pack(storage-only must set it tofalseexplicitly),anonymous_access. See the Deployment Guide for deployment practices.[oci]:enabledswitch; the/v2surface is double-gated (storage-only + enabled), otherwise a bare 404. See the User Guide for product surfaces.[agent_capture]: optional Agent Session Capture API for sessions, events, checkpoints, transcripts, and file-operation records; it is independent of Git push. Configureenabled,tenant_id/deployment_id, and[[agent_capture.ingest_tokens]](name / token / paths). Available only in storage-only mode; enabling requires explicitgit.push_authand at least one separate ingest token. See the Agent Capture configuration and API reference (Chinese).[storage_events]: outbound events for committed writes (default off).enabled(requires storage-only),installation_id(required, never auto-generated), numeric bounds for concurrency / timeouts / graceful shutdown,[[storage_events.targets]](id / https url /secret_refHMAC / events / filter lists). The commented sample andconfig validatedescribe supported values and validation.[github_sync]: GitHub outbound sync (default off).enabled,ssh_host/ssh_user/ssh_host_key,ssh_key_ref(a SecretRef string — never put the private key itself here), four timeouts,[[github_sync.bindings]](id / path / remote). Seeconfig.tomlfor the full schema.
Config::validate and AppContext::new reject invalid configuration at startup. The Deployment Guide covers trunk / storage-only prerequisites, including cedar.enforcement = "off", no open CLs, no non-terminal push_queue rows, an explicit push_auth compatible with the selected mode, and ssh_receive_pack = false. Other examples include unknown or removed fields, invalid cedar.enforcement values, the reserved anonymous principal in monorepo.admin, optional services enabled outside storage-only mode, out-of-range storage_events values, non-canonical filters, and SecretRef namespace mismatches. Secret values are never included in error messages. With [views].enabled = true, services also check the reserved names .view and .filter before binding; see “View reserved names” in §4.
Validate after editing, before starting a service:
mega2 --config /etc/mega2/config.toml config validate
# With a profile and source diagnostics
mega2 --config /etc/mega2/config.toml --profile prod \
config validate --show-sources
# CI: warnings fail; secret refs are actually resolved (never printed)
mega2 --config /etc/mega2/config.toml \
config validate --deny-warnings --resolve-secrets--resolve-secrets: bootstraps the Vault and resolves the config's SecretRefs, verifying reachability and field presence; resolved values are neither printed nor written back.--deny-warnings: exits non-zero when source diagnostics (ignored fields, env mapping problems, etc.) contain warnings.--show-sources: prints field provenance and override relationships across the base / profile / env layers;--format jsonemits the frozen JSON schema (including the winning source ofcedar.enforcement, ADR-UN-01).config init [--output <path>] [--force]: writes the commented template config and prints follow-upconfig secret setandconfig validate --resolve-secretsguidance.
config validate uses read-only loading (§1): it never generates a default config, and a missing named file is an error. Configuration conventions for development and test environments are in the Contributing Guide. Repo-level conventions: AGENTS.md.
- This documentation set:
quick-start.md·user-guide.md·deployment.md·architecture.md·contributing.md