forked from lab-in-a-box/liab-deployments
The concrete need was to enable a runner deployment on the same host as a forgejo deployment, but the VM network setup did not allow podman's default setup to reach the host's service from the container. This change allows for adding arbitrary podman options. The can also be given as host defaults for multiple deployments.
204 lines
5.8 KiB
Python
204 lines
5.8 KiB
Python
#
|
|
# Deploy Forgejo runner (https://forgejo.org/docs/latest/admin/actions/runner-installation)
|
|
#
|
|
# This setup is based on the "offline registration" approach described
|
|
# in the forgejo docs. This creates a registration using a known 40-char
|
|
# secret that is used, both, at the forgejo site _and_ the runner.
|
|
# The example below, e.g., requires that the following command was executed
|
|
# at the respective forgejo site in order to make the registration possible.#
|
|
#
|
|
# [podman exec forgejo] forgejo forgejo-cli actions register --name <name> --secret <40char-random-hex-encoded>
|
|
#
|
|
# An example inventory may look like this:
|
|
#
|
|
# group = [
|
|
# (
|
|
# 'edu.datalad.org',
|
|
# {
|
|
# 'forgejo_runner': {
|
|
# # some settings support global defaults for homogeneity
|
|
# 'defaults': {
|
|
# 'user': ('forgejo-runner', '3000'),
|
|
# 'runner_site_name': 'edu',
|
|
# 'container_tag': 'data.forgejo.org/forgejo/runner:11',
|
|
# 'podman_network_opts': '--add-host 'host.containers.internal:xxx.xxx.xxx.xxx',
|
|
# },
|
|
# 'runners': [
|
|
# {
|
|
# 'name': 'git.example.org',
|
|
# 'forgejo_instance': 'https://git.example.org',
|
|
# 'config_file_asset': 'assets/example-forgejo-runner-config.yaml',
|
|
# 'secret': 'cf67f6e55b9c39256f30dca65af3657552eb65b3',
|
|
# # global scope when none is defined
|
|
# 'scope': 'exampleorg',
|
|
# #'runner_runtime_dir': '/bigdrive/runner',
|
|
# },
|
|
# ],
|
|
# },
|
|
# ...
|
|
#
|
|
from pyinfra.api import deploy
|
|
from pyinfra import (
|
|
host,
|
|
)
|
|
from pyinfra.operations import (
|
|
files,
|
|
server,
|
|
)
|
|
from liab_deployments.operations import (
|
|
user,
|
|
user_systemd,
|
|
)
|
|
|
|
|
|
service_unit_tmpl = """\
|
|
[Unit]
|
|
Description=Podman Forgejo runner service
|
|
Wants=network-online.target
|
|
After=network-online.target
|
|
RequiresMountsFor=%t/containers
|
|
|
|
[Service]
|
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
|
Restart=always
|
|
TimeoutStopSec=70
|
|
ExecStartPre=/bin/rm \\
|
|
-f %t/%n.ctr-id
|
|
ExecStart=/usr/bin/podman run \\
|
|
--cidfile=%t/%n.ctr-id \\
|
|
--cgroups=no-conmon \\
|
|
--rm \\
|
|
--sdnotify=conmon \\
|
|
--replace \\
|
|
--name {name} \\
|
|
--detach \\
|
|
--user 0:0 \\
|
|
-v {runner_runtime_dir}:/data \\
|
|
-v /run/user/{uid}/podman/podman.sock:/var/run/docker.sock:rw \\
|
|
-e DOCKER_HOST=unix:///var/run/docker.sock \\
|
|
{podman_network_opts} \\
|
|
{container_tag} \\
|
|
forgejo-runner \\
|
|
-c /data/config/config.yml \\
|
|
daemon
|
|
ExecStop=/usr/bin/podman stop \\
|
|
--ignore -t 10 \\
|
|
--cidfile=%t/%n.ctr-id
|
|
ExecStopPost=/usr/bin/podman rm \\
|
|
-f \
|
|
--ignore -t 10 \
|
|
--cidfile=%t/%n.ctr-id
|
|
Type=notify
|
|
NotifyAccess=all
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
"""
|
|
|
|
|
|
@deploy("Deploy Forgejo runner")
|
|
def deploy_runners():
|
|
if not hasattr(host.data, 'forgejo_runner'):
|
|
return
|
|
defaults = host.data.forgejo_runner.get('defaults', {})
|
|
for spec in host.data.forgejo_runner.get('runners', []):
|
|
_deploy_runner(
|
|
spec['container_tag']
|
|
if 'container_tag' in spec
|
|
else defaults['container_tag'],
|
|
spec['user']
|
|
if 'user' in spec
|
|
else defaults['user'],
|
|
spec['config_file_asset']
|
|
if 'config_file_asset' in spec
|
|
else defaults['config_file_asset'],
|
|
spec['secret'],
|
|
spec['forgejo_instance'],
|
|
name=spec.get('name', 'forgejo_runner'),
|
|
scope=spec.get('scope', None),
|
|
runner_site_name=spec['runner_site_name']
|
|
if 'runner_site_name' in spec
|
|
else defaults.get('runner_site_name', None),
|
|
podman_network_opts=spec.get(
|
|
'podman_network_opts',
|
|
defaults.get('podman_network_opts')),
|
|
)
|
|
|
|
|
|
def _deploy_runner(
|
|
container_tag: str,
|
|
user_spec: tuple[str, int],
|
|
config_file_asset,
|
|
secret: str,
|
|
forgejo_instance,
|
|
*,
|
|
name,
|
|
runner_runtime_dir=None,
|
|
scope=None,
|
|
runner_site_name=None,
|
|
podman_network_opts: str | None = None,
|
|
):
|
|
user_name, uid = user_spec
|
|
user_home = f'/home/{user_name}'
|
|
runner_runtime_dir = runner_runtime_dir or f'{user_home}/{name}'
|
|
runner_site_name = runner_site_name or 'runner'
|
|
|
|
user.systemd_service(
|
|
user_name,
|
|
uid,
|
|
user_home,
|
|
)
|
|
|
|
files.directory(
|
|
path=runner_runtime_dir,
|
|
present=True,
|
|
_sudo_user=user_name,
|
|
)
|
|
|
|
user_systemd.service_unit(
|
|
user_name,
|
|
user_home,
|
|
name,
|
|
service_unit_tmpl.format(
|
|
name=name,
|
|
user_home=user_home,
|
|
uid=uid,
|
|
runner_runtime_dir=runner_runtime_dir,
|
|
container_tag=container_tag,
|
|
podman_network_opts=podman_network_opts or '',
|
|
),
|
|
)
|
|
|
|
files.put(
|
|
name=f'Forgejo runner {name!r} config',
|
|
src=config_file_asset,
|
|
dest=f'{runner_runtime_dir}/config/config.yml',
|
|
_sudo_user=user_name,
|
|
)
|
|
|
|
user_systemd.run_service(
|
|
user_name,
|
|
uid,
|
|
'podman.socket',
|
|
)
|
|
|
|
server.shell(
|
|
name=f"Register {name!r}",
|
|
commands=[
|
|
'podman run --rm --user 0:0'
|
|
f' -v "{runner_runtime_dir}:/data"'
|
|
f' {container_tag}'
|
|
f' forgejo-runner create-runner-file'
|
|
f' --instance "{forgejo_instance}"'
|
|
f' --name "{runner_site_name}"'
|
|
f' --secret "{secret}"'
|
|
],
|
|
_chdir=user_home,
|
|
_sudo_user=user_name,
|
|
)
|
|
|
|
user_systemd.run_service(
|
|
user_name,
|
|
uid,
|
|
name,
|
|
)
|