#!/bin/bash # Copyright Forgejo Authors. # SPDX-License-Identifier: MIT set -o pipefail : ${TMPDIR:=$(mktemp -d)} export -n TMPDIR if ! test -d "$TMPDIR"; then echo "TMPDIR=$TMPDIR is expected to be a directory" exit 1 fi trap "rm -fr $TMPDIR" EXIT : ${INPUTS_LXC_CONFIG:=docker libvirt lxc} : ${INPUTS_SERIAL:=} : ${INPUTS_TOKEN:=} : ${INPUTS_FORGEJO:=https://code.forgejo.org} : ${INPUTS_LIFETIME:=7d} : ${INPUTS_LXC_HELPERS_VERSION:=1.0.3} : ${INPUTS_RUNNER_VERSION:=6.2.1} : ${KILL_AFTER:=21600} # 6h == 21600 NODEJS_VERSION=20 DEBIAN_RELEASE=bookworm YQ_VERSION=v4.45.1 SELF=${BASH_SOURCE[0]} SELF_FILENAME=$(basename "$SELF") ETC=/etc/forgejo-runner LIB=/var/lib/forgejo-runner LOG=/var/log/forgejo-runner LOCK=/var/lock/forgejo-runner : ${HOST:=$(hostname)} LXC_IPV4_PREFIX="10.105.7" LXC_IPV6_PREFIX="fd91" LXC_USER_NAME=debian LXC_USER_ID=1000 if ${VERBOSE:-false}; then set -ex PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: ' # export LXC_VERBOSE=true # use with caution, it will block .forgejo/workflows/example-lxc-systemd.yml else set -e fi if test $(id -u) != 0; then SUDO=sudo fi function config_inotify() { if grep --quiet fs.inotify.max_user_instances=8192 /etc/sysctl.conf; then return fi echo fs.inotify.max_user_instances=8192 | $SUDO tee -a /etc/sysctl.conf $SUDO sysctl -p } function install_or_update_lxc_helpers() { for lxc_helper in lxc-helpers.sh lxc-helpers-lib.sh; do local new=$TMPDIR/$lxc_helper local existing=/usr/local/bin/$lxc_helper curl --fail -sS -o $new https://code.forgejo.org/forgejo/lxc-helpers/raw/tag/v${INPUTS_LXC_HELPERS_VERSION}/$lxc_helper if ! test -f $existing || ! cmp --quiet $existing $new; then if test -f $existing; then $SUDO mv $existing $existing.backup fi $SUDO mv $new $existing $SUDO chmod +x $existing fi done } function install_or_update_self() { local bin=/usr/local/bin/$SELF_FILENAME if ! cmp --quiet $SELF $bin; then if test -f $bin; then $SUDO mv $bin $bin.backup fi $SUDO cp -a $SELF $bin fi } function install_self() { install_or_update_self } function dependencies() { if ! which curl jq retry >/dev/null; then export DEBIAN_FRONTEND=noninteractive $SUDO apt-get update -qq $SUDO apt-get install -y -qq curl jq retry fi if ! which yq >/dev/null; then $SUDO curl -L --fail -sS -o /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_arm64 $SUDO chmod +x /usr/local/bin/yq fi install_self install_or_update_lxc_helpers if ! which lxc-ls >/dev/null; then $SUDO lxc-helpers.sh lxc_install_lxc_inside $LXC_IPV4_PREFIX $LXC_IPV6_PREFIX fi } function lxc_name() { echo runner-${INPUTS_SERIAL}-lxc } function lxc_destroy() { $SUDO lxc-destroy -f $(lxc_name) >/dev/null || true } function lxc_create() { local name=$(lxc_name) local lib=$LIB/$name local etc=$ETC/$INPUTS_SERIAL lxc-helpers.sh --config "$INPUTS_LXC_CONFIG" lxc_container_create $name echo "lxc.start.auto = 1" | sudo tee -a /var/lib/lxc/$name/config local bin=/var/lib/lxc/$name/rootfs/usr/local/bin $SUDO cp -a $SELF $bin/$SELF_FILENAME $SUDO cp -a /usr/local/bin/forgejo-runner-$INPUTS_RUNNER_VERSION $bin/forgejo-runner $SUDO cp -a /usr/local/bin/yq $bin/yq $SUDO cp -a $(which jq) $bin/jq $SUDO mkdir -p $lib/.cache/actcache $SUDO chown -R $LXC_USER_ID $lib lxc-helpers.sh lxc_container_mount $name $lib/.cache/actcache $SUDO mkdir -p $etc $SUDO chown -R $LXC_USER_ID $etc lxc-helpers.sh lxc_container_mount $name $etc lxc-helpers.sh lxc_container_start $name if echo $INPUTS_LXC_CONFIG | grep --quiet 'docker'; then lxc-helpers.sh lxc_install_docker $name fi if echo $INPUTS_LXC_CONFIG | grep --quiet 'lxc'; then local ipv4="10.48.$INPUTS_SERIAL" local ipv6="fd$INPUTS_SERIAL" lxc-helpers.sh lxc_install_lxc $name $ipv4 $ipv6 fi lxc-helpers.sh lxc_container_user_install $name $LXC_USER_ID $LXC_USER_NAME } function service_create() { cat >$TMPDIR/forgejo-runner@.service <$etc/config fi $SUDO mkdir -p $LIB/$(lxc_name)/.cache/actcache } function ensure_configuration_and_registration() { local etc=$ETC/$INPUTS_SERIAL if ! test -f $etc/config.yml; then forgejo-runner generate-config >$etc/config.yml cat >$TMPDIR/edit-config <$TMPDIR/edit-config <$etc/env <&$log } function kill_runner() { cd $ETC/$INPUTS_SERIAL rm -f killed-* started-running set +e pkill --exact forgejo-runner if test $? = 1; then touch killed-already return fi timeout $KILL_AFTER pidwait --exact forgejo-runner status=$? set -e # pidwait will exit 1 if the process is already gone # pidwait will exit 0 if the process terminated gracefully before the timeout if test $status = 0 || test $status = 1; then touch killed-gracefully echo "forgejo-runner stopped gracefully" else pkill --exact --signal=KILL forgejo-runner touch killed-forcefully echo "forgejo-runner stopped forcefully" fi } function stop() { inside kill_runner } function main() { config_inotify dependencies install_runner service_create lxc_create inside ensure_configuration_and_registration } function upgrade() { run_in_copy upgrade_safely "$@" } function upgrade_safely() { local version="${1:-$INPUTS_RUNNER_VERSION}" local upgrade="${2:-$TMPDIR/$SELF_FILENAME}" if ! test -f $upgrade; then curl --fail -sS -o $upgrade https://code.forgejo.org/forgejo/runner/raw/tag/v$version/examples/lxc-systemd/forgejo-runner-service.sh fi chmod +x $upgrade $upgrade install_or_update_lxc_helpers $upgrade install_or_update_self } # # ensure an update of the current script does not break a long # running function (such as `start`) by running from a copy instead # of the script itself # function run_in_copy() { if test "$#" = 0; then echo "run_in_copy needs an argument" return 1 fi export TMPDIR # otherwise it will not be removed by trap cp $SELF $TMPDIR/$SELF_FILENAME exec $TMPDIR/$SELF_FILENAME "$@" } "${@:-main}"