vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 1h ago
4b8c9e1d
| 1 | [Unit] |
| 2 | Description=Vole CI runner |
| 3 | Documentation=https://rodent.software/vole |
| 4 | After=network-online.target |
| 5 | Wants=network-online.target |
| 6 | |
| 7 | # An instance that is down, or a name that does not resolve yet, is not a reason |
| 8 | # to give up on the machine: the runner waits and tries again. |
| 9 | StartLimitIntervalSec=0 |
| 10 | |
| 11 | [Service] |
| 12 | Type=simple |
| 13 | |
| 14 | # A dedicated unprivileged account with a home directory and a subuid and subgid |
| 15 | # range, so the container runtime is rootless. docs/deploy/runner.md has the two |
| 16 | # commands that set it up. The instance's own account is not this one: a job that |
| 17 | # escapes its container should not find the forge's data on the same machine. |
| 18 | User=ci-runner |
| 19 | Group=ci-runner |
| 20 | Environment=HOME=/var/lib/vole-runner |
| 21 | WorkingDirectory=/var/lib/vole-runner |
| 22 | RuntimeDirectory=vole-runner |
| 23 | RuntimeDirectoryMode=0700 |
| 24 | Environment=XDG_RUNTIME_DIR=/run/vole-runner |
| 25 | |
| 26 | # The arguments live in a file so one unit serves every machine: --remote, |
| 27 | # --label, --dir, --known-hosts, --container-network. See the example shipped as |
| 28 | # /etc/vole/runner.env. |
| 29 | EnvironmentFile=-/etc/vole/runner.env |
| 30 | ExecStartPre=/bin/sh -c 'test -n "$VOLE_RUNNER_ARGS" || { echo "runner is not configured: set VOLE_RUNNER_ARGS in /etc/vole/runner.env" >&2; exit 1; }' |
| 31 | ExecStart=/usr/bin/vole runner $VOLE_RUNNER_ARGS |
| 32 | |
| 33 | Restart=on-failure |
| 34 | RestartSec=10s |
| 35 | TimeoutStopSec=60s |
| 36 | |
| 37 | # Rootless podman creates a cgroup per container, which it can only do if this |
| 38 | # service's own cgroup is delegated to it. |
| 39 | Delegate=yes |
| 40 | |
| 41 | # The backstop under the container limits. Each job gets --container-memory, and this |
| 42 | # bounds the whole runner including every job in flight, for the case where a job gets |
| 43 | # past its own cgroup: a percentage is of the machine, so the unit does not have to |
| 44 | # know how big it is. Size --jobs times --container-memory below it, and leave the rest |
| 45 | # for the kernel and the runtime's own storage. TasksMax is deliberately not set: the |
| 46 | # container's own --container-pids already applies to everything the runner starts, |
| 47 | # and a unit-level value would have to track --jobs and --container-pids, failing |
| 48 | # builds in a way that looks like the workflow's fault when it drifts. |
| 49 | MemoryMax=90% |
| 50 | |
| 51 | # A core dump of this process is a copy of the runner's SSH key, which leases jobs |
| 52 | # and reports their results. |
| 53 | LimitCORE=0 |
| 54 | LimitNOFILE=65536 |
| 55 | UMask=0027 |
| 56 | PrivateTmp=true |
| 57 | |
| 58 | # Deliberately less sandboxed than vole.service: the isolation a job gets is its |
| 59 | # container, and the runtime needs user namespaces and a writable home to build |
| 60 | # one. Nothing here reaches the instance's data, because it is not on this |
| 61 | # machine. |
| 62 | # |
| 63 | # NoNewPrivileges is off for the same reason, and it is the one setting the unit |
| 64 | # cannot keep. Rootless podman, Docker, and nerdctl all map the calling user into |
| 65 | # the container through setuid newuidmap and newgidmap, and this setting stops a |
| 66 | # setuid helper from gaining privilege, so the mapping fails before a container |
| 67 | # exists: "newuidmap: write to uid_map failed: Operation not permitted". A job's |
| 68 | # isolation is its container, not this sandbox, so the rest of the hardening stays. |
| 69 | NoNewPrivileges=false |
| 70 | |
| 71 | [Install] |
| 72 | WantedBy=multi-user.target |
| 73 |