Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "template-appliance",
"name": "runner",
"build": {
"dockerfile": "Dockerfile"
},
Expand Down
6 changes: 3 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# GitHub Copilot instructions (template-appliance)
# GitHub Copilot instructions (runner)

These instructions describe how this repo is structured, how CI runs, and what “good changes” look like.

If anything here conflicts with an explicit user request in the chat, follow the user request.

## 1) What this repo is

`template-appliance` is a Bash-first skeleton project that ships scripts + systemd units for a generic appliance with a primary mode, a secondary mode, and a healthcheck-based failover.
`runner` is a Bash-first appliance project that ships scripts + systemd units for running a single GitHub Actions self-hosted runner.

The codebase emphasizes:
- Script correctness and predictable behavior under `set -euo pipefail`
Expand All @@ -23,7 +23,7 @@ Preferred ways to run things:
- One CI part: `./scripts/ci.sh lint-sh` (or other part names)
- Make targets (when available): `make lint`, `make test-unit`, `make test-integration`, `make ci`

The Makefile runs commands inside a Docker devcontainer image (`template-appliance-devcontainer:local`). If `docker` isn’t available, the Makefile may fall back to running locally.
The Makefile runs commands inside a Docker devcontainer image (`runner-devcontainer:local`). If `docker` isn’t available, the Makefile may fall back to running locally.

## 3) CI/router model (important)

Expand Down
32 changes: 16 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -37,7 +37,7 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh lint-permissions'

lint-naming:
Expand All @@ -54,7 +54,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -66,7 +66,7 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh lint-naming'

lint-sh:
Expand All @@ -83,7 +83,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -95,7 +95,7 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh lint-sh'

test-all:
Expand All @@ -112,7 +112,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -124,7 +124,7 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh tests'

test-coverage:
Expand All @@ -142,7 +142,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -155,7 +155,7 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh coverage'

- name: Show kcov output paths
Expand Down Expand Up @@ -217,7 +217,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -229,7 +229,7 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh lint-yaml'

lint-systemd:
Expand All @@ -246,7 +246,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -258,7 +258,7 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh lint-systemd'

lint-markdown:
Expand All @@ -275,7 +275,7 @@ jobs:
with:
context: .
file: .devcontainer/Dockerfile
tags: template-appliance-devcontainer:ci
tags: runner-devcontainer:ci
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
Expand All @@ -287,5 +287,5 @@ jobs:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer:ci \
runner-devcontainer:ci \
bash -lc './scripts/ci.sh lint-markdown'
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ tests/.tmp/
.tmp/
.tmp-bats-trace.txt

# Junk dirs accidentally created by misquoted paths
"mp_dir/
"PPLIANCE_ROOT/

# Coverage artifacts (generated by kcov runs)
coverage/
coverage-devcontainer*/
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ That is the same pipeline GitHub CI uses.
Build the devcontainer image:

```bash
docker build -t template-appliance-devcontainer -f .devcontainer/Dockerfile .
docker build -t runner-devcontainer -f .devcontainer/Dockerfile .
```

Run the full pipeline inside it:
Expand All @@ -72,7 +72,7 @@ Run the full pipeline inside it:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer \
runner-devcontainer \
bash -lc './scripts/ci.sh'
```

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SHELL := /usr/bin/env bash
ci

DOCKER ?= docker
DEVCONTAINER_IMAGE ?= template-appliance-devcontainer:local
DEVCONTAINER_IMAGE ?= runner-devcontainer:local
DEVCONTAINER_DOCKERFILE ?= .devcontainer/Dockerfile
DEVCONTAINER_CONTEXT ?= .
DEVCONTAINER_WORKDIR ?= /work
Expand Down
79 changes: 37 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# template-appliance
# runner

Reusable Bash + systemd appliance skeleton built around a simple model:
Bash + systemd appliance for running a single GitHub Actions self-hosted runner on Linux (including Raspberry Pi).

- Primary mode: run your main workload (`APPLIANCE_PRIMARY_CMD`)
- Secondary mode: run a fallback workload (`APPLIANCE_SECONDARY_CMD`)
- Healthcheck timer: if primary is not active, start secondary
Goal:

This repo is intentionally minimal: you provide the commands and any extra packages.
- Keep exactly one host runner process managed by systemd.
- Route job execution into an ephemeral `systemd-nspawn` guest (systemd PID1 semantics)
via GitHub Actions runner container hooks.

## Documentation

Expand All @@ -19,7 +19,7 @@ This repo is intentionally minimal: you provide the commands and any extra packa
Build the devcontainer image:

```bash
docker build -t template-appliance-devcontainer -f .devcontainer/Dockerfile .
docker build -t runner-devcontainer -f .devcontainer/Dockerfile .
```

Run the full CI pipeline inside it:
Expand All @@ -28,7 +28,7 @@ Run the full CI pipeline inside it:
docker run --rm \
-v "$PWD:/work" \
-w /work \
template-appliance-devcontainer \
runner-devcontainer \
bash -lc './scripts/ci.sh'
```

Expand All @@ -42,27 +42,18 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the required pre-PR checks.

## Runtime model

At a glance, systemd manages three key units:
At a glance, systemd manages two key units:

- `template-appliance-primary.service`
- `template-appliance-secondary.service`
- `template-appliance-healthcheck.timer` (runs `template-appliance-healthcheck.service`)

```mermaid
flowchart TD
PRIMARY["template-appliance-primary.service<br/>runs APPLIANCE_PRIMARY_CMD"]
SECONDARY["template-appliance-secondary.service<br/>runs APPLIANCE_SECONDARY_CMD"]
TIMER["template-appliance-healthcheck.timer"] --> CHECK["template-appliance-healthcheck.service"]
CHECK -->|"primary inactive"| SECONDARY
```
- `runner-install.service` (first-boot installer; retried until it succeeds)
- `runner.service` (runs the configured GitHub runner)

## Installation (cloud-init / Pi Imager)

The recommended install flow is:

1. cloud-init writes `/etc/template-appliance/config.env`.
1. cloud-init writes `/etc/runner/config.env`.
2. cloud-init installs a one-time installer unit + bootstrap script.
3. systemd runs `template-appliance-install.service` until install succeeds.
3. systemd runs `runner-install.service` until install succeeds.

Examples:

Expand All @@ -71,40 +62,44 @@ Examples:

## Configuration

Runtime configuration lives in `/etc/template-appliance/config.env`.
Runtime configuration lives in `/etc/runner/config.env`.

Required (first-boot bootstrap):

- `APPLIANCE_REPO_URL`
- `APPLIANCE_REPO_REF` (branch/tag/commit; pinning to a tag/commit is recommended)

Required (runtime modes):

- `APPLIANCE_PRIMARY_CMD`
- `APPLIANCE_SECONDARY_CMD`

Optional:

- `APPLIANCE_CHECKOUT_DIR` (default: `/opt/template-appliance`)
- `APPLIANCE_INSTALLED_MARKER` (default: `/var/lib/template-appliance/installed`)
- `APPLIANCE_CHECKOUT_DIR` (default: `/opt/runner`)
- `APPLIANCE_INSTALLED_MARKER` (default: `/var/lib/runner/installed`)
- `APPLIANCE_APT_PACKAGES` (space-separated extra packages for install)
- `APPLIANCE_PRIMARY_SERVICE` / `APPLIANCE_SECONDARY_SERVICE` (override healthcheck targets)
- `APPLIANCE_DRY_RUN=1` (do not modify system; record intended actions)

Runner:

- `RUNNER_ACTIONS_RUNNER_DIR` (default: `/opt/runner/actions-runner`)
- `RUNNER_HOOKS_DIR` (default: `/usr/local/lib/runner`)

Job isolation (`systemd-nspawn`):

- `RUNNER_NSPAWN_BASE_ROOTFS` (default: `/var/lib/runner/nspawn/base-rootfs`)
- `RUNNER_NSPAWN_READY_TIMEOUT_S` (default: `20`)
- `RUNNER_NSPAWN_BIND` / `RUNNER_NSPAWN_BIND_RO` (space-separated bind mount entries)

## Day-2 operations

Switch modes:
Inspect service status:

```bash
systemctl start template-appliance-primary.service
systemctl start template-appliance-secondary.service
systemctl status runner.service --no-pager
```

Inspect install/boot status:

```bash
systemctl status template-appliance-install.service --no-pager
ls -l /var/lib/template-appliance/installed || true
systemctl status runner-install.service --no-pager
ls -l /var/lib/runner/installed || true
```

## Manual install (no cloud-init)
Expand All @@ -118,18 +113,18 @@ sudo apt-get update
sudo apt-get install -y --no-install-recommends ca-certificates curl git
```

1. Create `/etc/template-appliance/config.env` (start from the example):
1. Create `/etc/runner/config.env` (start from the example):

```bash
sudo mkdir -p /etc/template-appliance
sudo cp /path/to/template-appliance/examples/config.env.example /etc/template-appliance/config.env
sudo nano /etc/template-appliance/config.env
sudo mkdir -p /etc/runner
sudo cp /path/to/runner/examples/config.env.example /etc/runner/config.env
sudo nano /etc/runner/config.env
```

1. Clone the repo and run the installer as root:

```bash
git clone https://github.com/your-org/template-appliance.git /opt/template-appliance
cd /opt/template-appliance
git clone https://github.com/your-org/github-runner.git /opt/runner
cd /opt/runner
sudo ./scripts/install.sh
```
Loading