{ "_comment": "Example configuration for gitea-macos-runner. Copy to ~/.config/gitea-macos-runner/config.json and edit. Keys beginning with an underscore are comments and are ignored by the loader.", "gitea": { "_comment": "How to reach Gitea and how to authenticate. The token must belong to a Gitea ADMIN: every endpoint used lives under /api/v1/admin/actions/.", "instanceURL": "https://gitea.example.com", "_comment_adminToken": "Admin API token. Set EXACTLY ONE of adminToken and adminTokenFile: setting both, or neither, is rejected at load. Prefer adminTokenFile so the secret is not sitting in a world-readable JSON file.", "adminTokenFile": "~/.config/gitea-macos-runner/admin-token", "_comment_registrationToken": "The shared runner registration token. IMPORTANT: registration tokens are REUSABLE and scope-wide, and minting a new one INVALIDATES every prior token for that scope. Never pre-generate one per VM. The recommended setup is to seed a fixed token server-side with GITEA_RUNNER_REGISTRATION_TOKEN and point registrationTokenFile at a copy of it.", "registrationTokenFile": "~/.config/gitea-macos-runner/registration-token", "_comment_fetchViaAPI": "When no static registration token is configured, fetch one from POST /api/v1/admin/actions/runners/registration-token. Off by default: that endpoint returns the scope's active token, and any behaviour change that made it mint a fresh one would invalidate tokens held by runners elsewhere.", "fetchRegistrationTokenViaAPI": false }, "runner": { "_comment": "Identity of the ephemeral runners registered inside each guest.", "_comment_labels": "BARE label names, matched case-sensitively against a job's runs-on. The ':host' schema suffix is added only when calling `gitea-runner register --labels`; the server never stores it.", "labels": ["macos-arm64"], "_comment_namePrefix": "Prefix for generated runner names. Each VM registers as , globally unique, which is what lets the reconcile loop identify and delete rows orphaned by an unclean VM death.", "namePrefix": "macos-vm-", "_comment_download": "Release asset for the gitea-runner binary installed into the guest. {version} is substituted. The binary is v3.x, renamed from act_runner and published from gitea.com/gitea/runner.", "runnerDownloadURL": "https://gitea.com/gitea/runner/releases/download/v{version}/gitea-runner-{version}-darwin-arm64", "version": "3.0.2" }, "scheduler": { "_comment": "Polling cadence, concurrency, and the timeouts that bound a stuck VM.", "_comment_maxConcurrentVMs": "Hard-clamped to 2. Apple's kernel allows at most two concurrent macOS guests per host; a third start() fails with VZError.virtualMachineLimitExceeded.", "maxConcurrentVMs": 2, "pollIntervalSeconds": 5, "_comment_reconcile": "How often to sweep Gitea for orphaned runner rows. Gitea itself only sweeps runner rows at midnight, and never sweeps a runner that claimed no task, so this loop is not optional.", "reconcileIntervalSeconds": 300, "_comment_jobTimeout": "Wall-clock ceiling on a single job before its VM is destroyed. Should be comfortably under Gitea's own ABANDONED_JOB_TIMEOUT (default 24h).", "jobTimeoutMinutes": 120, "_comment_bootTimeout": "Ceiling on clone + boot + DHCP lease + SSH readiness before the slot is declared dead and recycled.", "bootTimeoutSeconds": 300 }, "guest": { "_comment": "Shape of each guest VM and the credentials used to reach it over SSH. These credentials only ever traverse the host-private NAT link between this Mac and its own ephemeral guests.", "username": "admin", "password": "admin", "cpuCount": 4, "memoryGB": 8, "_comment_diskGB": "Nominal disk size. With the ASIF sparse format this is a ceiling, not an allocation.", "diskGB": 64 }, "storage": { "_comment": "Where images, ephemeral clones, IPSWs, and host state live. Images and clones must share one APFS volume: cloning relies on copy-on-write, which requires the same volume.", "storeDir": "~/Library/Application Support/gitea-macos-runner", "_comment_minFree": "Refuse to clone a VM when the store volume has less than this free. CoW clones start nearly free but grow with every guest write, so keep this well above one clone's nominal size.", "minFreeDiskGB": 20 } }