ci: run the runner as a dedicated account under a system service
The earlier script installed a systemd *user* service, which conflated two separate decisions. What matters in host mode is which account jobs run as, because there is no container boundary and the service account is the only sandbox. A system service with User= set gets unprivileged jobs *and* a root-managed lifecycle that starts at boot with no lingering session. Jobs now run as a dedicated `gitea-runner` account in the flutterdev group, with its own HOME so its Gradle and pub caches are its own. The alternative — a human account — would hand every workflow that user's home directory and SSH keys, which is a sharper edge than usual here because a workflow file is code and much of this repo is agent-authored. Running as root would be worse still, and would also miss the warm caches in a human HOME and re-download dependencies every run. flutterdev is what grants write access to /opt/flutter and /opt/android-sdk. Group membership alone does not prove it, since both carry ACLs, so the script tests writability as the service account and fails with the getfacl command to run. Tool lookups use systemd's default PATH rather than an interactive shell's, because that is what the service will actually have. Adds --check, which runs pre-flight and exits without creating anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+25
-4
@@ -196,10 +196,31 @@ arm64-host `gen_snapshot` ([flutter#189724](https://github.com/flutter/flutter/i
|
||||
An x86_64 runner is a requirement, not a preference.
|
||||
|
||||
The runner is **host mode**: jobs use the machine's own Flutter, JDK and Android
|
||||
SDK/NDK. That means CI builds with the same toolchain that produced the verified
|
||||
release, and no NDK download per run. The trade is isolation — workflows run as
|
||||
that user on that machine — so the runner is only for repos whose workflows are
|
||||
trusted.
|
||||
SDK/NDK. CI therefore builds with the same toolchain that produced the verified
|
||||
release, and no NDK is downloaded per run. The cost is that there is no
|
||||
container boundary, which is what decides the next paragraph.
|
||||
|
||||
Jobs run as a dedicated unprivileged account, `gitea-runner`, in the
|
||||
`flutterdev` group. In host mode the service account *is* the sandbox — there
|
||||
is no other — and a workflow file is code that runs as whoever the service runs
|
||||
as. A human account would hand every workflow that user's home directory and
|
||||
SSH keys; root would be worse and would also miss the warm caches in that home,
|
||||
re-downloading dependencies every run. The dedicated account keeps its own
|
||||
Gradle and pub caches under `/var/lib/gitea-runner`, so the first Android build
|
||||
is slow by design and later ones are not.
|
||||
|
||||
`flutterdev` is what grants write access to `/opt/flutter` and
|
||||
`/opt/android-sdk`; both carry ACLs with `group:flutterdev:rwx` and matching
|
||||
`default:` entries, so new files inherit it. The setup script proves that access
|
||||
as the service account rather than assuming group membership is sufficient.
|
||||
|
||||
It installs a **system** service, not a user service: root-managed lifecycle,
|
||||
starts at boot, no lingering session. That the service is root-managed is
|
||||
unrelated to the jobs being unprivileged — `User=` in the unit settles the
|
||||
second, and conflating the two is easy to do.
|
||||
|
||||
`sudo ./tool/setup_ci_runner.sh --check` validates this machine's toolchain
|
||||
without creating anything.
|
||||
|
||||
Two jobs:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user