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
86 changes: 22 additions & 64 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,85 +9,43 @@ module follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-07-09

The initial release: a rootless GitLab Runner host — runner config, the rootless-Docker
pieces it depends on, packages, and secrets — managed as reviewable Puppet code, applied
standalone or from a Puppet fleet. Targets Ubuntu 22.04 with Puppet 8/OpenVox 8.
The initial release: a rootless GitLab Runner host — runner config, the rootless-Docker pieces it depends on, packages, and secrets — managed as reviewable Puppet code, applied standalone or from a Puppet fleet. Targets Ubuntu 22.04 with Puppet 8/OpenVox 8.

### Added

#### Runner configuration
- Manage the entire GitLab Runner `config.toml` file from Hiera data — global settings and any
number of runners on a single host, all from one node file. The module is never edited to
onboard a host or team; host data drives it.
- Declare values shared by every runner once (`url`, `image`, `executor`, …) and override
per runner where needed.
- Support the Docker executor options runners actually use — images, volumes,
environment, per-runner caching, and image/pull-policy allowlists — so the module stays
out of the way of runner tuning.
- Changes converge continuously: once a data change is applied, the runner adopts the new
configuration on its own within seconds — no service restarts to arrange.
- Removing a runner is a data change too: drop its entry from Hiera and the next apply
renders it out of the config, so the host stops offering it (delete the record on the
GitLab side separately).
- Manage the entire GitLab Runner `config.toml` from Hiera data — global settings and any number of runners on a single host, all from one node file.
- Declare values shared by every runner once (`url`, `image`, `executor`, …) and override per runner where needed.
- Support the Docker executor options runners actually use — images, volumes, environment, per-runner caching, and image/pull-policy allowlists.
- A config change needs no service restart — GitLab Runner re-reads `config.toml` within seconds on its own.
- Remove a runner as a data change: drop its entry from Hiera and the next apply renders it out.

#### Rootless Docker
- Make the rootless-Docker `no-detach-netns` fix permanent, so routine package upgrades on
Ubuntu 22.04 stop silently breaking rootless container networking.
- A toggle lets the module build the runner user's rootless Docker daemon from scratch — so a
fresh host becomes a working runner from the module alone — or, left off, treats an existing
daemon as a prerequisite. When it builds the daemon, a preflight checks the prerequisites first
and stops with a clear message if any is missing.
- Make the rootless-Docker `no-detach-netns` fix permanent, so package upgrades on Ubuntu 22.04 stop breaking rootless container networking.
- Optionally build the runner user's rootless Docker daemon from scratch, with a preflight check on prerequisites; or, left off, treat an existing daemon as a prerequisite.

#### Secrets
- Keep runner tokens out of git: they live in an off-repository store on the host and are
referenced by name, so neither the control repository nor the rendered configuration ever
contains a secret.
- Deploy pre-created GitLab runner tokens — the module never talks to the GitLab API,
so an apply never depends on GitLab being reachable, and a host only ever holds its own
runner token.
- Fail loudly on an unrecognized runner setting, and — when a secret store is present — on
any runner whose token can't be resolved, so a typo (even in a security allowlist) stops
the apply instead of silently bringing up a misconfigured runner. Without a store
(dry-runs, CI validation), tokens render blank by design, so the configuration still
compiles anywhere.
- Fetch runner tokens by name from an off-repository store on the host, so neither the control repository nor the rendered configuration contains a secret.
- The module consumes pre-created runner tokens and never calls the GitLab API, so an apply never depends on GitLab being reachable.
- Fail loudly on an unrecognized runner setting, or a token that can't be resolved when a secret store is present. Without a store, tokens render blank so the configuration still compiles.

#### Flexible host ownership
- Opt-in toggles decide what the module owns on each host: the runner user and the
rootless prerequisites it needs, the Docker and GitLab Runner apt repositories (so stock
Ubuntu just works), the package set, and the runner service itself.
- Any toggle left off keeps the module hands-off that concern — safe to run alongside
another configuration-management system that already owns the user, the daemon, or the
repositories.
- Coexist with a central Puppet agent: the standalone apply runs with an isolated
`--confdir`/`--vardir`, so it never collides with an existing agent's configuration or
state on the same host.
- Restart the runner gracefully: where the module owns the service, a restart drains running
jobs (SIGQUIT) instead of aborting them, tunable via a kill-signal and a stop-timeout, so a
legitimate restart never kills in-flight CI jobs.
- Opt-in toggles decide what the module owns per host: the runner user and its rootless prerequisites, the Docker and GitLab Runner apt repositories, the package set, and the runner service.
- Any toggle left off keeps the module hands-off that concern, so it is safe to run alongside another configuration-management system.
- Coexist with a central Puppet agent: the standalone apply runs with an isolated `--confdir`/`--vardir`, so it never collides with an existing agent.
- Restart the runner gracefully: a restart drains running jobs (SIGQUIT) instead of aborting them, tunable via a kill-signal and stop-timeout.

#### Standalone self-update
- A single toggle turns a host into a self-converging runner: the module installs a
timer that pulls the control repository, verifies it, and re-applies on a schedule, so drift
corrects itself while every change stays review-gated through git.
- A single toggle turns a host into a self-converging runner: a timer pulls the control repository, verifies it, and re-applies on a schedule, so drift corrects itself while every change stays review-gated through git.
- Only signed commits on the protected branch are ever applied.
- A built-in health check continuously confirms the runner is healthy and the host has not
fallen behind on a stale checkout, surfacing problems through ordinary host monitoring. It also
verifies the apply timer is still enabled, so a halted self-update is caught at once.
- Route failures to your own alerting: an optional hook fires a systemd unit of your choice
when an apply or healthcheck run fails.
- A built-in health check confirms the runner is healthy, the checkout is not stale, and the apply timer is still enabled, surfacing problems through ordinary host monitoring.
- Route failures to your own alerting: an optional hook fires a systemd unit when an apply or healthcheck run fails.

### Security
- Rootless by design: the runner and its Docker daemon run as an unprivileged user,
not root; where the module manages the runner service, it runs privilege-dropped by default.
- With the rootless bring-up enabled, the module stops and masks the rootful system Docker
daemon and the idle root containerd service that installing `docker-ce`/`containerd.io`
otherwise leaves running as root, so the only container daemon on the host is the
unprivileged one.
- Runner tokens are handled as sensitive values end to end, so they stay out of compiled
catalogs, Puppet reports, and configuration diffs.
- Config inputs are escaped and type-checked where they are rendered, so a stray quote,
newline or malformed value in host data is escaped or rejected up front instead of
producing a broken or injected runner configuration.
- Rootless by design: the runner and its Docker daemon run as an unprivileged user, and the runner service runs privilege-dropped by default.
- With rootless bring-up enabled, the module stops and masks the rootful system Docker daemon and the idle root containerd service, so the only container daemon on the host is the unprivileged one.
- Runner tokens are handled as sensitive values end to end, so they stay out of compiled catalogs, Puppet reports, and configuration diffs.
- Config inputs are escaped and type-checked where they are rendered, so a malformed value in host data is rejected up front instead of producing a broken or injected configuration.

[Unreleased]: https://github.com/eth-library/puppet-rootless_gitlab_runner/compare/v1.0.0...main
[1.0.0]: https://github.com/eth-library/puppet-rootless_gitlab_runner/releases/tag/v1.0.0
36 changes: 36 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ merged and deployed. Consuming and operating the module on a host is covered by
- [Change workflow](#change-workflow)
- [Branching strategy](#branching-strategy)
- [Commit rules](#commit-rules)
- [Changelog](#changelog)
- [CI/CD](#cicd)
- [Releases](#releases)
- [References](#references)
Expand Down Expand Up @@ -360,6 +361,41 @@ Repository-wide rules; they apply to every commit on every branch.
[Releases](#releases): a renamed or removed parameter or Hiera key, a changed
host-affecting default, or a dropped supported Puppet, Ruby, or operating-system version.

## Changelog

`CHANGELOG.md` is consumer-facing: it follows Keep a Changelog [\[20\]](#ref-20), and its
per-version section is what the release workflow publishes verbatim as the GitHub Release
notes. Add an entry under `## [Unreleased]` in the same pull request as the change it
describes; at release time that section is rolled into the new version (see
[Releases](#releases)).

**What earns an entry — consumer-visible changes only:**
- a new parameter, toggle, or Hiera key, or a change to an existing one;
- a change in a default, or in what the module manages;
- a fix in rendered configuration or applied host state;
- a new or dropped dependency, or a change in supported OS / Puppet range;
- a security-relevant change.

Put it under the right Keep a Changelog category — Added, Changed, Deprecated, Removed,
Fixed, Security. Internal-only work earns no entry: refactors, tests, CI, and dev tooling.
Documentation changes usually don't either — the CHANGELOG tracks the module's behaviour and
interface, not its prose — but a substantial new piece of user-facing documentation (a guide,
a migration note) can warrant an **Added** entry.

**Style — write for an operator reading the release notes:**
- State what changed and, where it helps an operator, a concrete benefit or why it matters.
One entry per user-visible change, a line or two at most.
- Lead with the change itself, not a meta-phrase like "Now supports…".
- Then stop. Cut vague or promotional justification, long rationale, how-to, and examples;
those belong in the README.
- Don't overclaim: describe what the module actually does, not an outcome it can't
guarantee. e.g. write "fetch tokens by name from an
off-repository store", not "keep tokens out of git".
- Leave out implementation detail, such as file modes, exit codes, internal environment
variables, or private class names.
- Write each entry as a single unwrapped line. It becomes the GitHub Release notes, where
wrapped lines break mid-sentence.

## CI/CD

The GitHub Actions [\[12\]](#ref-12) workflow `.github/workflows/ci.yml` runs the
Expand Down