#!/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_RUNNER_VERSION:=6.2.0} : ${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 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 lxc-helpers.sh >/dev/null; then $SUDO curl --fail -sS -o /usr/local/bin/lxc-helpers-lib.sh https://code.forgejo.org/forgejo/lxc-helpers/raw/branch/main/lxc-helpers-lib.sh $SUDO curl --fail -sS -o /usr/local/bin/lxc-helpers.sh https://code.forgejo.org/forgejo/lxc-helpers/raw/branch/main/lxc-helpers.sh $SUDO chmod +x /usr/local/bin/lxc-helpers*.sh fi if ! which lxc-ls >/dev/null; then $SUDO lxc-helpers.sh lxc_install_lxc_inside $LXC_IPV4_PREFIX $LXC_IPV6_PREFIX 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 if ! cmp $SELF /usr/local/bin/$SELF_FILENAME; then cp -a $SELF /usr/local/bin/$SELF_FILENAME 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 } # # 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}"