runner: reproducible nix-built job image + compose-based act_runner
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
name: auto-merge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
merge:
|
||||
if: github.event.label.name == 'auto-merge'
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: merge
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
tea pr merge \
|
||||
--repo "${{ github.repository }}" \
|
||||
--style squash \
|
||||
"${{ github.event.pull_request.number }}"
|
||||
@@ -62,7 +62,9 @@ jobs:
|
||||
done
|
||||
done
|
||||
|
||||
# 4. Build smoke — every changed package must build.
|
||||
# 4. Build smoke — every changed package must build. Cache push
|
||||
# is intentionally absent for now (no shared binary cache);
|
||||
# add a step here once cache infra is decided.
|
||||
- name: build smoke
|
||||
if: steps.changed.outputs.packages != ''
|
||||
run: |
|
||||
@@ -71,20 +73,9 @@ jobs:
|
||||
.#${pkg} --no-link --print-out-paths
|
||||
done
|
||||
|
||||
# 5. Cache push (only on the validated outputs, before merge).
|
||||
- name: push to binary cache
|
||||
if: steps.changed.outputs.packages != ''
|
||||
env:
|
||||
NIX_SECRET_KEY_FILE: ${{ secrets.NIX_CACHE_SECRET_KEY_FILE }}
|
||||
CACHE_URL: ${{ vars.CARGOXX_CACHE_URL }}
|
||||
run: |
|
||||
for pkg in ${{ steps.changed.outputs.packages }}; do
|
||||
nix copy --extra-experimental-features 'nix-command flakes' \
|
||||
--to "${CACHE_URL}?secret-key=${NIX_SECRET_KEY_FILE}" \
|
||||
.#${pkg}
|
||||
done
|
||||
|
||||
# 6. Maintainer match.
|
||||
# 5. Maintainer check — PR must come from someone listed in
|
||||
# recipes/<pkg>/maintainers.txt (auto-pass for new packages,
|
||||
# since the PR introduces the file in the same commit).
|
||||
- name: maintainer check
|
||||
if: steps.changed.outputs.packages != ''
|
||||
run: |
|
||||
@@ -92,18 +83,11 @@ jobs:
|
||||
for pkg in ${{ steps.changed.outputs.packages }}; do
|
||||
list="recipes/$pkg/maintainers.txt"
|
||||
if [[ ! -f "$list" ]]; then
|
||||
echo "new package $pkg — maintainers.txt will be added by this PR"
|
||||
echo "new package $pkg — maintainers.txt added by this PR"
|
||||
continue
|
||||
fi
|
||||
if ! grep -E -q "^\s*${author}\s*(\#.*)?$" "$list"; then
|
||||
echo "PR author '$author' is not in $list"
|
||||
gh pr edit ${{ github.event.pull_request.number }} \
|
||||
--add-label needs-human-review
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: label auto-merge
|
||||
if: steps.changed.outputs.packages != ''
|
||||
run: |
|
||||
gh pr edit ${{ github.event.pull_request.number }} --add-label auto-merge
|
||||
|
||||
4
runner/.gitignore
vendored
Normal file
4
runner/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.env
|
||||
data/
|
||||
flake.lock
|
||||
result
|
||||
71
runner/README.md
Normal file
71
runner/README.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# cargoxx-pkgs runner
|
||||
|
||||
Self-hosted Gitea Actions runner that validates package PRs.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
+----------------+ +-------------------+ +--------------------+
|
||||
| Gitea instance | <----> | act_runner | -----> | job container |
|
||||
| (queues jobs) | poll | (this docker- | docker| cargoxx-runner- |
|
||||
| | | compose service) | spawn | job:latest |
|
||||
+----------------+ +-------------------+ +--------------------+
|
||||
```
|
||||
|
||||
- `compose.yml` runs the official `gitea/act_runner:nightly` image,
|
||||
which polls Gitea for jobs and spawns one container per workflow
|
||||
run via the host's Docker socket.
|
||||
- `flake.nix` builds the **job container image** — reproducible,
|
||||
declarative, ships `nix`, `git`, `curl`, `jq`, `tea`, and a
|
||||
single-user `NIX_CONFIG`. This image runs the actual workflow
|
||||
steps; act_runner just orchestrates it.
|
||||
- `config.yaml` maps `runs-on: self-hosted` (from
|
||||
`.gitea/workflows/*.yml`) to the job image.
|
||||
|
||||
## One-time setup
|
||||
|
||||
1. **Build + load the job image** into the host's Docker daemon:
|
||||
|
||||
```sh
|
||||
cd runner
|
||||
nix run --extra-experimental-features 'nix-command flakes' .#load-image
|
||||
```
|
||||
|
||||
Re-run whenever you bump nixpkgs or change the tool list.
|
||||
|
||||
2. **Mint a runner registration token** in the Gitea UI:
|
||||
`Site Administration → Actions → Runners → Create new Runner`.
|
||||
Copy the token.
|
||||
|
||||
3. **Provision the `.env`** alongside `compose.yml`:
|
||||
|
||||
```env
|
||||
GITEA_INSTANCE_URL=https://git.amadey.xyz
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN=<paste here>
|
||||
GITEA_RUNNER_NAME=cargoxx-pkgs-runner
|
||||
GITEA_RUNNER_LABELS=self-hosted
|
||||
```
|
||||
|
||||
4. **Start the runner**:
|
||||
|
||||
```sh
|
||||
docker compose up -d
|
||||
docker compose logs -f runner
|
||||
```
|
||||
|
||||
First boot registers the runner with Gitea; subsequent boots reuse
|
||||
the persisted token in `./data/.runner`.
|
||||
|
||||
## Updating
|
||||
|
||||
- Change a workflow step's tools → edit `flake.nix`, rerun
|
||||
`nix run .#load-image`.
|
||||
- Bump nixpkgs → `nix flake update` in this dir, rebuild the image.
|
||||
- act_runner itself updates by `docker compose pull && docker compose up -d`.
|
||||
|
||||
## Why not host mode?
|
||||
|
||||
Host mode (act_runner running workflows directly on the host) would
|
||||
save the Docker indirection but tightly couples the runner host to
|
||||
the toolchain. Docker mode lets us treat the job image as a
|
||||
deployable artifact — same image on every runner, no drift.
|
||||
20
runner/compose.yml
Normal file
20
runner/compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Runs the act_runner that listens to Gitea and spawns one job
|
||||
# container per workflow run. The job image (cargoxx-runner-job:latest)
|
||||
# is built reproducibly from runner/flake.nix — run `nix run .#load-image`
|
||||
# in this directory to load it into the host's Docker daemon before
|
||||
# starting the runner.
|
||||
version: "3.8"
|
||||
services:
|
||||
runner:
|
||||
image: docker.io/gitea/act_runner:nightly
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
CONFIG_FILE: /config.yaml
|
||||
GITEA_INSTANCE_URL: "${GITEA_INSTANCE_URL}"
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: "${GITEA_RUNNER_REGISTRATION_TOKEN}"
|
||||
GITEA_RUNNER_NAME: "${GITEA_RUNNER_NAME:-cargoxx-pkgs-runner}"
|
||||
GITEA_RUNNER_LABELS: "${GITEA_RUNNER_LABELS:-self-hosted}"
|
||||
volumes:
|
||||
- ./config.yaml:/config.yaml:ro
|
||||
- ./data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
28
runner/config.yaml
Normal file
28
runner/config.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# act_runner config. The `runner.labels` mapping says: when a workflow
|
||||
# requests `runs-on: self-hosted`, spawn the cargoxx-runner-job:latest
|
||||
# image (built from runner/flake.nix). Other labels can be added by
|
||||
# building additional images and listing them here.
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: .runner
|
||||
capacity: 1
|
||||
envs: {}
|
||||
labels:
|
||||
- "self-hosted:docker://cargoxx-runner-job:latest"
|
||||
|
||||
cache:
|
||||
enabled: false
|
||||
|
||||
container:
|
||||
network: bridge
|
||||
privileged: false
|
||||
options: ""
|
||||
workdir_parent: /workspace
|
||||
valid_volumes: []
|
||||
docker_host: "unix:///var/run/docker.sock"
|
||||
force_pull: false
|
||||
|
||||
host:
|
||||
workdir_parent: ""
|
||||
96
runner/flake.nix
Normal file
96
runner/flake.nix
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
description = "OCI image for cargoxx-pkgs CI jobs: nix + tea + git + jq";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
|
||||
# Single-user nix config — same defaults used by the cargoxx
|
||||
# distribution wrapper. Avoids the multi-user nixbld group
|
||||
# requirement; sandbox disabled because the runner container
|
||||
# itself doesn't usually have user-namespace support.
|
||||
nixConfig = ''
|
||||
experimental-features = nix-command flakes
|
||||
build-users-group =
|
||||
sandbox = false
|
||||
accept-flake-config = true
|
||||
'';
|
||||
in {
|
||||
packages.default = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "cargoxx-runner-job";
|
||||
tag = "latest";
|
||||
|
||||
contents = with pkgs; [
|
||||
bashInteractive
|
||||
coreutils
|
||||
findutils
|
||||
gawk
|
||||
gnugrep
|
||||
gnused
|
||||
gnutar
|
||||
gzip
|
||||
xz
|
||||
|
||||
nix
|
||||
git
|
||||
curl
|
||||
jq
|
||||
tea
|
||||
|
||||
cacert
|
||||
iana-etc
|
||||
];
|
||||
|
||||
# Skeleton filesystem layout: /tmp, /etc/passwd for nix,
|
||||
# writable nix store, cacert pointer.
|
||||
extraCommands = ''
|
||||
mkdir -p tmp etc nix/var/{nix,log/nix} root
|
||||
chmod 1777 tmp
|
||||
|
||||
cat > etc/passwd <<'EOF'
|
||||
root:x:0:0:root:/root:/bin/bash
|
||||
nobody:x:65534:65534:nobody:/var/empty:/bin/false
|
||||
EOF
|
||||
cat > etc/group <<'EOF'
|
||||
root:x:0:
|
||||
nobody:x:65534:
|
||||
EOF
|
||||
cat > etc/nix/nix.conf <<'EOF'
|
||||
${nixConfig}
|
||||
EOF
|
||||
'';
|
||||
|
||||
config = {
|
||||
Env = [
|
||||
"PATH=/bin:/usr/bin"
|
||||
"NIX_CONFIG=${nixConfig}"
|
||||
"NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
|
||||
"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
|
||||
"HOME=/root"
|
||||
"USER=root"
|
||||
];
|
||||
Cmd = [ "/bin/bash" ];
|
||||
WorkingDir = "/root";
|
||||
};
|
||||
};
|
||||
|
||||
# `nix run .#load-image` builds the image and pipes it into the
|
||||
# local Docker daemon — no registry needed for single-host
|
||||
# deployments.
|
||||
apps.load-image = {
|
||||
type = "app";
|
||||
program = toString (pkgs.writeShellScript "load-image" ''
|
||||
set -euo pipefail
|
||||
img=$(nix build --no-link --print-out-paths .#default)
|
||||
echo "loading $img into docker…"
|
||||
${pkgs.docker}/bin/docker load < "$img"
|
||||
'');
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user