Skip to content
Draft
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2311780
Gather forgejo classes together
theseal Oct 6, 2025
6ad67fc
Initial commit
theseal Oct 6, 2025
0dce843
Syntax error
theseal Oct 6, 2025
0d5e709
Must be a file
theseal Oct 6, 2025
dcfe50f
Collect everything in /opt
theseal Oct 6, 2025
e0a75f7
Establish trust
theseal Oct 6, 2025
2817eca
Puppet-lint
theseal Oct 6, 2025
9096236
Wrong target
theseal Oct 6, 2025
0d19d31
Download the image in where we will run runner
theseal Oct 6, 2025
62296ef
Wrong path
theseal Oct 6, 2025
2013a27
No checksum available
theseal Oct 6, 2025
f9150a7
Verify image
theseal Oct 6, 2025
af921fc
Verify image if changed
theseal Oct 6, 2025
14989b0
Variables in command
theseal Oct 6, 2025
eee10cb
Signture as well
theseal Oct 6, 2025
83f761a
Simply
theseal Oct 6, 2025
1d99ae6
Unpack
theseal Oct 6, 2025
a2557b3
Errors
theseal Oct 6, 2025
0d28f5b
First attempt to create user
theseal Oct 6, 2025
e405939
Wrong variable
theseal Oct 6, 2025
b949e68
Create homes
theseal Oct 6, 2025
88d928a
Wrapper to execute runner
theseal Oct 6, 2025
6080752
Syntax error
theseal Oct 6, 2025
25a4180
Declare before use
theseal Oct 7, 2025
04f8e4c
the "runner"
theseal Oct 7, 2025
c656c0d
podman needs virtiofsd in path
theseal Oct 7, 2025
c87345d
Run a global script instead of personal
theseal Oct 7, 2025
d54916a
Syntax error
theseal Oct 7, 2025
b5d3dfd
There is something wrong with podmans mounts
theseal Oct 7, 2025
65db749
Something is broken with podmans volume mount
theseal Oct 7, 2025
bdf423a
Script is run by root
theseal Oct 7, 2025
8bad4e8
Run with systemd
theseal Oct 7, 2025
152a556
This is a template
theseal Oct 7, 2025
8e91de2
New syntax and image in modern podman
theseal Oct 10, 2025
c023f18
Depends
theseal Oct 10, 2025
ccd2e59
Moar depends
theseal Oct 10, 2025
8808a04
Config for runner
theseal Oct 10, 2025
46770d1
Full username
theseal Oct 10, 2025
fa9ed06
Wrong owner
theseal Oct 10, 2025
9ec904a
Get secret from hiera
theseal Oct 10, 2025
3f4133a
Registrer if not already
theseal Oct 10, 2025
fda21c0
Syntax error
theseal Oct 10, 2025
a369168
Syntax error
theseal Oct 10, 2025
3b65c91
Fetch from repo
theseal Oct 10, 2025
eeb0ed3
Use pre existing config
theseal Oct 10, 2025
0456386
Syntax error
theseal Oct 10, 2025
ec073fc
Clean up non clean exits
theseal Oct 10, 2025
76975af
Readability
theseal Oct 10, 2025
5c8273b
Enable each runner
theseal Oct 10, 2025
cee2165
Depend
theseal Oct 10, 2025
c0db19c
Forgot to add the code
theseal Oct 10, 2025
9bdb83a
Podman needs the proxy
theseal Oct 10, 2025
528616e
Syntax error
theseal Oct 10, 2025
c9eb416
Should not be executable
theseal Oct 10, 2025
ce89348
Linting
theseal Oct 10, 2025
c9a3561
Moar lints
theseal Oct 10, 2025
c1c7a28
Enable ipv6 for containers and let them run as privileged as we isola…
mikaelfrykholm Oct 23, 2025
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
17 changes: 17 additions & 0 deletions files/forgejo/forgejo-runner.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[Unit]
Description=forgejo-runner as %i
After=network.target

[Service]
Type=simple
User=%i
Group=%i
TimeoutStartSec=0
TimeoutStopSec=30
Restart=always
RestartSec=10
ExecStart=/opt/forgejo-runner/libexec/runner-systemd-wrapper
Restart=on-failure

[Install]
WantedBy=multi-user.target
File renamed without changes.
91 changes: 91 additions & 0 deletions manifests/forgejo/runner.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# A class to install and manage Forgejo runner(s)
class sunet::forgejo::runner (
String $version = '11.1.2',
String $version_sha256sum = '6442d46db2434a227e567a116c379d0eddbe9e7a3f522596b25d31979fd59c8d',
String $machine_image = 'quay.io/podman/machine-os:5.4',
Integer $runners = 4,
String $forgejo_instance = "platform.sunet.se",
) {

include sunet::packages::podman
include sunet::packages::virtiofsd
include sunet::packages::qemu_system_x86
include sunet::packages::gvproxy

file {'/usr/lib/podman/gvproxy':
ensure => 'link',
target => '/usr/bin/gvproxy'
}

$registration_token = lookup('forgejo_registration_token', undef, undef, 'NOT_SET_IN_HIERA');

file {'/opt/forgejo-runner':
ensure => 'directory',
}
file {'/opt/forgejo-runner/bin':
ensure => 'directory',
}

file {'/opt/forgejo-runner/images':
ensure => 'directory',
}

file {'/opt/forgejo-runner/libexec':
ensure => 'directory',
}

file { '/opt/forgejo-runner/bin/forgejo-runner':
ensure => 'file',
source => "https://code.forgejo.org/forgejo/runner/releases/download/v${version}/forgejo-runner-${version}-linux-amd64",
checksum => 'sha256',
checksum_value => $version_sha256sum,
mode => '0755',
}

file { '/opt/forgejo-runner/libexec/runner-systemd-wrapper':
ensure => 'file',
content => template('sunet/forgejo/runner-systemd-wrapper.erb'),
mode => '0755',
}

file { '/opt/forgejo-runner/libexec/runner-wrapper':
ensure => 'file',
content => template('sunet/forgejo/runner-wrapper.erb'),
mode => '0755',
}

file { '/etc/systemd/system/[email protected]':
ensure => 'file',
content => file('sunet/forgejo/forgejo-runner.service'),
mode => '0644',
}

range(0, $runners - 1).each |$runner|{
$user = "runner-${runner}"

user { $user:
ensure => 'present',
groups => ['kvm'],
home => "/home/${user}",
managehome => true,
notify => Exec["linger_user_${runner}"]
}

exec { "linger_user_${runner}":
command => "/usr/bin/loginctl enable-linger ${user}",
refreshonly => true,
}

file { "/home/${user}/runner.config":
ensure => 'file',
content => template('sunet/forgejo/runner.config.erb'),
mode => '0700',
owner => $user,
}

service { "sunet-forgejo-runner@${user}":
ensure => 'running',
enable => true,
}
}
}
4 changes: 4 additions & 0 deletions manifests/packages/gvproxy.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# gvproxy
class sunet::packages::gvproxy {
package { 'gvproxy': ensure => installed }
}
32 changes: 3 additions & 29 deletions manifests/packages/podman.pp
Original file line number Diff line number Diff line change
@@ -1,30 +1,4 @@
# Install podman from kubic
class sunet::packages::podman(
) {
include sunet::packages::curl
include sunet::packages::gpg
include stdlib

if $facts['os']['name'] == 'Ubuntu' {
$reponame = 'xUbuntu'
} else {
$reponame = $facts['os']['name']
}
exec { 'podman_repo_key':
creates => '/etc/apt/trusted.gpg.d/libcontainers.gpg',
command => "curl https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/${reponame}_${facts['os']['release']['major']}/Release.key | gpg --dearmor > /etc/apt/trusted.gpg.d/libcontainers.gpg",
unless => 'test -f /etc/apt/trusted.gpg.d/libcontainers.gpg',
}
-> file { 'podman_repo_file':
ensure => file,
name => '/etc/apt/sources.list.d/libcontainers.list',
content => "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/${reponame}_${facts['os']['release']['major']}/ /\n",
}
-> exec { 'podman_update':
command => 'apt update',
unless => 'dpkg -l podman'
}
-> package { 'podman':
ensure => latest
}
# podman
class sunet::packages::podman {
package { 'podman': ensure => installed }
}
4 changes: 4 additions & 0 deletions manifests/packages/qemu_system_x86.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# qemu-system-x86
class sunet::packages::qemu_system_x86 {
package { 'qemu-system-x86': ensure => installed }
}
4 changes: 4 additions & 0 deletions manifests/packages/virtiofsd.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# virtiofsd
class sunet::packages::virtiofsd {
package { 'virtiofsd': ensure => installed }
}
26 changes: 26 additions & 0 deletions templates/forgejo/runner-systemd-wrapper.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash

XDG_RUNTIME_DIR=/run/user/$(id -u)
export XDG_RUNTIME_DIR

PATH="${PATH}:/usr/libexec/"

cp /opt/forgejo-runner/libexec/runner-wrapper ${HOME}/
cp /opt/forgejo-runner/bin/forgejo-runner ${HOME}/


if [ ! -f "/home/$(whoami)/.runner" ]; then
# Registration needed
/opt/forgejo-runner/bin/forgejo-runner --config "/home/$(whoami)/runner.config" register --no-interactive --instance "https://<%= @forgejo_instance %>" --token "<%= @registration_token %>" --name "$(hostname -f):$(whoami)"
fi

podman machine rm -f

while true; do

podman machine init --image "docker://<%= @machine_image %>" --memory 4096 --rootful --now
podman machine ssh "/home/$(whoami)/runner-wrapper $(whoami)"
podman machine rm -f

sleep 1
done
17 changes: 17 additions & 0 deletions templates/forgejo/runner-wrapper.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

while true; do
/home/$1/forgejo-runner --config /home/$1/runner.config one-job 2>&1 | sed '/^Error: could not fetch task/,$b;$q1'

# Match for Error: could not fetch task in sed
# aka no jobs to fetch
# Try again lateer
if [ "${PIPESTATUS[1]}" -eq "0" ]; then
sleep 5
continue
else
break
fi
done

echo "Exiting to destroy VM - BOOM"
192 changes: 192 additions & 0 deletions templates/forgejo/runner.config.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Example configuration file, it's safe to copy this as the default config file without any modification.

# You don't have to copy this file to your instance,
# just run `forgejo-runner generate-config > config.yaml` to generate a config file.

#
# The value of level or job_level can be trace, debug, info, warn, error or fatal
#
log:
#
# What is displayed in the output of the runner process but not sent
# to the Forgejo instance.
#
level: info
#
# What is sent to the Forgejo instance and therefore
# visible in the web UI for a given job.
#
job_level: info

runner:
# Where to store the registration result.
file: /home/<%= @user %>/.runner
# Execute how many tasks concurrently at the same time.
capacity: 1
# Extra environment variables to run jobs.
envs:
A_TEST_ENV_NAME_1: a_test_env_value_1
A_TEST_ENV_NAME_2: a_test_env_value_2
# Extra environment variables to run jobs from a file.
# It will be ignored if it's empty or the file doesn't exist.
env_file: .env
# The timeout for a job to be finished.
# Please note that the Forgejo instance also has a timeout (3h by default) for the job.
# So the job could be stopped by the Forgejo instance if it's timeout is shorter than this.
timeout: 3h
# The timeout for the runner to wait for running jobs to finish when
# shutting down because a TERM or INT signal has been received. Any
# running jobs that haven't finished after this timeout will be
# cancelled.
# If unset or zero the jobs will be cancelled immediately.
shutdown_timeout: 3h
# Whether skip verifying the TLS certificate of the instance.
insecure: false
# The timeout for fetching the job from the Forgejo instance.
fetch_timeout: 5s
# The interval for fetching the job from the Forgejo instance.
fetch_interval: 2s
# The interval for reporting the job status and logs to the Forgejo instance.
report_interval: 1s
# The labels of a runner are used to determine which jobs the runner can run, and how to run them.
# Like: ["macos-arm64:host", "ubuntu-latest:docker://node:20-bookworm", "ubuntu-22.04:docker://node:20-bookworm"]
# If it's empty when registering, it will ask for inputting labels.
# If it's empty when executing the `daemon`, it will use labels in the `.runner` file.
labels: []

cache:
#
# When enabled, workflows will be given the ACTIONS_CACHE_URL environment variable
# used by the https://code.forgejo.org/actions/cache action. The server at this
# URL must implement a compliant REST API and it must also be reachable from
# the container or host running the workflows.
#
# See also https://forgejo.org/docs/next/user/actions/advanced-features/#cache
#
# When it is not enabled, none of the following options apply.
#
# It works as follows:
#
# - the workflow is given a one time use ACTIONS_CACHE_URL
# - a cache proxy listens to ACTIONS_CACHE_URL
# - the cache proxy securely communicates with the cache server using
# a shared secret
#
enabled: true
#
#######################################################################
#
# Only used for the internal cache server.
#
# If external_server is not set, the Forgejo runner will spawn a
# cache server that will be used by the cache proxy.
#
#######################################################################
#
# The port bound by the internal cache server.
# 0 means to use a random available port.
#
port: 0
#
# The directory to store the cache data.
#
# If empty, the cache data will be stored in $HOME/.cache/actcache.
#
dir: ""
#
#######################################################################
#
# Only used for the external cache server.
#
# If external_server is set, the internal cache server is not
# spawned.
#
#######################################################################
#
# The URL of the cache server. The URL should generally end with
# "/". The cache proxy will forward requests to the external
# server. The requests are authenticated with the "secret" that is
# shared with the external server.
#
external_server: ""
#
# The shared cache secret used to secure the communications between
# the cache proxy and the cache server.
#
# If empty, it will be generated to a new secret automatically when
# the server starts and it will stay the same until it restarts.
#
secret: ""
#
#######################################################################
#
# Common to the internal and external cache server
#
#######################################################################
#
# The IP or hostname (195.84.20.30 or example.com) to use when constructing
# ACTIONS_CACHE_URL which is the URL of the cache proxy.
#
# If empty it will be detected automatically.
#
# If the containers or host running the workflows reside on a
# different network than the Forgejo runner (for instance when the
# docker server used to create containers is not running on the same
# host as the Forgejo runner), it may be impossible to figure that
# out automatically. In that case you can specify which IP or
# hostname to use to reach the internal cache server created by the
# Forgejo runner.
#
host: ""
#
# The port bound by the internal cache proxy.
# 0 means to use a random available port.
#
proxy_port: 0
#
# Overrides the ACTIONS_CACHE_URL variable passed to workflow
# containers. The URL should generally not end with "/". This should only
# be used if the runner host is not reachable from the workflow containers,
# and requires further setup.
#
actions_cache_url_override: ""

container:
# Specifies the network to which the container will connect.
# Could be host, bridge or the name of a custom network.
# If it's empty, create a network automatically.
network: ""
# Whether to create networks with IPv6 enabled. Requires the Docker daemon to be set up accordingly.
# Only takes effect if "network" is set to "".
enable_ipv6: false
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
privileged: false
# And other options to be used when the container is started (eg, --volume /etc/ssl/certs:/etc/ssl/certs:ro).
options:
# The parent directory of a job's working directory.
# If it's empty, /workspace will be used.
workdir_parent:
# Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
# You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
# For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
# valid_volumes:
# - data
# - /etc/ssl/certs
# If you want to allow any volume, please use the following configuration:
# valid_volumes:
# - '**'
valid_volumes: []
# overrides the docker client host with the specified one.
# If "-" or "", an available docker host will automatically be found.
# If "automount", an available docker host will automatically be found and mounted in the job container (e.g. /var/run/docker.sock).
# Otherwise the specified docker host will be used and an error will be returned if it doesn't work.
docker_host: "-"
# Pull docker image(s) even if already present
force_pull: false
# Rebuild local docker image(s) even if already present
force_rebuild: false

host:
# The parent directory of a job's working directory.
# If it's empty, $HOME/.cache/act/ will be used.
workdir_parent:
Loading