# ci-orchestrator config example. # # Copy to $CI_ROOT/config.toml or set $CI_CONFIG to the file path. # Environment variables (CI_ROOT, CI_TEMPLATES, CI_BUILD_VMS, CI_ARTIFACTS, # CI_KEYS, CI_VMRUN_PATH, CI_SSH_KEY_PATH, CI_GUEST_CRED_TARGET) override # anything set here. # # IMPORTANT (TOML rule): top-level keys must appear BEFORE any [section] header. # Keys placed after a [section] belong to that section, not to the root table. # Top-level orchestrator settings. vmrun_path = "C:/Program Files (x86)/VMware/VMware Workstation/vmrun.exe" ssh_key_path = "F:/CI/keys/ci_linux" guest_cred_target = "BuildVMGuest" # --------------------------------------------------------------- Windows host [paths] root = "F:/CI" templates = "F:/CI/Templates" build_vms = "F:/CI/BuildVMs" artifacts = "F:/CI/Artifacts" keys = "F:/CI/keys" # Phase B (Linux host) — uncomment after migration. # [paths] # root = "/var/lib/ci" # templates = "/var/lib/ci/templates" # build_vms = "/var/lib/ci/build-vms" # artifacts = "/var/lib/ci/artifacts" # keys = "/etc/ci/keys" # Phase C hook: backend selector. Only "workstation" is implemented in Phase A. [backend] type = "workstation" # Optional: pre-assign static IPs to build VMs via VMX guestinfo injection. # Eliminates VMware-Tools IP-detect polling (20–180 s variance) — the host # injects guestinfo.ip-assignment into each cloned VMX before vmrun start, # and the guest boot script applies the static IP immediately. # # Prerequisite: install the guest-side startup script from # template/guest-setup/linux/ci-report-ip.sh (Linux guests) # template/guest-setup/windows/ci-static-ip.ps1 (Windows guests) # and re-take the BaseClean / BaseClean-Linux snapshot. # # Pool size should equal runner capacity (default 4). # Addresses must be outside the VMnet8 DHCP range (check # /etc/vmware/vmnet8/dhcpd/dhcpd.conf — typically .128–.254). # # [ip_pool] # addresses = ["192.168.79.201", "192.168.79.202", # "192.168.79.203", "192.168.79.204"] # netmask = "255.255.255.0" # gateway = "192.168.79.2" # VMnet8 gateway; leave empty to skip default route # lease_file = "/var/lib/ci/ip-pool.json" # ------------------------------------------------- cargo dependency cache # Compiled Rust dependency artifacts, reused between jobs. Engages only for # Linux guests whose build workdir contains a Cargo.lock, so non-Rust jobs # need no flag. Workspace crates are stripped before an entry is stored and # the job refuses to publish if any survive, so a restored entry cannot carry # the code under test. # # One immutable file per key under `root`; a key is # sha256(Cargo.lock) + rustc version + host triple. Deleting entries by hand # is always safe -- each one costs exactly one cold build to regenerate. # # `root` defaults to /cache/cargo and follows CI_ROOT unless set # here explicitly. Measured on LinuxRust2404: an entry is ~645 MB and a warm # job pays ~8s to skip ~120s of dependency compilation. [cargo_cache] enabled = true # root = "/var/lib/ci/cache/cargo" retention_days = 30 # --------------------------------------------------------------------- web UI # Optional management web UI (`ci web serve`). Disabled unless `enabled = true`. # Server-rendered dashboard for VM/host management, auth'd via Gitea OIDC. # # SECURITY: # * `bind_host` defaults to 127.0.0.1 (localhost only). Only set it to a # LAN IP explicitly and deliberately -- never "0.0.0.0" (that exposes the # dashboard, which can kill VMs and purge caches, to every network the # host is reachable from). # * `session_secret_cred` and `[web.oidc] client_secret_cred` below are # KEYRING KEY NAMES, not the secrets themselves. The actual secret # values are never written to this file -- set them with: # ci creds set WebSessionSecret # ci creds set WebOidcClientSecret # (the `_cred` values here just tell the app which keyring entry to read # at request time). # # [web] # enabled = true # bind_host = "192.168.1.50" # explicit LAN IP; never 0.0.0.0 # bind_port = 8800 # audit_log = "/var/lib/ci/logs/web-audit.jsonl" # allowed_users = ["simone"] # Gitea usernames allowed to log in # session_secret_cred = "WebSessionSecret" # keyring KEY NAME, not the secret # # Executions card (recent Gitea Actions runs, live ones included). It normally # reuses the access token from the operator's own OIDC login, held server-side # only -- so neither key below is required. Set them to cover the cases that # token cannot: a service restart (which forgets the logged-in tokens) or a # non-admin operator. # executions_limit = 10 # default rows in the card's selector (1..100) # gitea_token_cred = "GiteaApiToken" # keyring KEY NAME of a fallback PAT # # Gitea OIDC client registration: Gitea -> Settings -> Applications -> OAuth2, # redirect URI must exactly match `redirect_url` below (host+port+path). # # `scopes` needs an API read scope on top of the OIDC ones for the Executions # card: `read:repository` lists the operator's own repos, `read:admin` (admin # accounts only) lists every repo on the instance. Adding a scope requires one # fresh login to re-consent. # # [web.oidc] # issuer = "https://gitea.lan" # client_id = "ci-web" # redirect_url = "http://192.168.1.50:8800/auth/callback" # client_secret_cred = "WebOidcClientSecret" # keyring KEY NAME, not the secret # scopes = ["openid", "profile", "read:repository"] # # Optional: override the default systemd-unit allow-list the dashboard's # service-management panel (Stage D) is allowed to start/stop/restart. Leave # unset to use the built-in default (act-runner, act-runner-rust, # vmware-host, ci-web -- see operations/services.py::MANAGED_SERVICES). # A malformed row (missing/wrong-typed key) is skipped, never crashes # startup. Every command reaching sudo is an exact-match systemctl/systemd-run # invocation approved in deploy/sudoers.d/ci-web -- adding a unit here without # also adding its NOPASSWD entries there just means every action for it fails # (sudo prompts for a password that is never supplied, exit non-zero). # # [[web.managed_services]] # id = "act-runner" # unit = "act-runner.service" # label = "Runner" # allow_stop = true # # [[web.managed_services]] # id = "ci-web" # unit = "ci-web.service" # label = "Web UI" # allow_stop = false # restart-only -- stopping it would lock the operator out