feat(homelab): isolate PR runner and add Paperless

This commit is contained in:
forust committed 2026-10-09 01:01:35 +02:00
1 parent e3ae86cd01
commit 563e4c2244
19 files changed
+581 -42

No files matched your search

+51 -20
View File
@@ -1,10 +1,13 @@
# Homelab CI/CD
The native Gitea runners run on **vps**; production runs on **workstation**.
Main-branch checks and image builds use `homelab:host`. Pull request and
non-main checks use `homelab-pr:host` under a separate account without Docker
access. The `homelab-pr` runner is registered at User scope for `forust`, so
any repository under that account can schedule jobs that request this label.
Main-branch checks and image builds use `homelab:host`. Pull request checks use
`homelab-pr` in a Docker job container. CI PR checks use `pull_request_target`,
so Gitea loads the workflow from the trusted base branch. That event then runs
untrusted PR code, so the workflow must select `homelab-pr` before checkout and
must not expose secrets. The CI validation jobs grant only `contents: read` and
checkout the explicit PR head SHA with `persist-credentials: false`. Register
`homelab-pr` at repository scope so only this repository can schedule its jobs.
Each runner accepts one job at a time; the build waits for every check to pass.
CI and deploy runs also show a summary with
the release SHA, image build or reuse results, deploy mode, selected services,
@@ -42,29 +45,57 @@ Nothing runs `docker system prune`, removes unrelated images, or deletes volumes
### Pull request runner
Install the unprivileged host runner on the VPS:
Install the PR container runner on the VPS:
```sh
sudo bash .gitea/runner/setup-pr-runner.sh
```
Get a registration token from the user Actions runner settings. Run the
installer in a terminal. It asks for the token without echoing it, registers the
runner as `homelab-pr` with label `homelab-pr:host`, then enables the service.
The work directory is `/var/lib/gitea-pr-runner`. Confirm that Gitea lists the
runner as User scope before merging the workflow change. An unmatched label can
fall back to the default job image.
Create a runner registration token from this repository's Actions runner
settings. Run the installer in a terminal. It asks for the token without
echoing it and registers `homelab-pr` with label
`homelab-pr:docker://docker.gitea.com/runner-images:ubuntu-24.04-v26.09.01`. Confirm that
Gitea lists the runner at Repository scope. The service runs as
`gitea-pr-runner`; systemd grants that service access to the Docker socket with
`SupplementaryGroups=docker`. Keep the account itself out of the `docker`
group. The work directory is `/var/lib/gitea-pr-runner`.
Renovate PR validation uses `pull_request_target`, which reads the workflow from
the base branch. It checks out the PR head only after runner selection and runs
that code on `homelab-pr`. Keep this workflow read-only and do not add secrets.
The runner config disables privileged containers, forbids workflow volume
mounts, and prevents the Docker socket from being mounted into job and action
containers. Do not mount the runner home or its host-side tool cache into a job.
The existing host-side cache is retained, but PR job containers cannot read it.
An unmatched label can fall back to the default job image; check the registered
label before enabling PR checks.
The PR runner has a separate home and tool cache. Do not add it to the `docker`
group or give it access to `/var/run/docker.sock`. It runs repository code from
pull requests, so keep its registration and permissions separate from the
trusted `homelab` runner. This separates users and host permissions, but both
runners still share the VPS kernel and network. Use a disposable VM if PRs from
untrusted external authors must be fully isolated.
The installer reuses `/var/lib/gitea-pr-runner/.runner` when it exists. That
file keeps the registration scope assigned by Gitea. To move an existing
User-scoped runner to Repository scope, stop the service, remove the old runner
from Gitea, back up and remove that registration file, then run the installer
with a token created in this repository's Actions runner settings. Confirm the
new scope in Gitea before enabling PR checks.
The CI and Renovate workflows use `pull_request_target`, which reads the
workflow from the base branch. They select `homelab-pr` before checking out PR
code. The explicit head SHA and `persist-credentials: false` are mandatory:
without the latter, checkout can leave the job token in Git configuration.
Keep PR validation read-only and do not add Actions secrets. In the checked-in
workflows, only a push to `main` or a manual CI run on `main` can select the
trusted `homelab` runner. Gitea schedules jobs by matching `runs-on` labels; the
runner does not restrict jobs by event or branch. Keep Gitea's approval gate for
fork PR workflows enabled. Verify the live Gitea version and approval setting
before relying on this gate; the image tag in the repository does not prove the
version currently running. Before approving a fork workflow run, review all new
and changed workflow files: a PR-defined `pull_request` workflow can request
the `homelab` label. Automatic CI and Renovate PR checks use the trusted base
workflow and select only `homelab-pr`. The release and deploy jobs stay on the
trusted runner.
The runner service can access the host Docker daemon, but job and action
containers do not receive its socket or arbitrary host mounts. The runner and
job containers still share the VPS kernel and Docker daemon. A container escape
can therefore affect the host and other workloads. This is container isolation,
not VM isolation; use disposable VMs for PRs that require a separate kernel and
Docker daemon.
## Workstation setup