mirror of
https://codeberg.org/pierreprinetti/forgejo-hetzner-runner.git
synced 2025-06-27 16:25:53 +00:00
First commit
This commit is contained in:
commit
b6fc7d59b3
5 changed files with 232 additions and 0 deletions
53
README.md
Normal file
53
README.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# forgejo-hetzer-runner
|
||||||
|
|
||||||
|
Spawn a new Forgejo runner on Hetzner infrastructure.
|
||||||
|
|
||||||
|
**Requirements:**
|
||||||
|
* [`jq`](https://jqlang.github.io/jq)
|
||||||
|
|
||||||
|
**Required environment variables:**
|
||||||
|
* `HETZNER_API_TOKEN`: A Hetzner token valid for operating servers
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
**To stand up a runner:**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./runner-up.sh -r <runner_token>
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid root login with password by passing your SSH key ID on server creation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./runner-up.sh -s <ssh_key id> -r <runner_token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Delete the server(s):**
|
||||||
|
```shell
|
||||||
|
./runner-get.sh | jq '.servers[].id' | xargs -r -n1 ./runner-down.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Log in:**
|
||||||
|
```shell
|
||||||
|
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "runner@$(./runner-get.sh | jq -j '.servers[0].public_net.ipv4.ip')"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fetching a registration token
|
||||||
|
|
||||||
|
The `FORGEJO_TOKEN` must be manually retrieved from the web interface. Note that each token is only valid for registering one runner.
|
||||||
|
|
||||||
|
* Retrieve a token to register a runner for your organization: `https://codeberg.org/org/${organization_name}/settings/runners`
|
||||||
|
* Retrieve a token to register a runner for one repository: `https://codeberg.org/${user_or_organization_name}/${repository_name}/settings/runners`
|
||||||
|
|
||||||
|
This issue tracks the addition of an API endpoint to fetch registration tokens in Forgejo: https://codeberg.org/forgejo/forgejo/issues/1030
|
||||||
|
|
||||||
|
## Additional notes
|
||||||
|
|
||||||
|
The server has to have an IPv4 interface. Otherwise:
|
||||||
|
* fetching `forgejo-runner` fails because `code.forgejo.org` is IPv4-only
|
||||||
|
* fetching default Docker base images fails because `docker.io` is IPv4-only
|
||||||
|
* your CI steps might involve communicating with an IPv4-only machine.
|
||||||
|
|
||||||
|
## External references
|
||||||
|
|
||||||
|
* [Hetzner cloud API reference](https://docs.hetzner.cloud/)
|
10
runner-down.sh
Executable file
10
runner-down.sh
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
declare -r id="$1"
|
||||||
|
|
||||||
|
curl -isS \
|
||||||
|
-X DELETE \
|
||||||
|
-H "Authorization: Bearer ${HETZNER_API_TOKEN}" \
|
||||||
|
"https://api.hetzner.cloud/v1/servers/${id}"
|
9
runner-get.sh
Executable file
9
runner-get.sh
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
curl -sS \
|
||||||
|
-X GET \
|
||||||
|
-H "Authorization: Bearer ${HETZNER_API_TOKEN}" \
|
||||||
|
--url-query 'label_selector=role==runner' \
|
||||||
|
'https://api.hetzner.cloud/v1/servers'
|
111
runner-up.sh
Executable file
111
runner-up.sh
Executable file
|
@ -0,0 +1,111 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
random_string() {
|
||||||
|
declare -r length="$1"
|
||||||
|
tr -dc a-z </dev/random \
|
||||||
|
| head -c "$length"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare \
|
||||||
|
image='debian-12' \
|
||||||
|
location='fsn1' \
|
||||||
|
name="runner-$(random_string 5)" \
|
||||||
|
server_type='cx11' \
|
||||||
|
cloud_init_path='runner.cloud-init.yaml' \
|
||||||
|
ssh_key_id='' \
|
||||||
|
runner_token="${FORGEJO_TOKEN:-}" \
|
||||||
|
|
||||||
|
while getopts "hi:l:n:t:c:r:s:" arg; do
|
||||||
|
case $arg in
|
||||||
|
h)
|
||||||
|
echo 'runner-up'
|
||||||
|
echo 'https://codeberg.org/pierreprinetti/forgejo-hetzner-runner'
|
||||||
|
echo
|
||||||
|
echo -e 'Usage:'
|
||||||
|
echo -e "\t-i: image (default: '${image}')"
|
||||||
|
echo -e "\t-l: location (default: '${location}')"
|
||||||
|
echo -e "\t-n: name (default: '${name}')"
|
||||||
|
echo -e "\t-t: type (default: '${server_type}')"
|
||||||
|
echo -e "\t-c: cloud-init path (default: '${cloud_init_path}')"
|
||||||
|
echo -e "\t-r: Forgejo runner token (default: '${runner_token}')"
|
||||||
|
echo -e "\t-s: SSH key ID (default: none)"
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
i)
|
||||||
|
image=$OPTARG
|
||||||
|
;;
|
||||||
|
l)
|
||||||
|
location=$OPTARG
|
||||||
|
;;
|
||||||
|
n)
|
||||||
|
name=$OPTARG
|
||||||
|
;;
|
||||||
|
t)
|
||||||
|
server_type=$OPTARG
|
||||||
|
;;
|
||||||
|
c)
|
||||||
|
cloud_init_path=$OPTARG
|
||||||
|
;;
|
||||||
|
r)
|
||||||
|
runner_token=$OPTARG
|
||||||
|
;;
|
||||||
|
s)
|
||||||
|
ssh_key_id=$OPTARG
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
readonly \
|
||||||
|
image \
|
||||||
|
location \
|
||||||
|
name \
|
||||||
|
server_type \
|
||||||
|
cloud_init_path \
|
||||||
|
runner_token \
|
||||||
|
ssh_key_id
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
if [[ -z "$runner_token" ]]; then
|
||||||
|
echo 'Set a runner token'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare authorized_ssh_key
|
||||||
|
declare data
|
||||||
|
data="$(jq -c \
|
||||||
|
--arg image "$image" \
|
||||||
|
--arg location "$location" \
|
||||||
|
--arg name "$name" \
|
||||||
|
--arg server_type "$server_type" \
|
||||||
|
'.
|
||||||
|
| .image=$image
|
||||||
|
| .location=$location
|
||||||
|
| .name=$name
|
||||||
|
| .server_type=$server_type
|
||||||
|
| .labels.role="runner"
|
||||||
|
' <<< '{}')"
|
||||||
|
if [[ -n $ssh_key_id ]]; then
|
||||||
|
data="$(jq -c --argjson ssh_key_id "$ssh_key_id" '.ssh_keys=[$ssh_key_id]' <<< "$data")"
|
||||||
|
authorized_ssh_key="$(curl -sS -X GET \
|
||||||
|
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
|
||||||
|
"https://api.hetzner.cloud/v1/ssh_keys/${ssh_key_id}" \
|
||||||
|
| jq -r '.ssh_key.public_key')"
|
||||||
|
fi
|
||||||
|
readonly authorized_ssh_key
|
||||||
|
|
||||||
|
export runner_token
|
||||||
|
export authorized_ssh_key
|
||||||
|
data="$(jq -c --arg cloud_init "$(envsubst '$authorized_ssh_key $runner_token' < "$cloud_init_path")" '.user_data=$cloud_init' <<< "$data")"
|
||||||
|
readonly data
|
||||||
|
|
||||||
|
curl -isS \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer ${HETZNER_API_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$data" \
|
||||||
|
'https://api.hetzner.cloud/v1/servers'
|
49
runner.cloud-init.yaml
Normal file
49
runner.cloud-init.yaml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#cloud-config
|
||||||
|
package_update: true
|
||||||
|
package_upgrade: true
|
||||||
|
packages:
|
||||||
|
- apparmor
|
||||||
|
- docker.io
|
||||||
|
runcmd:
|
||||||
|
- sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
|
||||||
|
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
|
||||||
|
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
|
||||||
|
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config
|
||||||
|
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
|
||||||
|
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
|
||||||
|
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
|
||||||
|
- sed -i '$a AllowUsers runner' /etc/ssh/sshd_config
|
||||||
|
- curl -sSL https://code.forgejo.org/forgejo/runner/releases/download/v2.1.0/forgejo-runner-amd64 > /usr/bin/forgejo-runner
|
||||||
|
- echo 'f0dab69994fcdc3d35ef34b59ff3cff6f44a70112a6e7125f1fb7949d879e02e2a2d1d0a3ac8732b2bae7e47bfb7358a8fa5f409fe4d85e48c4e69b0c38c8e43 /usr/bin/forgejo-runner' | sha512sum -c && chmod +x /usr/bin/forgejo-runner
|
||||||
|
- mkdir -p /etc/runner
|
||||||
|
- cd /etc/runner && /usr/bin/forgejo-runner register --no-interactive --token ${runner_token} --name runner --instance https://codeberg.org --labels docker:docker://node:16-bullseye
|
||||||
|
- /usr/bin/forgejo-runner generate-config > /etc/runner/config.yml
|
||||||
|
- chown -R runner:runner /etc/runner
|
||||||
|
- |
|
||||||
|
cat > /etc/systemd/system/runner.service <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Forgejo runner
|
||||||
|
Wants=network.target
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=runner
|
||||||
|
Group=runner
|
||||||
|
WorkingDirectory=/etc/runner
|
||||||
|
ExecStart=/usr/bin/forgejo-runner daemon
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
- systemctl enable runner.service
|
||||||
|
- reboot
|
||||||
|
users:
|
||||||
|
- groups: users, admin, docker
|
||||||
|
name: runner
|
||||||
|
shell: /bin/bash
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- ${authorized_ssh_key}
|
||||||
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
Loading…
Add table
Add a link
Reference in a new issue