Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
4fdd177
Intitial commit of orchestrator
theseal Mar 27, 2026
4f71a37
Wrapper
theseal Mar 30, 2026
ffb5bda
Executed on server
theseal Mar 30, 2026
1faa7ec
New home
theseal Mar 30, 2026
0e1e492
Wrap in systemd
theseal Mar 30, 2026
7b0ff6f
Wrong path
theseal Mar 30, 2026
747930a
Nameing of runner
theseal Mar 30, 2026
6e21e21
Trace origin
theseal Mar 30, 2026
4c336d2
Wrong name
theseal Mar 30, 2026
8abe395
Syntax error
theseal Mar 30, 2026
e11b155
Runners need configuration
theseal Mar 30, 2026
3d691a2
Always forgetting to add new files…
theseal Mar 30, 2026
704a370
Put config in place
theseal Mar 30, 2026
20360b1
More files to handle
theseal Mar 30, 2026
650ea67
Easier to understand full paths
theseal Mar 30, 2026
6f8baf1
Put clouds.yaml in to place
theseal Mar 30, 2026
446bc2c
Puppet-lint
theseal Mar 30, 2026
fefdaad
Same command
theseal Mar 30, 2026
175cdbe
We need access some files
theseal Mar 30, 2026
3cdef28
Scoping
theseal Mar 30, 2026
d2e5fcc
Variable key
theseal Mar 30, 2026
5e96f4b
Create and expose key
theseal Mar 30, 2026
6369fca
Variable network
theseal Mar 30, 2026
569b195
Binary needed
theseal Mar 30, 2026
1ccfcc7
Read only
theseal Mar 30, 2026
f902862
Wrong direction
theseal Mar 30, 2026
664a835
Docker didn't like links
theseal Mar 30, 2026
07fa3a9
We need to be very specific
theseal Apr 2, 2026
e98fb45
Protocol required
theseal Apr 2, 2026
a931f96
Expose native execution aswell
theseal Apr 2, 2026
cf69b50
Don't registrer more then once per runner
theseal Apr 2, 2026
f090165
We do need no know which runner to run
theseal Apr 2, 2026
18a3685
Credentials
theseal Apr 2, 2026
b2eb614
Same path as we try to scp
theseal Apr 2, 2026
38f1a0b
Possibilty to override image
theseal Apr 7, 2026
9814f61
Tool to gain ssh access
theseal Apr 7, 2026
a92a412
Fixed version
theseal Apr 7, 2026
920f0fa
Bump runner
theseal May 7, 2026
5e6b59c
New feature to wait for jobs
theseal May 7, 2026
f35753a
Real ephemeral runners
theseal May 8, 2026
8b082e9
Bad declaration
theseal May 8, 2026
6daaec5
Fail if not a good response
theseal May 8, 2026
9d2b972
Exit bad if required key doesn't exist
theseal May 8, 2026
2ce7f3b
We need to expand the variables
theseal May 8, 2026
d93048b
Syntax error
theseal May 8, 2026
43cc279
Wrong path
theseal May 8, 2026
1ee1ac3
We need some tools
theseal May 8, 2026
e97533e
Forgot install
theseal May 8, 2026
db8e47f
A proper name
theseal May 8, 2026
9837f24
Assume all tools are available if we have one
theseal May 8, 2026
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
16 changes: 16 additions & 0 deletions files/forgejo/forgejo-runner-2.0.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[Unit]
Description=forgejo-runner as %i
After=network.target

[Service]
Type=simple
TimeoutStartSec=0
TimeoutStopSec=30
Restart=always
RestartSec=10
Environment="INT=%i"
ExecStart=/opt/forgejo-runner-orchestrator/libexec/runner-orchestrator
Restart=on-failure

[Install]
WantedBy=multi-user.target
194 changes: 194 additions & 0 deletions files/forgejo/runner-2.0.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# 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/ubuntu/.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:
- "ubuntu24:host"
- "docker"

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: true
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
privileged: true
# 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: "automount"
# 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:
104 changes: 104 additions & 0 deletions manifests/forgejo/runner/orchestrator.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# A class to install and manage Forgejo runner(s)
class sunet::forgejo::runner::orchestrator (
String $version = '12.10.1',
String $version_sha256sum = '9e0378b4a22b95da98b350cfcb1b3844c1929a354a961f1ca7b49122b42abcc1',
Integer $runners = 4,
String $forgejo_instance = 'platform.sunet.se',
String $forgejo_prefix = 'runner',
String $openstack_key_name = 'dirigenten',
String $openstack_network = 'public',
String $dirigenten_version = 'latest',
Optional[String] $runner_image = undef,
) {

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

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

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

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

# Generate SSH-key used to access DB nodes
$key_path = '/opt/forgejo-runner-orchestrator/config/id_ed25519'
if lookup('forgejo_runner_ssh_key', undef, undef, undef) {
ensure_resource('sunet::snippets::secret_file', $key_path, {
hiera_key => 'forgejo_runner_ssh_key',
})
} else {
if (!find_file($key_path)){
sunet::snippets::ssh_keygen{$key_path:} # This will not overwrite an existing key
}
}

file { '/opt/forgejo-runner-orchestrator/config/runner.config':
ensure => 'file',
content => file('sunet/forgejo/runner-2.0.config'),
}

$clouds = lookup('clouds', undef, undef, {})
file { '/opt/forgejo-runner-orchestrator/config/clouds.yaml':
ensure => file,
content => to_yaml({'clouds' => $clouds}),
}


file { "/opt/forgejo-runner-orchestrator/bin/forgejo-runner-${version}":
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-orchestrator/bin/forgejo-runner':
ensure => link,
target => "/opt/forgejo-runner-orchestrator/bin/forgejo-runner-${version}",
}

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

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

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

file { '/usr/local/bin/runnerctl':
ensure => 'file',
content => template('sunet/forgejo/runnerctl.erb'),
mode => '0755',
}

file { '/etc/systemd/system/sunet-forgejo-runner-orchestrator@.service':
ensure => 'file',
content => file('sunet/forgejo/forgejo-runner-2.0.service'),
mode => '0644',
}

range(0, $runners - 1).each |$runner|{

service { "sunet-forgejo-runner-orchestrator@runner-${runner}":
ensure => 'running',
enable => true,
}
}
}
30 changes: 30 additions & 0 deletions templates/forgejo/runner-orchestrator.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash

set -eu

RUNNER=/opt/forgejo-runner-orchestrator/bin/forgejo-runner
ME=$(hostname -f)

docker run --rm \
--name "dirigent-${INT}" \
-v /opt/forgejo-runner-orchestrator/config/runner.config:/runner.config:ro \
-v /opt/forgejo-runner-orchestrator/libexec/runner-wrapper:/wrapper:ro \
-v /opt/forgejo-runner-orchestrator/config/clouds.yaml:/etc/openstack/clouds.yaml:ro \
-v /opt/forgejo-runner-orchestrator/config/id_ed25519:/id_ed25519:ro \
-v /opt/forgejo-runner-orchestrator/libexec/runner-registration:/runner-registration:ro \
-v $(realpath /opt/forgejo-runner-orchestrator/bin/forgejo-runner):/forgejo-runner:ro \
-e DIRIGENTEN_HOSTNAME=${ME} \
platform.sunet.se/platform/dirigenten:<%= @dirigenten_version %> \
--network <%= @openstack_network %> \
--prefix <%= @forgejo_prefix %>-${INT} \
--key-name <%= @openstack_key_name %> \
--pre-exec /runner-registration \
--ssh-key /id_ed25519 \
--ssh-host $(hostname -f) \
--scp /wrapper:/home/ubuntu/wrapper \
--scp /runner.config:/home/ubuntu/runner.config \
--scp /tmp/uuid:/tmp/non-secret-uuid.txt \
--scp /tmp/token:/tmp/secret-token.txt \
--scp /forgejo-runner:/home/ubuntu/forgejo-runner \
<% if @runner_image -%>--image <%= @runner_image %> \<%- end -%>
/home/ubuntu/wrapper
32 changes: 32 additions & 0 deletions templates/forgejo/runner-registration.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash

set -eu -o pipefail

if [[ ! -f /usr/bin/curl ]]; then
apt-get update
apt-get -y install \
curl \
jq
fi

# Clean up if anything would be left from previous run
for file in uuid token; do
file_path="/tmp/${file}"
if [[ -f "${file_path}" ]]; then
rm "${file_path}"
fi
done

response=$(curl -f -X 'POST' \
'https://<%= @forgejo_instance %>/api/v1/admin/actions/runners' \
-H 'accept: application/json' \
-H 'Authorization: Bearer <%= @forgejo_registration_token %>' \
-H 'Content-Type: application/json' \
-d "{
\"name\": \"${DIRIGENTEN_HOSTNAME}-${DIRIGENTEN_PREFIX}-$(date +%s)\",
\"ephemeral\": true
}")

for key in uuid token; do
echo "${response}" | jq -re --arg k "$key" '.[$k]' > "/tmp/${key}"
done
12 changes: 12 additions & 0 deletions templates/forgejo/runner-wrapper.2.0.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash

export RUNNER_TOOL_CACHE=/opt/hostedtoolcache

RUNNER=/home/ubuntu/forgejo-runner
${RUNNER} --config ./runner.config one-job \
--url "https://<%= @forgejo_instance %>" \
--uuid "$(cat /tmp/non-secret-uuid.txt)" \
--token-url file:///tmp/secret-token.txt \
--wait

echo "Exiting to destroy VM - BOOM"
Loading
Loading