Files
git/playbooks/deploy.yml
M.V. Hutz 4cb6eaf091 feat: add Gitea Actions runner on private compute
Adds a private runner server on the Hetzner private network with NAT
through the gitea server for outbound internet access. Includes
Terraform resources, Ansible playbooks, and iptables forwarding rules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:40:08 -04:00

237 lines
7.0 KiB
YAML

- name: Set up for fresh host.
gather_facts: false
hosts: localhost
vars_files:
- ../vault.yml
- ../dist/terraform_outputs.yml
tasks:
- name: Add remote host.
ansible.builtin.add_host:
name: server_fresh
ansible_ssh_host: "{{ server_ip.value }}"
ansible_user: root
ansible_port: 22
ansible_private_key_file: "{{ secret.private_ssh_key_path }}"
- name: Switch port to 2222.
hosts: server_fresh
ignore_unreachable: true
gather_facts: false
tasks:
- name: Update SSH port.
ansible.builtin.lineinfile:
dest: "/etc/ssh/sshd_config"
regexp: "^Port"
line: "Port 2222"
- name: Restart service.
ansible.builtin.service:
name: ssh
state: restarted
- name: Set up real host.
gather_facts: false
hosts: localhost
tags:
- deploy
vars_files:
- ../vault.yml
- ../dist/terraform_outputs.yml
tasks:
- name: Add remote host.
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: Install Docker.
gather_facts: true
hosts: server
vars_files:
- ../vault.yml
- ../dist/terraform_outputs.yml
tasks:
- name: Install PIP.
ansible.builtin.apt:
state: present
update_cache: true
name:
- python3-pip
- name: Install needed packages.
ansible.builtin.pip:
name:
- botocore
- boto3
- 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
- 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: 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:
- deploy
gather_facts: false
vars_files:
- ../variables.yml
- ../vault.yml
- ../dist/terraform_outputs.yml
tasks:
- name: Copy gitea folder.
ansible.builtin.copy:
src: ../gitea/
dest: /root/gitea/
mode: preserve
- name: Build image.
community.docker.docker_image_build:
name: "{{ variables.image_name }}"
path: /root/gitea
nocache: true
rebuild: always
pull: true
- name: Create data directory.
ansible.builtin.file:
path: /root/data
state: directory
mode: '0777'
- name: Run image.
community.docker.docker_container:
name: server
image: "{{ variables.image_name }}"
state: started
recreate: true
restart_policy: unless-stopped
memory: 425m
memory_swap: 900m
ports: [80:80, 443:443, "22:22"]
env:
# Secrets.
GITEA__security__INTERNAL_TOKEN: "{{ secret.internal }}"
GITEA__server__LFS_JWT_SECRET: "{{ secret.lfs }}"
GITEA__oauth2__JWT_SECRET: "{{ secret.jwt }}"
GITEA__server__ACME_EMAIL: "acme@maximhutz.me"
GITEA__server__SSH_DOMAIN: "{{ server_fqdn.value }}"
GITEA__server__DOMAIN: "{{ server_fqdn.value }}"
GITEA__server__ROOT_URL: "https://{{ server_fqdn.value }}/"
# General S3 storage information.
GITEA__storage__MINIO_BUCKET: "{{ secret.bucket.name }}"
GITEA__storage__MINIO_ENDPOINT: "{{ secret.bucket.endpoint }}"
GITEA__storage__MINIO_ACCESS_KEY_ID: "{{ secret.bucket.access_key }}"
GITEA__storage__MINIO_SECRET_ACCESS_KEY: "{{ secret.bucket.secret_key }}"
# Set storage to specific S3 bucket path.
GITEA__storage_0x2E_attachments__MINIO_BASE_PATH: "{{ secret.storage.key }}/attachments"
GITEA__storage_0x2E_lfs__MINIO_BASE_PATH: "{{ secret.storage.key }}/lfs"
GITEA__storage_0x2E_avatars__MINIO_BASE_PATH: "{{ secret.storage.key }}/avatars"
GITEA__storage_0x2E_repo_0X2D_archive___MINIO_BASE_PATH: "{{ secret.storage.key }}/repo-archive"
GITEA__storage_0x2E_repo_0X2D_avatars__MINIO_BASE_PATH: "{{ secret.storage.key }}/repo-avatars"
GITEA__storage_0x2E_packages__MINIO_BASE_PATH: "{{ secret.storage.key }}/packages"
GITEA__storage_0x2E_actions_log__MINIO_BASE_PATH: "{{ secret.storage.key }}/actions_log"
GITEA__storage_0x2E_actions_artifacts__MINIO_BASE_PATH: "{{ secret.storage.key }}/actions_artifacts"
labels:
docker-volume-backup.stop-during-backup: "true"
volumes:
- /root/data:/var/lib/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- name: Run backup.
community.docker.docker_container:
name: backup
image: offen/docker-volume-backup:v2
state: started
recreate: true
restart_policy: unless-stopped
volumes:
- /root/data:/backup/my-app-backup:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
env:
AWS_S3_BUCKET_NAME: "{{ secret.bucket.name }}"
AWS_S3_PATH: "{{ secret.backup.key }}"
AWS_REGION: "{{ secret.bucket.region }}"
AWS_ACCESS_KEY_ID: "{{ secret.bucket.access_key }}"
AWS_SECRET_ACCESS_KEY: "{{ secret.bucket.secret_key }}"
AWS_ENDPOINT: "{{ secret.bucket.endpoint }}"
BACKUP_CRON_EXPRESSION: "0 0 * * *"