feat: add Gitea Actions runner (#6)

## Summary
- Adds a private runner server on the Hetzner private network (no public IP)
- NAT through the gitea server for outbound internet access via `hcloud_network_route` and iptables forwarding rules
- Runner connects to gitea over HTTPS on the private network with TLS verification disabled
- Includes Taskfile commands for runner deployment and SSH access

## Test plan
- [x] Runner registers with gitea instance
- [x] Private network connectivity verified
- [ ] Run a test workflow to confirm end-to-end CI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #6
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
This commit was merged in pull request #6.
This commit is contained in:
2026-03-16 01:40:44 +00:00
committed by Maxim Hutz
parent af5d40d84e
commit 04ca230bee
10 changed files with 312 additions and 73 deletions

View File

@@ -11,6 +11,7 @@ tasks:
deploy: ansible-playbook playbooks/deploy.yml {{.CLI_ARGS}}
destroy: ansible-playbook playbooks/destroy.yml {{.CLI_ARGS}}
restore: ansible-playbook playbooks/restore.yml {{.CLI_ARGS}}
runner: ansible-playbook playbooks/runner.yml {{.CLI_ARGS}}
assets:
- cp ./assets/icon.png ./gitea/custom/public/assets/img/logo.png
@@ -25,3 +26,10 @@ tasks:
vars:
KEY: { sh: ansible-vault view vault.yml | yq -r ".secret.private_ssh_key_path" }
IP: { sh: cat dist/terraform_outputs.yml | jq -r ".server_ip.value" }
enter-runner:
cmd: ssh -i {{.KEY}} -o ProxyCommand="ssh -i {{.KEY}} -p 2222 -W %h:%p root@{{.IP}}" root@{{.RUNNER_IP}}
vars:
KEY: { sh: ansible-vault view vault.yml | yq -r ".secret.private_ssh_key_path" }
IP: { sh: cat dist/terraform_outputs.yml | jq -r ".server_ip.value" }
RUNNER_IP: { sh: cat dist/terraform_outputs.yml | jq -r ".runner_ip.value" }

View File

@@ -91,6 +91,9 @@ DEFAULT_MERGE_STYLE = merge
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[actions]
ENABLED = true
[storage]
STORAGE_TYPE = minio
MINIO_USE_SSL = true

View File

@@ -96,6 +96,9 @@ DEFAULT_TRUST_MODEL = committer
[oauth2]
JWT_SECRET = x-----------------------------------------x
[actions]
ENABLED = true
[storage]
STORAGE_TYPE = minio
MINIO_ENDPOINT = localstack:4566

View File

@@ -108,6 +108,41 @@
- docker-buildx-plugin
- docker-compose-plugin
- name: Enable NAT for private network.
hosts: server
gather_facts: false
tasks:
- name: Enable IP forwarding.
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: "1"
sysctl_set: true
reload: true
- name: Add NAT masquerade rule.
ansible.builtin.iptables:
table: nat
chain: POSTROUTING
source: "10.0.1.0/24"
jump: MASQUERADE
state: present
- name: Allow forwarding from private network.
ansible.builtin.iptables:
chain: DOCKER-USER
source: "10.0.1.0/24"
jump: ACCEPT
action: insert
state: present
- name: Allow established/related return traffic.
ansible.builtin.iptables:
chain: DOCKER-USER
ctstate: ESTABLISHED,RELATED
jump: ACCEPT
action: insert
state: present
- name: Deploy artifact to instance.
hosts: server
tags:

131
playbooks/runner.yml Normal file
View File

@@ -0,0 +1,131 @@
- name: Set up runner host via jumphost.
gather_facts: false
hosts: localhost
vars_files:
- ../vault.yml
- ../dist/terraform_outputs.yml
tasks:
- name: Add gitea server as jumphost.
ansible.builtin.add_host:
name: server
ansible_ssh_host: "{{ server_ip.value }}"
ansible_user: root
ansible_port: 2222
ansible_private_key_file: "{{ secret.private_ssh_key_path }}"
- name: Add runner host (via jumphost).
ansible.builtin.add_host:
name: runner
ansible_ssh_host: "{{ runner_ip.value }}"
ansible_user: root
ansible_private_key_file: "{{ secret.private_ssh_key_path }}"
ansible_ssh_common_args: >-
-o ProxyCommand="ssh -i {{ secret.private_ssh_key_path }} -p 2222 -W %h:%p root@{{ server_ip.value }}"
- name: Install Docker on runner.
gather_facts: true
hosts: runner
vars_files:
- ../vault.yml
- ../dist/terraform_outputs.yml
tasks:
- name: Set DNS resolver.
ansible.builtin.copy:
content: "nameserver 185.12.64.2\n"
dest: /etc/resolv.conf
mode: "0644"
- name: Install PIP.
ansible.builtin.apt:
state: present
update_cache: true
name:
- python3-pip
- name: Install needed packages.
ansible.builtin.pip:
name:
- packaging
state: present
break_system_packages: true
- name: Download Docker repository key.
ansible.builtin.apt_key:
url: https://download.docker.com/linux/debian/gpg
state: present
- name: Download Docker repository.
ansible.builtin.apt_repository:
repo: "deb https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
state: present
- name: Remove bad packages.
ansible.builtin.apt:
state: absent
package:
- docker.io
- docker-doc
- docker-compose
- podman-docker
- containerd
- runc
- name: Download Docker dependencies.
ansible.builtin.apt:
state: present
package:
- ca-certificates
- curl
- jq
- name: Download Docker packages.
ansible.builtin.apt:
state: present
update_cache: true
package:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
- name: Register and start Gitea runner.
hosts: runner
gather_facts: false
vars_files:
- ../vault.yml
- ../dist/terraform_outputs.yml
vars:
gitea_internal_url: "https://{{ server_fqdn.value }}"
tasks:
- name: Create runner data volume.
community.docker.docker_volume:
name: runner-data
state: present
- name: Generate runner config.
ansible.builtin.copy:
dest: /root/runner-config.yaml
mode: "0644"
content: |
runner:
insecure: true
- name: Start Gitea runner container.
community.docker.docker_container:
name: gitea-runner
image: gitea/act_runner:latest
state: started
recreate: true
restart_policy: unless-stopped
etc_hosts:
"{{ server_fqdn.value }}": "10.0.1.2"
volumes:
- runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
- /root/runner-config.yaml:/config.yaml:ro
env:
GITEA_INSTANCE_URL: "{{ gitea_internal_url }}"
GITEA_RUNNER_REGISTRATION_TOKEN: "{{ secret.runner_registration_token }}"
GITEA_RUNNER_NAME: "runner-01"
CONFIG_FILE: "/config.yaml"

View File

@@ -60,3 +60,23 @@ resource "hcloud_firewall_attachment" "server_fw_attachment" {
firewall_id = hcloud_firewall.server_firewall.id
server_ids = [hcloud_server.server_instance.id]
}
resource "hcloud_server" "runner_instance" {
name = "runner-server"
image = local.server_image
server_type = local.server_type
datacenter = local.datacenter
ssh_keys = [hcloud_ssh_key.ssh_key.id]
public_net {
ipv4_enabled = false
ipv6_enabled = false
}
network {
network_id = hcloud_network.private_network.id
ip = local.runner_ip
}
depends_on = [hcloud_network_subnet.private_subnet]
}

24
terraform/network.tf Normal file
View File

@@ -0,0 +1,24 @@
resource "hcloud_network" "private_network" {
name = "repository-network"
ip_range = local.network_cidr
}
resource "hcloud_network_subnet" "private_subnet" {
network_id = hcloud_network.private_network.id
type = "cloud"
network_zone = local.network_zone
ip_range = local.subnet_cidr
}
resource "hcloud_server_network" "server_network" {
server_id = hcloud_server.server_instance.id
network_id = hcloud_network.private_network.id
ip = local.server_ip
}
resource "hcloud_network_route" "nat_route" {
network_id = hcloud_network.private_network.id
destination = "0.0.0.0/0"
gateway = local.server_ip
}

View File

@@ -1,11 +1,17 @@
output "server_ip" {
description = "The public address of the server."
value = hcloud_server.server_instance.ipv4_address
sensitive = false
value = hcloud_server.server_instance.ipv4_address
sensitive = false
}
output "server_fqdn" {
description = "The public domain of the server."
value = "${local.subdomain}.${local.domain}"
sensitive = false
value = "${local.subdomain}.${local.domain}"
sensitive = false
}
output "runner_ip" {
description = "The private network address of the runner."
value = local.runner_ip
sensitive = false
}

View File

@@ -5,6 +5,12 @@ locals {
domain = "maximhutz.com"
subdomain = "git"
network_zone = "eu-central"
network_cidr = "10.0.0.0/16"
subnet_cidr = "10.0.1.0/24"
server_ip = "10.0.1.2"
runner_ip = "10.0.1.3"
}
# ---------------------------------------------------------------------------- #

141
vault.yml
View File

@@ -1,70 +1,73 @@
$ANSIBLE_VAULT;1.1;AES256
62353135613131656461393763316639363866326663633830306532306430373638396437373064
6365353865303534366432616235373930616665306666660a396332353639633164366562666461
34393030333732326436386234626532373939613435656161306131626634313730666532386362
6130323636623233390a303631366464636561623133343334393865646639643030323732653834
39366539663463386236303531326437396439636362306639306230356265373936636132633334
62376235353261626665666161393232623564646633633538613963356565323536303137393633
31333763303163396530623939336165346433643562636566353238393265623538346563313164
37303230336261306365613132626132333463323261386536313765393164346364643036646263
39353131333561646538336130663337646139663362666332373736366236643436666265613930
38623634366665353039336330633235393039373839663938386366383834373238343237653362
66303763396463373139646638333662346538333662363234616466343634643034636363343539
31393464323463326632373438346336366434323664303230616630623636323164336162323534
39376363616361636164653030316464356438313331333962626330636232363065663764613930
32373262326166633536656366323637326333346233663938633530363632353539363331376636
32383636616262303364633039666163363932643766363934333931616663353665353237363030
62366634343461396539633537646564343237613661373835613534396439386437616264636164
38653930386635613465616137356536316534393030396366323633346539323638373166633633
34393866396263386535633435306232323331353263373530323837303237623939616532366463
65366432313438333333636631623339316162623139323631626336343465646330356232313238
36313266363162626330323033333430363964353236343032643839643530333235633738616365
62653763323365353334646638616434366138346165623866323762613435333436366163666633
34643839613934366636353839363433396332343564633663353735333731333065346432363663
37373964323461623033383635376333316238336638653362656631393561366661643934316565
63356634313161373037353164336665333039666230643934363064393039616438323832356265
30313966313065626335656433326237656134396264383066653730643136636363353965313966
62663631306561373337653661663965636335363766383266363133636438346464613731363961
65346162376164646334356431386561656262366139613664643132363636346339376539303739
37383764656562636364326464613037383939346333396164356139393230376566653435653636
36383266323561363763633236326266623561613735616439366162393031376665376232646238
30646434363839663432613766343935306561636366303865623537313732376639666533353536
37373765376236396662306463343261353031353839616363373036376562333633336236383036
35653865363561623839616264326563626362623839373438363065333531373965656632303135
32303933353066303039636232626433343139353963613162326431346363336137303564616339
61366461326539376165646262393830623662613938303534633837346563626639356164613838
37613963366433346563346661666334346231346530623238646161316631636339396436616565
30363532623465386631623161396462346166323161306436373965633961386266636539333864
31373437356434353362633562363630663462626162613430393563393665336436626238326362
63646537663039646266623338333734666665656431633464343863373563393836333964633437
33303430643935373930343835376335626338633431653365366137373131316661666663643538
65366563313433303234353032396166616630633137643763643036626261663231303361313365
62626135376434326266653538623063383335383635316339623864356533306364336131336138
38373831386633336235336530343561333966636263303732356433333839663161633634386662
62306336666636636362663934383863313930656564336437643833346263343464323334613032
34363461613566393131643661363763646236326562336236363066326266336637313338643336
31393233623738616566373838366434346131663063653931336563633565663065306139306436
64633963393232363164613962623434626632373366356133323665666561386230326335633637
33663135396632616563663538313533623866336162303961663231333033633133376361306664
64313939306566366538373861613538663232383539313433613232363133343234656331663134
64626262373433343138393961613162373063346562626232316231316536356632386466373835
38303365376230653734366434383263616533343233393362306635666265636531363965373563
31623562636239666334393765396238316239613562626136336466333239396165383832656362
61623565306139616333346139363464663335663930623237313438366130316530383634623832
30656339353736373364393664663266633961323861313061656661643564303439393831326566
63333261393135323437663337353866396563313363313465653063316632626666323338643331
35323930613763663462303532626532353435313235623631613239353266306239323864363863
32636335643738643738653438323236646366623637303839313365633763646434613662626237
30656630656638323362636537633662323230353865656665663334336138316365366361643862
39623336346430656335663630303232396638656164613436346237653939393264626336333664
37333739303932366165326633313835343762613539613066336662376634326265366666306264
65633839363730626363663061356362623062623166356664333733633164643364323234393630
36326337376164393265356137653634656634313836663430323139613461303165336438336230
65636639326236366539343537356263343637393165323139316632646636386333366261646535
31613635373965343563373739643937633538336638303231316336386335366466363163643834
64303235666636396633613665626335346563663034336365326563383765646639643462653762
37306639613137383165613563616238653837616561623339356338616233386535353830623931
65623236643963396232373938353264633334326133656238343735653164323239396435313037
61643631663330396363356632316266303031346332386337353464393832643636396339346333
31393238356637373361653262666462633831316632323633383134663763313362376639376431
6237
30646630636633336635633463343933323930346431663331313133343737326566643031363961
3963373966333330363032346563306363373132643738330a636261613164336537643463663165
36363234643564316331363438313032363961353630623033303735353439346331333138633937
6331613661656534380a326162643030623164616437626635393933353931366130653535353764
35306463323533376465623734336534326233363737643661633463626532306161353166313833
61646235666166366330303737613332653466353737626663616531323136353738613537356163
37646262363264316630666633636365316634643435636132643865393038623039663333316435
38666166633366643264306563633039643761623237316439333838313836336437386462353630
36323538613239393938313564643637343930376535363630643635343037383737386630343531
39626138623930663835366130343537356136333530316566623530623030346664326663343464
30393031343638613436626265616632323264663435653533376535396266323331626162326131
37343031373839316135623363386133653231633965653663653366643630366366343431626530
66316337646665303339323736373834366634376533306334323337356261343038373037623134
32336266656137623266616564633238303565363933623465386134303537646563613135643836
62643233646535363237363063326535636232376361366539633136333031663761653535613266
30353738303661333434653034373961666665343739343264396135633763343637393839353465
31373666626662616536663963396662303937303034653164306138336431303735383436346166
32376562613438646166653637663064313435366563313563666564326662663834613435636133
38396334393836346263663061333537303533633733636639646330393638303237613466643731
37653065346263643264663230383435323334616432383531333563336235346535356338363632
32666361396233623539333934616361643937396165636134646666363563363263383262633831
63383837383039346664663334613832343662633331626463613830383863343332316530656331
31343331623462323437643834663934386434626436366239323065343638396239383338636136
61306236623036376664336563626564623132363366646162376464626162633037653466336462
65636233623032636137316235613062393465646532353232393038316238636366313638343033
30653263356366636331653964336138613462383339626564636434613832343934343138386536
38396161666164306430326533323964373664333830386534613461393361346262333337663563
33663633653032653538663930393938613931316537396466663933323231393037373632663637
66626562623434393033373335633336316532366661393139356561323831623236353130313039
39656262366566636136373863613464653661363634303036306462376431323163616535626561
31663563616233393039326436356434663531346531643236333438346362363531396335313037
64656432333634643438333236353235616565336662393761303835346538313136333634633939
33303931616237646534663536353366383239643837653630353966346261303434343932316438
33373530626562656339663530646632646331646538626435613863613064643633323139316232
37623263643135383365356531616632393337643237313733383764313763323138643637613863
61626237303062656166356537353364383865346331393564336530363066646636343236626130
62393335313239373533373135366534363734623865363266623832626432626231383764643731
63626333333836373964323334353562353366343265366338383665636133373366616137316431
30633437363438663539393032396662356339666131656464323433656137396436373636646335
30366431333932343561393361333032346535666163393563353761653163343631643436343930
31666366346136616130393161663737666434633439323833326462623638306466396533353338
32313966363532373933643461653135303339663362303030303065313239353939376661303630
36666262636464316661323761653766306536663333316363363136326231333530396165363337
31303864613630386561393837386533383138303763316563363230366630396437343665343138
36323135386465306239633637626335316666396265393565643434656164393437653862386538
31316236336130323436653836343232623762326263333234316530313763646633613739333834
32656261336563383937353035383836373466616161373464356633333131623561386534343933
65636232353636663131356661666536616438383631313766366263313235646137303131363435
35396537363036353039646133306365376161326232373736626261383130346666346333313136
33343762666431373935616361326461656463306661313239353066636635346263616431366538
66613763633564363162316531633735626463626462353566356239353365663565616437326237
61653962646532383232376330323662656532623334666233346531353936313236353938633735
62343663356266353563643632393631366639633730666337616336663730643139323438313565
30303631333835373061363935313965316663613131386437396633653630613865383062333564
33343163313164373437633466393733663661646332636264323531386262613166633962303166
31383139623834633636356137343464363436326631666638323463636531336337316664663131
37623835356161346339663730623531336662326235633538353966343633393933336362333233
38363163616666326638393764346665663462386139613635346433396337393962343837353031
66343762653137363863326465613939646437616662386261626464663432626235613336323130
33653238323662366335383362613130353238653964623861633630623061396338393838666362
65623236366238373265363834643461633861303132333133663337333831333264613066393335
33393362313465343364663234646561626166376262366332333439623134333266366331386666
32353834313733646633663135633132623962643266346233353137353837396334393265373535
64663036386331636134376362646361666532346637663131326539623364623632353165313266
36326439663935643438356130366436633639636436363766376138343138353061663730663466
62623463326664383961643965636133346538623332366138653262356439356462633435386238
33316262353633353762326332313934356565623664396163306432383833356661643030376234
63656361633939373565313932653963373038376166633636656638353961633664613338303237
65303731393262613963623964386333396631626239316231313162323938316464343266653362
32316636373265346339343938663137623664373432366364343133636430333736383932616562
61663239383662646430303532623663303965353366303139646338643434666464626434663035
30636162613464653332