Initial commit: nult - Ansible deployment toolkit
Merged from veridion-gitea and veridion-act-runner-gitea repos. nult (Null-T) - instant teleportation from Strugatsky's Noon Universe. Like Null-T, this toolkit instantly deploys infrastructure. Roles: - gitea: Gitea server with PostgreSQL (Docker Compose) - act_runner: Gitea Actions runner Playbooks: - gitea.yml: Deploy Gitea server - act-runner.yml: Deploy Act Runner - site.yml: Deploy all services Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
23
.ansible-lint
Normal file
23
.ansible-lint
Normal file
@@ -0,0 +1,23 @@
|
||||
# =============================================================================
|
||||
# Ansible-lint Configuration
|
||||
# =============================================================================
|
||||
# See: https://ansible.readthedocs.io/projects/lint/configuring/
|
||||
|
||||
# Use production profile (strictest)
|
||||
profile: production
|
||||
|
||||
# Exclude paths
|
||||
exclude_paths:
|
||||
- .git/
|
||||
- .gitignore
|
||||
|
||||
# Enable offline mode (don't download roles/collections)
|
||||
offline: true
|
||||
|
||||
# Warn about these rules instead of failing
|
||||
warn_list:
|
||||
- experimental
|
||||
|
||||
# Skip these rules entirely (if needed)
|
||||
# skip_list:
|
||||
# - yaml[line-length]
|
||||
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# =============================================================================
|
||||
# Gitignore for Ansible Gitea Deployment
|
||||
# =============================================================================
|
||||
# Reference: https://docs.ansible.com/ansible/latest/vault_guide/index.html
|
||||
|
||||
# Encrypted vault files (contain secrets)
|
||||
# Users should create these locally with: ansible-vault create <file>
|
||||
group_vars/all/vault.yml
|
||||
group_vars/*/vault.yml
|
||||
**/vault.yml
|
||||
!**/vault.yml.example
|
||||
|
||||
# Vault password files
|
||||
.vault_pass
|
||||
.vault_password
|
||||
*.vault_pass
|
||||
|
||||
# Ansible retry files
|
||||
*.retry
|
||||
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.log
|
||||
53
act-runner.yml
Normal file
53
act-runner.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner Deployment Playbook
|
||||
# =============================================================================
|
||||
#
|
||||
# Deploys and configures Gitea Act Runner on Ubuntu servers.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory/hosts.yml act-runner.yml --ask-vault-pass
|
||||
#
|
||||
# Dry run:
|
||||
# ansible-playbook -i inventory/hosts.yml act-runner.yml --check --diff --ask-vault-pass
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
- name: Deploy Gitea Act Runner
|
||||
hosts: runner_servers
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Validate target operating system
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['distribution'] == "Ubuntu"
|
||||
- ansible_facts['distribution_major_version'] | int >= 20
|
||||
fail_msg: >-
|
||||
This playbook requires Ubuntu 20.04 or later.
|
||||
Detected: {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}
|
||||
|
||||
- name: Update apt package cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
|
||||
roles:
|
||||
- role: act_runner
|
||||
tags: [act_runner]
|
||||
|
||||
post_tasks:
|
||||
- name: Display deployment summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "=============================================="
|
||||
- "Gitea Act Runner - Deployment Complete"
|
||||
- "=============================================="
|
||||
- "Runner name: {{ act_runner_name }}"
|
||||
- "Gitea instance: {{ gitea_instance_url }}"
|
||||
- "Service status: {{ 'RUNNING' if act_runner_service_status.status.ActiveState == 'active' else 'NOT RUNNING' }}"
|
||||
- ""
|
||||
- "Verify in Gitea UI:"
|
||||
- " {{ gitea_instance_url }}/-/admin/actions/runners"
|
||||
- "=============================================="
|
||||
23
gitea.yml
Normal file
23
gitea.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Deployment Playbook
|
||||
# =============================================================================
|
||||
#
|
||||
# Deploys and configures Gitea with PostgreSQL using Docker Compose.
|
||||
# Includes backup, domain configuration, and security hardening.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory/hosts.yml gitea.yml --ask-vault-pass
|
||||
#
|
||||
# Dry run:
|
||||
# ansible-playbook -i inventory/hosts.yml gitea.yml --check --diff --ask-vault-pass
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
- name: Deploy Gitea
|
||||
hosts: gitea_servers
|
||||
gather_facts: true
|
||||
|
||||
roles:
|
||||
- role: gitea
|
||||
tags: [gitea]
|
||||
53
group_vars/all/vars.yml
Normal file
53
group_vars/all/vars.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Group Variables - All Hosts
|
||||
# =============================================================================
|
||||
#
|
||||
# Maps vault secrets to role variables and sets common overrides.
|
||||
# Vault variables (prefixed with vault_) are stored encrypted in vault.yml.
|
||||
#
|
||||
# HOW TO USE:
|
||||
# 1. Create the vault: ansible-vault create group_vars/all/vault.yml
|
||||
# 2. Add your secrets to the vault (see vault.yml.example)
|
||||
# 3. The mappings below will reference those vault variables
|
||||
#
|
||||
# See: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# Gitea Server Configuration
|
||||
# =============================================================================
|
||||
# Used by: roles/gitea
|
||||
|
||||
# Domain configuration
|
||||
gitea_domain: "{{ vault_gitea_domain }}"
|
||||
gitea_ssh_domain: "{{ gitea_domain }}"
|
||||
gitea_root_url: "https://{{ gitea_domain }}"
|
||||
|
||||
# Database credentials
|
||||
gitea_db_password: "{{ vault_gitea_db_password }}"
|
||||
|
||||
# ACME/TLS configuration
|
||||
gitea_acme_email: "{{ vault_gitea_acme_email | default('') }}"
|
||||
|
||||
# =============================================================================
|
||||
# Act Runner Configuration
|
||||
# =============================================================================
|
||||
# Used by: roles/act_runner
|
||||
|
||||
# Gitea instance URL (e.g., "https://git.example.com")
|
||||
gitea_instance_url: "{{ vault_gitea_instance_url }}"
|
||||
|
||||
# Registration token from Gitea admin panel
|
||||
# Get it from: {{ gitea_instance_url }}/-/admin/actions/runners
|
||||
act_runner_token: "{{ vault_act_runner_token }}"
|
||||
|
||||
# Package registry hostname (usually same as Gitea host, without https://)
|
||||
gitea_registry: "{{ vault_gitea_registry }}"
|
||||
|
||||
# Service account username for package registry authentication
|
||||
gitea_actions_user: "{{ vault_gitea_actions_user }}"
|
||||
|
||||
# Personal Access Token (PAT) for package registry
|
||||
# Create at: {{ gitea_instance_url }}/user/settings/applications
|
||||
gitea_packages_token: "{{ vault_gitea_packages_token }}"
|
||||
29
group_vars/all/vault.yml.example
Normal file
29
group_vars/all/vault.yml.example
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Vault Secrets Example
|
||||
# =============================================================================
|
||||
#
|
||||
# Copy this file and encrypt it:
|
||||
# cp vault.yml.example vault.yml
|
||||
# ansible-vault encrypt vault.yml
|
||||
#
|
||||
# Or create directly:
|
||||
# ansible-vault create vault.yml
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Gitea Server Secrets
|
||||
# -----------------------------------------------------------------------------
|
||||
vault_gitea_domain: "git.example.com"
|
||||
vault_gitea_db_password: "your-secure-database-password"
|
||||
vault_gitea_acme_email: "admin@example.com"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Act Runner Secrets
|
||||
# -----------------------------------------------------------------------------
|
||||
vault_gitea_instance_url: "https://git.example.com"
|
||||
vault_act_runner_token: "runner-registration-token-from-gitea-admin"
|
||||
vault_gitea_registry: "git.example.com"
|
||||
vault_gitea_actions_user: "gitea_actions"
|
||||
vault_gitea_packages_token: "personal-access-token-for-packages"
|
||||
37
inventory/hosts.yml
Normal file
37
inventory/hosts.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Ansible Inventory - nult
|
||||
# =============================================================================
|
||||
# Null-T: Instant teleportation from Strugatsky's Noon Universe.
|
||||
# Like Null-T, this toolkit instantly deploys infrastructure.
|
||||
#
|
||||
# Servers are grouped by role. A host can belong to multiple groups.
|
||||
# =============================================================================
|
||||
|
||||
all:
|
||||
children:
|
||||
# -------------------------------------------------------------------------
|
||||
# Gitea Servers
|
||||
# -------------------------------------------------------------------------
|
||||
gitea_servers:
|
||||
hosts:
|
||||
gitea-primary:
|
||||
ansible_host: 51.250.71.151
|
||||
gitea_install_dir: /home/parf/gitea
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Act Runner Servers
|
||||
# -------------------------------------------------------------------------
|
||||
runner_servers:
|
||||
hosts:
|
||||
tralalero-tralala:
|
||||
ansible_host: 147.45.100.33
|
||||
ansible_user: root
|
||||
# act_runner_name: "custom-name" # Optional override
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Global Variables
|
||||
# ---------------------------------------------------------------------------
|
||||
vars:
|
||||
ansible_user: parf
|
||||
ansible_become: true
|
||||
99
roles/act_runner/defaults/main.yml
Normal file
99
roles/act_runner/defaults/main.yml
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Role Default Variables
|
||||
# =============================================================================
|
||||
#
|
||||
# This file defines configurable variables for the act_runner role.
|
||||
# Override these in group_vars/all.yml or inventory host_vars as needed.
|
||||
#
|
||||
# REQUIRED VARIABLES (must be set in vault - not defined here):
|
||||
# - gitea_instance_url : URL of your Gitea instance
|
||||
# - act_runner_token : Registration token from Gitea
|
||||
# - gitea_packages_token : PAT for package registry access
|
||||
# - gitea_registry : Package registry hostname
|
||||
# - gitea_actions_user : Service account username
|
||||
#
|
||||
# See group_vars/vault.yml.example for how to set these secrets.
|
||||
# See: https://docs.gitea.com/usage/actions/act-runner
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Act Runner Binary Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Version of act_runner to install.
|
||||
# Check available versions at: https://dl.gitea.com/act_runner/
|
||||
# Format: semantic version string without 'v' prefix.
|
||||
act_runner_version: "0.2.13"
|
||||
|
||||
# Target CPU architecture for the binary download.
|
||||
# Valid values: "amd64" (x86_64), "arm64" (aarch64)
|
||||
act_runner_arch: "amd64"
|
||||
|
||||
# Whether to verify SHA256 checksum after downloading the binary.
|
||||
# STRONGLY RECOMMENDED: Leave as true for security.
|
||||
act_runner_verify_checksum: true
|
||||
|
||||
# Filesystem path where the act_runner binary will be installed.
|
||||
# /usr/local/bin is the standard FHS location for locally installed binaries.
|
||||
act_runner_bin_path: "/usr/local/bin/act_runner"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# System User Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Unix username for running the act_runner service.
|
||||
# Will be created as a system user if it doesn't exist.
|
||||
act_runner_user: "act_runner"
|
||||
|
||||
# Unix group for the act_runner user.
|
||||
act_runner_group: "act_runner"
|
||||
|
||||
# Home directory for the act_runner user.
|
||||
# Stores: .runner file, cache, working directories.
|
||||
act_runner_home: "/home/act_runner"
|
||||
|
||||
# Directory for act_runner configuration files.
|
||||
# Stores: config.yaml
|
||||
act_runner_config_dir: "/etc/act_runner"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Node.js Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Node.js major version to install via NodeSource.
|
||||
# Required for JavaScript-based GitHub Actions.
|
||||
# Valid values: "18", "20", "22", "24"
|
||||
# See: https://nodejs.org/en/about/previous-releases
|
||||
act_runner_nodejs_version: "24"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Runner Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Human-readable name for this runner instance.
|
||||
# Displayed in Gitea UI under Actions > Runners.
|
||||
# Default: server's hostname
|
||||
act_runner_name: "{{ ansible_facts['hostname'] }}"
|
||||
|
||||
# Labels determine which jobs this runner can execute.
|
||||
# Format: "label-name:executor"
|
||||
# Executors:
|
||||
# - "host" : Run directly on the host system
|
||||
# - "docker://image" : Run in Docker container
|
||||
#
|
||||
# Examples:
|
||||
# - "ubuntu-latest:host"
|
||||
# - "ubuntu-latest:docker://node:24"
|
||||
# - "self-hosted:host"
|
||||
act_runner_labels:
|
||||
- "ubuntu-latest:host"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Container Behavior
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Whether to always pull container images before running jobs.
|
||||
# true: Ensures latest image (recommended for CI/CD)
|
||||
# false: Uses cached image if available (faster)
|
||||
act_runner_container_force_pull: true
|
||||
26
roles/act_runner/handlers/main.yml
Normal file
26
roles/act_runner/handlers/main.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Ansible Handlers
|
||||
# =============================================================================
|
||||
#
|
||||
# Handlers are triggered by 'notify' in tasks and run once at the end of play.
|
||||
# They provide a way to restart services only when configuration changes.
|
||||
#
|
||||
# Usage in tasks:
|
||||
# - name: Deploy config
|
||||
# template: ...
|
||||
# notify: restart act_runner
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Reload systemd configuration after unit file changes.
|
||||
# Required before systemctl can see updated unit files.
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
# Restart the act_runner service to apply configuration changes.
|
||||
- name: Restart act_runner
|
||||
ansible.builtin.systemd:
|
||||
name: act_runner
|
||||
state: restarted
|
||||
88
roles/act_runner/tasks/binary.yml
Normal file
88
roles/act_runner/tasks/binary.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Binary Installation
|
||||
# =============================================================================
|
||||
#
|
||||
# Downloads and installs the act_runner binary from:
|
||||
# https://dl.gitea.com/act_runner/
|
||||
#
|
||||
# Security: Binary integrity is verified via SHA256 checksum.
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Construct download URLs based on version and architecture.
|
||||
- name: Set act_runner download URLs
|
||||
ansible.builtin.set_fact:
|
||||
act_runner_download_url: >-
|
||||
https://dl.gitea.com/act_runner/{{ act_runner_version }}/act_runner-{{ act_runner_version }}-linux-{{ act_runner_arch }}
|
||||
act_runner_checksum_url: >-
|
||||
https://dl.gitea.com/act_runner/{{ act_runner_version }}/act_runner-{{ act_runner_version }}-linux-{{ act_runner_arch }}.sha256
|
||||
|
||||
# Download the act_runner binary to a temporary location.
|
||||
- name: Download act_runner binary
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ act_runner_download_url }}"
|
||||
dest: /tmp/act_runner
|
||||
mode: '0755'
|
||||
|
||||
# Download checksum file for verification (when enabled).
|
||||
- name: Download act_runner checksum
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ act_runner_checksum_url }}"
|
||||
dest: /tmp/act_runner.sha256
|
||||
mode: '0644'
|
||||
when: act_runner_verify_checksum
|
||||
|
||||
# Read the expected checksum from the downloaded file.
|
||||
- name: Read expected checksum
|
||||
ansible.builtin.slurp:
|
||||
src: /tmp/act_runner.sha256
|
||||
register: act_runner_expected_checksum_file
|
||||
when: act_runner_verify_checksum
|
||||
|
||||
# Parse the checksum (format: "checksum filename").
|
||||
- name: Parse expected checksum
|
||||
ansible.builtin.set_fact:
|
||||
act_runner_expected_checksum: "{{ (act_runner_expected_checksum_file.content | b64decode).split()[0] }}"
|
||||
when: act_runner_verify_checksum
|
||||
|
||||
# Calculate actual checksum of downloaded binary.
|
||||
- name: Calculate actual checksum
|
||||
ansible.builtin.stat:
|
||||
path: /tmp/act_runner
|
||||
checksum_algorithm: sha256
|
||||
register: act_runner_actual_checksum
|
||||
when: act_runner_verify_checksum
|
||||
|
||||
# Verify checksums match (fail if tampered).
|
||||
- name: Verify checksum matches
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- act_runner_actual_checksum.stat.checksum == act_runner_expected_checksum
|
||||
fail_msg: >-
|
||||
Checksum verification FAILED!
|
||||
Expected: {{ act_runner_expected_checksum }}
|
||||
Actual: {{ act_runner_actual_checksum.stat.checksum }}
|
||||
The downloaded binary may have been tampered with.
|
||||
success_msg: "Checksum verified: {{ act_runner_expected_checksum }}"
|
||||
when: act_runner_verify_checksum
|
||||
|
||||
# Install binary to final location.
|
||||
- name: Install act_runner binary
|
||||
ansible.builtin.copy:
|
||||
src: /tmp/act_runner
|
||||
dest: "{{ act_runner_bin_path }}"
|
||||
remote_src: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
notify: Restart act_runner
|
||||
|
||||
# Clean up temporary files.
|
||||
- name: Clean up temporary files
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /tmp/act_runner
|
||||
- /tmp/act_runner.sha256
|
||||
56
roles/act_runner/tasks/config.yml
Normal file
56
roles/act_runner/tasks/config.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Configuration and Registration
|
||||
# =============================================================================
|
||||
#
|
||||
# Deploys the runner configuration and registers with Gitea.
|
||||
# Registration is idempotent: only runs if .runner file doesn't exist.
|
||||
#
|
||||
# The .runner file contains the runner's identity after registration.
|
||||
# DO NOT DELETE this file or re-registration will be required.
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Deploy configuration file from template.
|
||||
- name: Deploy act_runner configuration
|
||||
ansible.builtin.template:
|
||||
src: config.yaml.j2
|
||||
dest: "{{ act_runner_config_dir }}/config.yaml"
|
||||
owner: "{{ act_runner_user }}"
|
||||
group: "{{ act_runner_group }}"
|
||||
mode: '0640' # Restrictive: contains secrets
|
||||
notify: Restart act_runner
|
||||
|
||||
# Check if runner is already registered.
|
||||
# The .runner file is created during registration and persists.
|
||||
- name: Check if runner is already registered
|
||||
ansible.builtin.stat:
|
||||
path: "{{ act_runner_home }}/.runner"
|
||||
register: act_runner_runner_file
|
||||
|
||||
# Register the runner with Gitea (only if not already registered).
|
||||
# This is a one-time operation that creates the .runner file.
|
||||
- name: Register runner with Gitea
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
{{ act_runner_bin_path }} register
|
||||
--no-interactive
|
||||
--config {{ act_runner_config_dir }}/config.yaml
|
||||
--instance {{ gitea_instance_url }}
|
||||
--token {{ act_runner_token }}
|
||||
--name {{ act_runner_name }}
|
||||
--labels {{ act_runner_labels | join(',') }}
|
||||
chdir: "{{ act_runner_home }}"
|
||||
become: true
|
||||
become_user: "{{ act_runner_user }}"
|
||||
when: not act_runner_runner_file.stat.exists
|
||||
register: act_runner_registration_result
|
||||
changed_when: act_runner_registration_result.rc == 0
|
||||
# Don't show token in logs
|
||||
no_log: true
|
||||
|
||||
# Display registration result (without sensitive data).
|
||||
- name: Display registration status
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Runner registration: {{ 'NEW - registered successfully' if act_runner_registration_result.changed | default(false) else 'EXISTING - already registered' }}
|
||||
91
roles/act_runner/tasks/docker.yml
Normal file
91
roles/act_runner/tasks/docker.yml
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Docker Installation
|
||||
# =============================================================================
|
||||
#
|
||||
# Installs Docker CE following the official Docker documentation:
|
||||
# https://docs.docker.com/engine/install/ubuntu/
|
||||
#
|
||||
# Uses DEB822 format (.sources) as per current Docker documentation.
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Remove old/conflicting Docker packages that may interfere.
|
||||
- name: Remove conflicting Docker packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker.io
|
||||
- docker-doc
|
||||
- docker-compose
|
||||
- docker-compose-v2
|
||||
- podman-docker
|
||||
- containerd
|
||||
- runc
|
||||
state: absent
|
||||
failed_when: false
|
||||
|
||||
# Install packages required to add Docker's APT repository.
|
||||
- name: Install Docker prerequisites
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
state: present
|
||||
|
||||
# Create directory for APT keyrings.
|
||||
- name: Create keyrings directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
# Download Docker's official GPG key.
|
||||
- name: Download Docker GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
mode: '0644'
|
||||
|
||||
# Get the dpkg architecture for the repository URL.
|
||||
- name: Get dpkg architecture
|
||||
ansible.builtin.command: dpkg --print-architecture
|
||||
register: act_runner_dpkg_arch
|
||||
changed_when: false
|
||||
|
||||
# Add Docker's APT repository using DEB822 format.
|
||||
# This is the modern format recommended by Docker documentation.
|
||||
- name: Add Docker APT repository (DEB822 format)
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/sources.list.d/docker.sources
|
||||
mode: '0644'
|
||||
content: |
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/ubuntu
|
||||
Suites: {{ ansible_facts['distribution_release'] }}
|
||||
Components: stable
|
||||
Architectures: {{ act_runner_dpkg_arch.stdout }}
|
||||
Signed-By: /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Update apt cache after adding repository.
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 0 # Force update since we just added a new repo
|
||||
|
||||
# Install Docker CE and related packages.
|
||||
- name: Install Docker packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
|
||||
# Ensure Docker service is running and enabled on boot.
|
||||
- name: Ensure Docker service is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: true
|
||||
75
roles/act_runner/tasks/main.yml
Normal file
75
roles/act_runner/tasks/main.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Main Task Orchestration
|
||||
# =============================================================================
|
||||
#
|
||||
# This file orchestrates the act_runner installation in the correct order.
|
||||
# Each include_tasks imports a focused task file for better maintainability.
|
||||
#
|
||||
# Execution order matters:
|
||||
# 1. Validate inputs (fail fast on missing required values)
|
||||
# 2. Install Docker (required for container operations in Actions)
|
||||
# 3. Install Node.js (required for JavaScript-based GitHub Actions)
|
||||
# 4. Download act_runner binary (the core component)
|
||||
# 5. Create system user (security: run as unprivileged user)
|
||||
# 6. Configure and register (connect to Gitea instance)
|
||||
# 7. Setup systemd service (enable automatic startup)
|
||||
# 8. Verify installation (ensure everything works)
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Fail early if required variables are not set.
|
||||
# This prevents partial installations that would be harder to debug.
|
||||
- name: Validate required variables are defined
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- gitea_instance_url is defined
|
||||
- gitea_instance_url | length > 0
|
||||
- act_runner_token is defined
|
||||
- act_runner_token | length > 0
|
||||
- gitea_packages_token is defined
|
||||
- gitea_packages_token | length > 0
|
||||
- gitea_registry is defined
|
||||
- gitea_registry | length > 0
|
||||
- gitea_actions_user is defined
|
||||
- gitea_actions_user | length > 0
|
||||
fail_msg: >-
|
||||
Missing required variables. Ensure these are set in vault:
|
||||
gitea_instance_url, act_runner_token, gitea_packages_token,
|
||||
gitea_registry, gitea_actions_user.
|
||||
See group_vars/vault.yml.example for details.
|
||||
success_msg: "All required variables are defined"
|
||||
|
||||
# Docker is needed even for host execution because many GitHub Actions
|
||||
# use Docker internally (e.g., actions/checkout uses node in container).
|
||||
- name: Install and configure Docker
|
||||
ansible.builtin.include_tasks: docker.yml
|
||||
|
||||
# Node.js is required for JavaScript-based GitHub Actions.
|
||||
# Many popular actions (checkout, cache, upload-artifact) need Node.js.
|
||||
- name: Install Node.js runtime
|
||||
ansible.builtin.include_tasks: nodejs.yml
|
||||
|
||||
# Download and install the act_runner binary with checksum verification.
|
||||
- name: Install act_runner binary
|
||||
ansible.builtin.include_tasks: binary.yml
|
||||
|
||||
# Create dedicated system user for security isolation.
|
||||
# The runner should not run as root.
|
||||
- name: Create act_runner system user
|
||||
ansible.builtin.include_tasks: user.yml
|
||||
|
||||
# Deploy configuration and register with Gitea instance.
|
||||
# Registration only happens if .runner file doesn't exist (idempotent).
|
||||
- name: Configure and register runner
|
||||
ansible.builtin.include_tasks: config.yml
|
||||
|
||||
# Deploy systemd unit file for service management.
|
||||
# Enables automatic startup on boot and easy service control.
|
||||
- name: Setup systemd service
|
||||
ansible.builtin.include_tasks: systemd.yml
|
||||
|
||||
# Run verification checks to ensure installation succeeded.
|
||||
# Fails the playbook if any critical component is not working.
|
||||
- name: Verify installation
|
||||
ansible.builtin.include_tasks: verify.yml
|
||||
54
roles/act_runner/tasks/nodejs.yml
Normal file
54
roles/act_runner/tasks/nodejs.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Node.js Installation
|
||||
# =============================================================================
|
||||
#
|
||||
# Installs Node.js LTS via NodeSource:
|
||||
# https://github.com/nodesource/distributions
|
||||
#
|
||||
# Node.js is required because:
|
||||
# - Many GitHub Actions are written in JavaScript
|
||||
# - Popular actions like checkout, cache, upload-artifact need Node.js
|
||||
# - The runner itself may need Node.js for certain operations
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Install prerequisites for NodeSource setup script.
|
||||
- name: Install Node.js prerequisites
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
state: present
|
||||
|
||||
# Download NodeSource setup script.
|
||||
# This script adds the NodeSource APT repository.
|
||||
- name: Download NodeSource setup script
|
||||
ansible.builtin.get_url:
|
||||
url: "https://deb.nodesource.com/setup_{{ act_runner_nodejs_version }}.x"
|
||||
dest: /tmp/nodesource_setup.sh
|
||||
mode: '0755'
|
||||
|
||||
# Execute the NodeSource setup script to configure APT repository.
|
||||
# Script is safe to re-run (idempotent).
|
||||
- name: Execute NodeSource setup script
|
||||
ansible.builtin.command:
|
||||
cmd: /tmp/nodesource_setup.sh
|
||||
args:
|
||||
creates: /etc/apt/sources.list.d/nodesource.list
|
||||
register: act_runner_nodesource_result
|
||||
changed_when: act_runner_nodesource_result.rc == 0
|
||||
|
||||
# Install Node.js from NodeSource repository.
|
||||
- name: Install Node.js
|
||||
ansible.builtin.apt:
|
||||
name: nodejs
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
# Clean up setup script.
|
||||
- name: Remove NodeSource setup script
|
||||
ansible.builtin.file:
|
||||
path: /tmp/nodesource_setup.sh
|
||||
state: absent
|
||||
37
roles/act_runner/tasks/systemd.yml
Normal file
37
roles/act_runner/tasks/systemd.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Systemd Service Setup
|
||||
# =============================================================================
|
||||
#
|
||||
# Creates and enables the systemd service for act_runner.
|
||||
# This ensures the runner:
|
||||
# - Starts automatically on boot
|
||||
# - Restarts if it crashes
|
||||
# - Can be controlled via systemctl
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Deploy systemd service unit file from template.
|
||||
- name: Deploy systemd service file
|
||||
ansible.builtin.template:
|
||||
src: act_runner.service.j2
|
||||
dest: /etc/systemd/system/act_runner.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart act_runner
|
||||
|
||||
# Reload systemd to pick up the new/changed unit file.
|
||||
# This is immediate (not via handler) to ensure service can be started.
|
||||
- name: Reload systemd daemon
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
# Enable and start the act_runner service.
|
||||
- name: Enable and start act_runner service
|
||||
ansible.builtin.systemd:
|
||||
name: act_runner
|
||||
state: started
|
||||
enabled: true
|
||||
54
roles/act_runner/tasks/user.yml
Normal file
54
roles/act_runner/tasks/user.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - System User Setup
|
||||
# =============================================================================
|
||||
#
|
||||
# Creates a dedicated system user for running the act_runner service.
|
||||
# Running as an unprivileged user improves security by:
|
||||
# - Limiting what the service can access
|
||||
# - Isolating it from other services
|
||||
# - Following the principle of least privilege
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Create the act_runner system group.
|
||||
- name: Create act_runner group
|
||||
ansible.builtin.group:
|
||||
name: "{{ act_runner_group }}"
|
||||
state: present
|
||||
system: true
|
||||
|
||||
# Create the act_runner system user.
|
||||
- name: Create act_runner user
|
||||
ansible.builtin.user:
|
||||
name: "{{ act_runner_user }}"
|
||||
group: "{{ act_runner_group }}"
|
||||
# Add to docker group for container access.
|
||||
groups: docker
|
||||
append: true
|
||||
# Use bash shell for better compatibility with actions.
|
||||
shell: /bin/bash
|
||||
# Home directory for runner data.
|
||||
home: "{{ act_runner_home }}"
|
||||
create_home: true
|
||||
# System user (no login, low UID).
|
||||
system: true
|
||||
state: present
|
||||
|
||||
# Ensure home directory has correct permissions.
|
||||
- name: Set permissions on home directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ act_runner_home }}"
|
||||
state: directory
|
||||
owner: "{{ act_runner_user }}"
|
||||
group: "{{ act_runner_group }}"
|
||||
mode: '0750'
|
||||
|
||||
# Create configuration directory.
|
||||
- name: Create configuration directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ act_runner_config_dir }}"
|
||||
state: directory
|
||||
owner: "{{ act_runner_user }}"
|
||||
group: "{{ act_runner_group }}"
|
||||
mode: '0750'
|
||||
80
roles/act_runner/tasks/verify.yml
Normal file
80
roles/act_runner/tasks/verify.yml
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Installation Verification
|
||||
# =============================================================================
|
||||
#
|
||||
# Verifies that all components were installed correctly.
|
||||
# Fails the playbook if any critical check doesn't pass.
|
||||
#
|
||||
# Verification order:
|
||||
# 1. Docker daemon is running and accessible
|
||||
# 2. Node.js is installed at the expected version
|
||||
# 3. act_runner binary is executable and reports version
|
||||
# 4. act_runner systemd service is active
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Verify Docker daemon is running and accessible.
|
||||
- name: Verify Docker daemon is accessible
|
||||
ansible.builtin.command: docker info
|
||||
changed_when: false
|
||||
register: act_runner_docker_info_result
|
||||
|
||||
- name: Display Docker status
|
||||
ansible.builtin.debug:
|
||||
msg: "Docker is running: {{ act_runner_docker_info_result.stdout_lines[0] | default('OK') }}"
|
||||
|
||||
# Verify Node.js is installed at the expected major version.
|
||||
- name: Verify Node.js installation
|
||||
ansible.builtin.command: node --version
|
||||
changed_when: false
|
||||
register: act_runner_node_version_result
|
||||
|
||||
- name: Verify Node.js major version matches expected
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- act_runner_node_version_result.stdout is match('^v' ~ act_runner_nodejs_version ~ '\\.')
|
||||
fail_msg: >-
|
||||
Node.js version mismatch!
|
||||
Expected: v{{ act_runner_nodejs_version }}.x
|
||||
Actual: {{ act_runner_node_version_result.stdout }}
|
||||
success_msg: "Node.js {{ act_runner_node_version_result.stdout }} installed correctly"
|
||||
|
||||
# Verify act_runner binary is installed and executable.
|
||||
- name: Verify act_runner binary
|
||||
ansible.builtin.command: "{{ act_runner_bin_path }} --version"
|
||||
changed_when: false
|
||||
register: act_runner_version_result
|
||||
|
||||
- name: Display act_runner version
|
||||
ansible.builtin.debug:
|
||||
msg: "act_runner version: {{ act_runner_version_result.stdout }}"
|
||||
|
||||
# Verify the systemd service is running.
|
||||
- name: Get act_runner service status
|
||||
ansible.builtin.systemd:
|
||||
name: act_runner
|
||||
register: act_runner_service_status
|
||||
|
||||
- name: Verify act_runner service is active
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- act_runner_service_status.status.ActiveState == "active"
|
||||
fail_msg: >-
|
||||
act_runner service is NOT running!
|
||||
State: {{ act_runner_service_status.status.ActiveState }}
|
||||
Check logs: journalctl -u act_runner -n 50
|
||||
success_msg: "act_runner service is running (PID: {{ act_runner_service_status.status.MainPID }})"
|
||||
|
||||
# Final summary.
|
||||
- name: Verification complete
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "=========================================="
|
||||
- "All verification checks PASSED!"
|
||||
- "=========================================="
|
||||
- "Docker: running"
|
||||
- "Node.js: {{ act_runner_node_version_result.stdout }}"
|
||||
- "act_runner: {{ act_runner_version_result.stdout }}"
|
||||
- "Service: active (PID {{ act_runner_service_status.status.MainPID }})"
|
||||
- "=========================================="
|
||||
71
roles/act_runner/templates/act_runner.service.j2
Normal file
71
roles/act_runner/templates/act_runner.service.j2
Normal file
@@ -0,0 +1,71 @@
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Systemd Service Unit
|
||||
# =============================================================================
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
#
|
||||
# Common commands:
|
||||
# systemctl status act_runner - Check service status
|
||||
# systemctl restart act_runner - Restart the service
|
||||
# journalctl -u act_runner -f - Follow service logs
|
||||
#
|
||||
# See: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
|
||||
# =============================================================================
|
||||
|
||||
[Unit]
|
||||
# Human-readable description
|
||||
Description=Gitea Actions runner
|
||||
|
||||
# Documentation link
|
||||
Documentation=https://gitea.com/gitea/act_runner
|
||||
|
||||
# Start after Docker and network are available
|
||||
After=docker.service network-online.target
|
||||
|
||||
# Request network-online.target to be started
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
# Simple type: process runs in foreground
|
||||
Type=simple
|
||||
|
||||
# Main command
|
||||
ExecStart={{ act_runner_bin_path }} daemon --config {{ act_runner_config_dir }}/config.yaml
|
||||
|
||||
# Reload command (sends HUP signal)
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
|
||||
# Working directory
|
||||
WorkingDirectory={{ act_runner_home }}
|
||||
|
||||
# No timeout for start/stop (jobs may take long)
|
||||
TimeoutSec=0
|
||||
|
||||
# Wait before restarting after failure
|
||||
RestartSec=10
|
||||
|
||||
# Always restart on any exit
|
||||
Restart=always
|
||||
|
||||
# Run as unprivileged user
|
||||
User={{ act_runner_user }}
|
||||
Group={{ act_runner_group }}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Security Hardening
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# No new privileges via setuid/setgid
|
||||
NoNewPrivileges=true
|
||||
|
||||
# Make /usr, /boot, /efi read-only
|
||||
ProtectSystem=strict
|
||||
|
||||
# Allow writes only to these paths
|
||||
ReadWritePaths={{ act_runner_home }} {{ act_runner_config_dir }}
|
||||
|
||||
# Private /tmp directory
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
# Start on normal boot
|
||||
WantedBy=multi-user.target
|
||||
113
roles/act_runner/templates/config.yaml.j2
Normal file
113
roles/act_runner/templates/config.yaml.j2
Normal file
@@ -0,0 +1,113 @@
|
||||
# =============================================================================
|
||||
# Gitea Act Runner - Configuration File
|
||||
# =============================================================================
|
||||
# Managed by Ansible - DO NOT EDIT MANUALLY
|
||||
#
|
||||
# To modify settings, update the role variables and re-run the playbook.
|
||||
#
|
||||
# Reference: https://docs.gitea.com/usage/actions/act-runner
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
log:
|
||||
# Log verbosity level.
|
||||
# Valid values: trace, debug, info, warn, error, fatal
|
||||
level: info
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Runner Core Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
runner:
|
||||
# Path to the runner registration state file.
|
||||
# Created during 'act_runner register'. DO NOT DELETE.
|
||||
file: {{ act_runner_home }}/.runner
|
||||
|
||||
# Maximum number of concurrent jobs.
|
||||
capacity: 1
|
||||
|
||||
# Environment variables injected into every job.
|
||||
envs:
|
||||
# Package registry hostname
|
||||
registry: {{ gitea_registry }}
|
||||
|
||||
# Service account username
|
||||
actions_user: {{ gitea_actions_user }}
|
||||
|
||||
# PAT for package registry authentication
|
||||
PACKAGES_TOKEN: {{ gitea_packages_token }}
|
||||
|
||||
# Optional file for additional environment variables.
|
||||
env_file: .env
|
||||
|
||||
# Maximum job duration (also limited by Gitea instance).
|
||||
timeout: 3h
|
||||
|
||||
# Grace period for jobs during shutdown.
|
||||
shutdown_timeout: 0s
|
||||
|
||||
# Skip TLS verification. WARNING: Security risk if true.
|
||||
insecure: false
|
||||
|
||||
# Job polling settings.
|
||||
fetch_timeout: 5s
|
||||
fetch_interval: 2s
|
||||
|
||||
# Labels determine which jobs this runner handles.
|
||||
labels:
|
||||
{% for label in act_runner_labels %}
|
||||
- "{{ label }}"
|
||||
{% endfor %}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Cache Server Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
cache:
|
||||
# Enable built-in cache server for actions/cache.
|
||||
enabled: true
|
||||
|
||||
# Cache storage directory (empty = default).
|
||||
dir: ""
|
||||
|
||||
# Network settings (empty = auto-detect).
|
||||
host: ""
|
||||
port: 0
|
||||
|
||||
# External cache server URL (empty = use built-in).
|
||||
external_server: ""
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Container Execution Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
container:
|
||||
# Docker network (empty = isolated per job).
|
||||
network: ""
|
||||
|
||||
# Privileged mode. WARNING: Security risk if true.
|
||||
privileged: false
|
||||
|
||||
# Additional docker run options.
|
||||
options:
|
||||
|
||||
# Working directory inside containers.
|
||||
workdir_parent:
|
||||
|
||||
# Allowed volume mounts (empty = none, ["**"] = any).
|
||||
valid_volumes: []
|
||||
|
||||
# Docker daemon (empty = auto-detect).
|
||||
docker_host: ""
|
||||
|
||||
# Always pull images before jobs.
|
||||
force_pull: {{ act_runner_container_force_pull | lower }}
|
||||
|
||||
# Rebuild images even if they exist.
|
||||
force_rebuild: false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Host Execution Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
host:
|
||||
# Working directory for host execution.
|
||||
workdir_parent:
|
||||
151
roles/gitea/defaults/main.yml
Normal file
151
roles/gitea/defaults/main.yml
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Role Defaults
|
||||
# =============================================================================
|
||||
#
|
||||
# Default values for Gitea role variables.
|
||||
# These have the LOWEST precedence and can be overridden by:
|
||||
# - group_vars/all/vars.yml
|
||||
# - inventory host_vars
|
||||
# - command line --extra-vars
|
||||
#
|
||||
# Reference: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable
|
||||
# Reference: https://docs.gitea.com/administration/config-cheat-sheet
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Version Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# Using major.minor pinning for automatic security patch updates.
|
||||
# Reference: https://hub.docker.com/r/gitea/gitea
|
||||
# Reference: https://hub.docker.com/_/postgres
|
||||
|
||||
gitea_version: "1.25"
|
||||
gitea_postgres_version: "17-alpine"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Container Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# Service names for docker compose commands
|
||||
gitea_service_name: "server"
|
||||
gitea_db_service_name: "db"
|
||||
|
||||
# Container names for docker exec commands (may differ from service names)
|
||||
gitea_container_name: "gitea"
|
||||
gitea_db_container_name: "gitea-db-1"
|
||||
|
||||
# User/Group IDs for container processes
|
||||
# These should match existing volume ownership
|
||||
gitea_user_uid: 1002
|
||||
gitea_user_gid: 1004
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Database Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
gitea_db_type: "postgres"
|
||||
gitea_db_host: "db:5432"
|
||||
gitea_db_name: "gitea"
|
||||
gitea_db_user: "gitea"
|
||||
# gitea_db_password: MUST be set via vault mapping in group_vars/all/vars.yml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Network Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# HTTP port inside container (external 443 maps to this via ACME)
|
||||
gitea_http_port: 3000
|
||||
|
||||
# SSH port configuration
|
||||
gitea_ssh_port: 22
|
||||
gitea_ssh_listen_port: 22
|
||||
gitea_ssh_external_port: 2222
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Security Hardening - Password & Authentication
|
||||
# -----------------------------------------------------------------------------
|
||||
# Reference: https://docs.gitea.com/administration/config-cheat-sheet#security-security
|
||||
# Reference: https://onappsec.com/gitea-configuration-hardening/
|
||||
|
||||
# Password hashing algorithm
|
||||
# Options: argon2, pbkdf2, pbkdf2_v1, pbkdf2_hi, scrypt, bcrypt
|
||||
# argon2 is the strongest and recommended choice
|
||||
gitea_password_hash_algo: "argon2"
|
||||
|
||||
# Password complexity requirements
|
||||
# Options: off, lower, upper, digit, spec (comma-separated)
|
||||
gitea_password_complexity: "lower,upper,digit,spec"
|
||||
|
||||
# Minimum password length (NIST SP 800-63B recommends 8+)
|
||||
gitea_min_password_length: 12
|
||||
|
||||
# Check passwords against HaveIBeenPwned database
|
||||
gitea_password_check_pwn: true
|
||||
|
||||
# Two-factor authentication is enabled by default in Gitea 1.25+
|
||||
# No configuration needed - users can enable 2FA in their account settings
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Security Hardening - API & Features
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Disable Git hooks (prevents arbitrary code execution via hooks)
|
||||
gitea_disable_git_hooks: true
|
||||
|
||||
# Reject API tokens passed in URL query strings (header-based only)
|
||||
# Prevents token leakage in server logs and browser history
|
||||
gitea_disable_query_auth_token: true
|
||||
|
||||
# Webhook allowed destination hosts
|
||||
# Options: loopback, private, external, *, or CIDR list
|
||||
gitea_webhook_allowed_hosts: "external"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Security Hardening - Session & Cookies
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Force HTTPS for session cookies
|
||||
gitea_cookie_secure: true
|
||||
|
||||
# SameSite cookie policy (strict prevents CSRF)
|
||||
# Options: lax, strict, none
|
||||
gitea_same_site: "strict"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Security Hardening - TLS
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Minimum TLS version (disable older vulnerable protocols)
|
||||
gitea_ssl_min_version: "TLSv1.2"
|
||||
|
||||
# ACME/Let's Encrypt automatic certificate provisioning
|
||||
gitea_enable_acme: true
|
||||
gitea_acme_accept_tos: true
|
||||
gitea_acme_directory: "https"
|
||||
# gitea_acme_email: SHOULD be set via vault mapping in group_vars/all/vars.yml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Service Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Disable public registration (admin-only account creation)
|
||||
gitea_disable_registration: true
|
||||
|
||||
# Require sign-in to view any content
|
||||
gitea_require_signin_view: false
|
||||
|
||||
# LFS (Large File Storage) support
|
||||
gitea_lfs_enabled: true
|
||||
|
||||
# Offline mode (don't fetch external resources like Gravatar)
|
||||
gitea_offline_mode: true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Backup Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Backup directory (relative to gitea_install_dir)
|
||||
gitea_backup_dir: "backups"
|
||||
|
||||
# Number of backup sets to retain
|
||||
gitea_backup_retention: 5
|
||||
23
roles/gitea/handlers/main.yml
Normal file
23
roles/gitea/handlers/main.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Role Handlers
|
||||
# =============================================================================
|
||||
#
|
||||
# Handlers are triggered by 'notify' in tasks and run at the end of the play.
|
||||
# They're typically used for service restarts after configuration changes.
|
||||
#
|
||||
# In this role, most restarts happen via docker compose in deploy.yml,
|
||||
# so handlers are minimal. These are provided for ad-hoc config changes.
|
||||
# =============================================================================
|
||||
|
||||
- name: Restart gitea
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -f {{ gitea_install_dir }}/docker-compose.yml restart {{ gitea_container_name }}"
|
||||
listen: "restart gitea"
|
||||
changed_when: true
|
||||
|
||||
- name: Restart gitea services
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -f {{ gitea_install_dir }}/docker-compose.yml up -d --wait"
|
||||
listen: "restart gitea services"
|
||||
changed_when: true
|
||||
138
roles/gitea/tasks/backup.yml
Normal file
138
roles/gitea/tasks/backup.yml
Normal file
@@ -0,0 +1,138 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Backup Tasks - Using Official Gitea Dump
|
||||
# =============================================================================
|
||||
#
|
||||
# Uses the official `gitea dump` command for comprehensive backup.
|
||||
# Reference: https://docs.gitea.com/administration/backup-and-restore
|
||||
#
|
||||
# The dump creates a ZIP containing:
|
||||
# - app.ini and custom/ directory (configuration)
|
||||
# - All git repositories
|
||||
# - Database SQL dump (gitea-db.sql)
|
||||
# - Attachments, avatars, LFS objects
|
||||
#
|
||||
# Note: We run the dump while Gitea is running. The official docs recommend
|
||||
# stopping Gitea for perfect consistency, but docker exec requires a running
|
||||
# container. The consistency risk during the brief dump operation is minimal.
|
||||
# =============================================================================
|
||||
|
||||
# Set up variables for this backup run.
|
||||
# gitea_backup_timestamp: Used in filename for unique identification (e.g., 20260114T143022)
|
||||
# gitea_backup_dir_path: Where backups are stored on the host filesystem
|
||||
- name: Set backup variables
|
||||
ansible.builtin.set_fact:
|
||||
gitea_backup_timestamp: "{{ ansible_facts['date_time']['iso8601_basic_short'] }}"
|
||||
gitea_backup_dir_path: "{{ gitea_install_dir }}/{{ gitea_backup_dir }}"
|
||||
|
||||
# Ensure the backup directory exists on the host.
|
||||
# Mode 0750: owner read/write/execute, group read/execute, others no access
|
||||
- name: Ensure backup directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_backup_dir_path }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Create Backup Using Official Gitea Dump
|
||||
# -----------------------------------------------------------------------------
|
||||
# Run the official gitea dump command inside the running container.
|
||||
#
|
||||
# Command breakdown:
|
||||
# docker exec {{ container }} - Execute command inside the container
|
||||
# /usr/local/bin/gitea dump - The gitea binary with dump subcommand
|
||||
# -c /data/gitea/conf/app.ini - Path to config file (tells gitea where data is)
|
||||
# -w /tmp - Working directory for temporary files
|
||||
# -f /tmp/gitea-backup-*.zip - Output filename for the backup archive
|
||||
#
|
||||
# Note: We write to /tmp inside the container first, then copy out.
|
||||
# This avoids permission issues with mounted volumes.
|
||||
|
||||
- name: Run gitea dump command
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker exec -u git {{ gitea_container_name }}
|
||||
/usr/local/bin/gitea dump
|
||||
-c /data/gitea/conf/app.ini
|
||||
-w /tmp
|
||||
-f /tmp/gitea-backup-{{ gitea_backup_timestamp }}.zip
|
||||
register: gitea_dump_result
|
||||
changed_when: true
|
||||
|
||||
# Copy backup from container to host filesystem
|
||||
# docker cp copies files between container and host.
|
||||
# Format: docker cp container:/path/in/container /path/on/host
|
||||
- name: Copy backup from container to host
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker cp
|
||||
{{ gitea_container_name }}:/tmp/gitea-backup-{{ gitea_backup_timestamp }}.zip
|
||||
{{ gitea_backup_dir_path }}/gitea-backup-{{ gitea_backup_timestamp }}.zip
|
||||
changed_when: true
|
||||
|
||||
# Clean up the backup file inside the container
|
||||
# We don't want to leave large files in the container's /tmp
|
||||
# failed_when: false - Don't fail if cleanup fails (backup already succeeded)
|
||||
- name: Remove backup file from container
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker exec -u git {{ gitea_container_name }}
|
||||
rm /tmp/gitea-backup-{{ gitea_backup_timestamp }}.zip
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Backup Verification
|
||||
# -----------------------------------------------------------------------------
|
||||
# Verify the backup was actually created and is not empty.
|
||||
# This catches cases where the command succeeded but produced no output.
|
||||
|
||||
- name: Verify backup file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_backup_dir_path }}/gitea-backup-{{ gitea_backup_timestamp }}.zip"
|
||||
register: gitea_backup_file
|
||||
|
||||
# Display backup info for operator visibility
|
||||
# Size calculation: bytes / 1048576 = megabytes (1024*1024)
|
||||
- name: Display backup info
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Backup completed: {{ gitea_backup_dir_path }}/gitea-backup-{{ gitea_backup_timestamp }}.zip
|
||||
Size: {{ (gitea_backup_file.stat.size / 1048576) | round(2) }}MB
|
||||
when: gitea_backup_file.stat.exists | default(false)
|
||||
|
||||
- name: Fail if backup file is missing or empty
|
||||
ansible.builtin.fail:
|
||||
msg: "Backup file is missing or empty. Cannot proceed."
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- not gitea_backup_file.stat.exists or gitea_backup_file.stat.size == 0
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Backup Rotation
|
||||
# -----------------------------------------------------------------------------
|
||||
# Keep only the N most recent backups (controlled by gitea_backup_retention).
|
||||
# This prevents disk space from being exhausted by old backups.
|
||||
|
||||
# Find all existing backup files matching our naming pattern
|
||||
- name: Find old backup files
|
||||
ansible.builtin.find:
|
||||
paths: "{{ gitea_backup_dir_path }}"
|
||||
patterns: "gitea-backup-*.zip"
|
||||
file_type: file
|
||||
register: gitea_old_backup_files
|
||||
|
||||
# Delete old backups, keeping only the most recent ones.
|
||||
# Logic breakdown:
|
||||
# gitea_old_backup_files.files | sort(attribute='mtime') - Sort by modification time (oldest first)
|
||||
# [:-gitea_backup_retention] - Slice: all except last N items
|
||||
# (Python slice notation: [:-5] = all but last 5)
|
||||
# Example: If we have 7 backups and retention=5, this deletes the 2 oldest.
|
||||
- name: Remove old backups beyond retention limit
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ (gitea_old_backup_files.files | sort(attribute='mtime'))[:-gitea_backup_retention] }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
when: gitea_old_backup_files.files | length > gitea_backup_retention
|
||||
139
roles/gitea/tasks/config.yml
Normal file
139
roles/gitea/tasks/config.yml
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Configuration Tasks - Update Domain Settings
|
||||
# =============================================================================
|
||||
#
|
||||
# Updates Gitea's app.ini configuration file with the new domain.
|
||||
#
|
||||
# The app.ini file lives inside the Docker volume at /data/gitea/conf/app.ini.
|
||||
# We extract it, modify it on the host, then copy it back into the container.
|
||||
#
|
||||
# Settings that need updating for domain rename:
|
||||
# [server]
|
||||
# DOMAIN = git.veridion.ru (web domain)
|
||||
# SSH_DOMAIN = git.veridion.ru (SSH clone URLs)
|
||||
# ROOT_URL = https://git.veridion.ru/ (base URL for all links)
|
||||
#
|
||||
# Reference: https://docs.gitea.com/administration/config-cheat-sheet
|
||||
# =============================================================================
|
||||
|
||||
# Create a temporary directory on the host for config editing.
|
||||
# We'll extract app.ini here, modify it, then copy back.
|
||||
# check_mode: false - tempfile is harmless, needed for subsequent tasks
|
||||
- name: Create temporary directory for config editing
|
||||
ansible.builtin.tempfile:
|
||||
state: directory
|
||||
prefix: gitea_config_
|
||||
register: gitea_config_temp_dir
|
||||
check_mode: false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Extract Configuration from Container
|
||||
# -----------------------------------------------------------------------------
|
||||
# docker cp extracts files from a container to the host filesystem.
|
||||
# Format: docker cp <container>:<path_in_container> <path_on_host>
|
||||
# check_mode: false - read-only extraction needed for lineinfile to evaluate changes
|
||||
|
||||
- name: Extract app.ini from Gitea container
|
||||
ansible.builtin.command:
|
||||
cmd: "docker cp {{ gitea_container_name }}:/data/gitea/conf/app.ini {{ gitea_config_temp_dir.path }}/app.ini"
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Update Domain Settings
|
||||
# -----------------------------------------------------------------------------
|
||||
# Using lineinfile module to update specific settings in app.ini.
|
||||
# Each task finds a line matching the regexp and replaces it.
|
||||
#
|
||||
# lineinfile parameters:
|
||||
# path: File to modify
|
||||
# regexp: Pattern to find (uses Python regex)
|
||||
# line: Replacement line
|
||||
# backrefs: If true, allows using \1, \2 for captured groups (not used here)
|
||||
#
|
||||
# The regexp patterns:
|
||||
# ^DOMAIN\s*= Matches "DOMAIN = " at start of line, with any whitespace
|
||||
# ^\s*DOMAIN\s*= Would also match indented lines (not typical in app.ini)
|
||||
|
||||
- name: Update DOMAIN setting in app.ini
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ gitea_config_temp_dir.path }}/app.ini"
|
||||
regexp: '^DOMAIN\s*='
|
||||
line: "DOMAIN = {{ gitea_domain }}"
|
||||
register: gitea_domain_updated
|
||||
|
||||
- name: Update SSH_DOMAIN setting in app.ini
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ gitea_config_temp_dir.path }}/app.ini"
|
||||
regexp: '^SSH_DOMAIN\s*='
|
||||
line: "SSH_DOMAIN = {{ gitea_ssh_domain }}"
|
||||
register: gitea_ssh_domain_updated
|
||||
|
||||
# ROOT_URL must include the protocol (https://) and trailing slash
|
||||
- name: Update ROOT_URL setting in app.ini
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ gitea_config_temp_dir.path }}/app.ini"
|
||||
regexp: '^ROOT_URL\s*='
|
||||
line: "ROOT_URL = {{ gitea_root_url }}/"
|
||||
register: gitea_root_url_updated
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Apply Security Hardening (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# These settings enhance security. They're applied during domain update
|
||||
# since we're already modifying the config.
|
||||
#
|
||||
# Each setting is conditional on whether the variable is defined,
|
||||
# allowing operators to skip specific hardening options.
|
||||
|
||||
# Password hashing: argon2 is more secure than pbkdf2 (Gitea default)
|
||||
- name: Update password hash algorithm
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ gitea_config_temp_dir.path }}/app.ini"
|
||||
regexp: '^PASSWORD_HASH_ALGO\s*='
|
||||
line: "PASSWORD_HASH_ALGO = {{ gitea_password_hash_algo }}"
|
||||
insertafter: '^\[security\]'
|
||||
when: gitea_password_hash_algo is defined
|
||||
|
||||
# Disable git hooks to prevent arbitrary code execution
|
||||
- name: Update git hooks setting
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ gitea_config_temp_dir.path }}/app.ini"
|
||||
regexp: '^DISABLE_GIT_HOOKS\s*='
|
||||
line: "DISABLE_GIT_HOOKS = {{ gitea_disable_git_hooks | lower }}"
|
||||
insertafter: '^\[security\]'
|
||||
when: gitea_disable_git_hooks is defined
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Copy Updated Configuration Back to Container
|
||||
# -----------------------------------------------------------------------------
|
||||
# docker cp can also copy from host to container.
|
||||
# Format: docker cp <path_on_host> <container>:<path_in_container>
|
||||
|
||||
- name: Copy updated app.ini back to container
|
||||
ansible.builtin.command:
|
||||
cmd: "docker cp {{ gitea_config_temp_dir.path }}/app.ini {{ gitea_container_name }}:/data/gitea/conf/app.ini"
|
||||
changed_when: true
|
||||
when: gitea_domain_updated.changed or gitea_ssh_domain_updated.changed or gitea_root_url_updated.changed
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Cleanup
|
||||
# -----------------------------------------------------------------------------
|
||||
# Remove the temporary directory we created.
|
||||
# check_mode: false - clean up the temp dir we created with check_mode: false
|
||||
|
||||
- name: Remove temporary config directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ gitea_config_temp_dir.path }}"
|
||||
state: absent
|
||||
check_mode: false
|
||||
|
||||
# Display summary of changes for operator visibility
|
||||
- name: Display configuration changes
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Configuration updated:
|
||||
DOMAIN = {{ gitea_domain }}
|
||||
SSH_DOMAIN = {{ gitea_ssh_domain }}
|
||||
ROOT_URL = {{ gitea_root_url }}/
|
||||
126
roles/gitea/tasks/deploy.yml
Normal file
126
roles/gitea/tasks/deploy.yml
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Deploy Tasks - Pull Images and Restart Services
|
||||
# =============================================================================
|
||||
#
|
||||
# Performs the actual deployment:
|
||||
# 1. Pull the new Docker images
|
||||
# 2. Restart services with --wait flag (waits for healthchecks)
|
||||
# 3. Verify the deployment
|
||||
#
|
||||
# This task relies on healthchecks defined in docker-compose.yml:
|
||||
# - PostgreSQL: pg_isready checks database is accepting connections
|
||||
# - Gitea: curl to /api/healthz checks web server is responding
|
||||
#
|
||||
# The --wait flag makes docker compose block until all services are healthy.
|
||||
# This is cleaner than manually polling with Ansible loops.
|
||||
#
|
||||
# Database migrations happen AUTOMATICALLY when Gitea starts with a new
|
||||
# version. The start_period in Gitea's healthcheck allows time for this.
|
||||
#
|
||||
# Reference: https://docs.gitea.com/installation/upgrade-from-gitea
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pull New Docker Images
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pull images before stopping services to minimize downtime.
|
||||
# This downloads the new image layers while the old containers still run.
|
||||
|
||||
- name: Pull new Docker images
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -f {{ gitea_install_dir }}/docker-compose.yml pull"
|
||||
register: gitea_docker_pull
|
||||
changed_when: "'Pulled' in gitea_docker_pull.stdout or 'Downloaded' in gitea_docker_pull.stdout"
|
||||
|
||||
- name: Display image pull results
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ gitea_docker_pull.stdout_lines | default(['Images already up to date']) }}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Restart Services with Healthcheck Wait
|
||||
# -----------------------------------------------------------------------------
|
||||
# docker compose up -d --wait:
|
||||
# -d : Detached mode (run in background)
|
||||
# --wait : Block until all services are healthy (based on healthcheck)
|
||||
#
|
||||
# This single command:
|
||||
# 1. Stops old containers if config changed
|
||||
# 2. Creates new containers with new images
|
||||
# 3. Waits for healthchecks to pass
|
||||
#
|
||||
# Timeout is set high (5 minutes) to allow for:
|
||||
# - Database startup
|
||||
# - Gitea migrations (can take time on version upgrades)
|
||||
# - ACME certificate issuance (30-60 seconds for new domain)
|
||||
|
||||
- name: Restart services and wait for healthy
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -f {{ gitea_install_dir }}/docker-compose.yml up -d --wait"
|
||||
chdir: "{{ gitea_install_dir }}"
|
||||
register: gitea_compose_up
|
||||
changed_when: true
|
||||
# 5 minute timeout for migrations + ACME certificate
|
||||
timeout: 300
|
||||
|
||||
- name: Display compose output
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ gitea_compose_up.stdout_lines | default(['Services started']) }}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Verify Deployment
|
||||
# -----------------------------------------------------------------------------
|
||||
# Additional verification beyond healthchecks.
|
||||
|
||||
# Check container health status
|
||||
# check_mode: false - read-only, shows current state during dry run
|
||||
- name: Verify container health status
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -f {{ gitea_install_dir }}/docker-compose.yml ps --format json"
|
||||
register: gitea_compose_status
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Display container status
|
||||
ansible.builtin.debug:
|
||||
msg: "Container status: {{ gitea_compose_status.stdout }}"
|
||||
|
||||
# Check Gitea logs for migration activity
|
||||
# check_mode: false - read-only, shows current logs during dry run
|
||||
- name: Check Gitea logs for errors or migrations
|
||||
ansible.builtin.command:
|
||||
cmd: "docker logs --tail 30 {{ gitea_container_name }}"
|
||||
register: gitea_logs
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Display recent Gitea logs
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ gitea_logs.stdout_lines[-10:] | default(['No logs available']) }}"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Final Summary
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
- name: Deployment summary
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
============================================
|
||||
DEPLOYMENT COMPLETE
|
||||
============================================
|
||||
Domain: https://{{ gitea_domain }}/
|
||||
SSH: ssh://git@{{ gitea_domain }}:{{ gitea_ssh_external_port }}/
|
||||
Gitea: {{ gitea_version }}
|
||||
PostgreSQL: {{ gitea_postgres_version }}
|
||||
|
||||
Healthchecks passed - services are running.
|
||||
|
||||
Verify manually:
|
||||
1. Login at https://{{ gitea_domain }}/
|
||||
2. Clone a repo via HTTPS and SSH
|
||||
3. Check Settings > Admin for version info
|
||||
|
||||
If act-runner was configured, update its config:
|
||||
vault_gitea_instance_url: https://{{ gitea_domain }}
|
||||
vault_gitea_registry: {{ gitea_domain }}
|
||||
============================================
|
||||
39
roles/gitea/tasks/main.yml
Normal file
39
roles/gitea/tasks/main.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Gitea Role - Main Task Orchestration
|
||||
# =============================================================================
|
||||
#
|
||||
# This file controls the execution order of all tasks.
|
||||
# Tasks are designed to be idempotent and safe to run multiple times.
|
||||
#
|
||||
# IMPORTANT: Database migrations are handled AUTOMATICALLY by Gitea on startup.
|
||||
# The backup task MUST run before any changes to allow rollback.
|
||||
#
|
||||
# Reference: https://docs.gitea.com/installation/upgrade-from-gitea
|
||||
# =============================================================================
|
||||
|
||||
- name: Include preflight checks
|
||||
ansible.builtin.include_tasks: preflight.yml
|
||||
tags:
|
||||
- preflight
|
||||
- always
|
||||
|
||||
- name: Include backup tasks
|
||||
ansible.builtin.include_tasks: backup.yml
|
||||
tags:
|
||||
- backup
|
||||
|
||||
- name: Include configuration tasks
|
||||
ansible.builtin.include_tasks: config.yml
|
||||
tags:
|
||||
- config
|
||||
|
||||
- name: Include upgrade tasks
|
||||
ansible.builtin.include_tasks: upgrade.yml
|
||||
tags:
|
||||
- upgrade
|
||||
|
||||
- name: Include deployment tasks
|
||||
ansible.builtin.include_tasks: deploy.yml
|
||||
tags:
|
||||
- deploy
|
||||
144
roles/gitea/tasks/preflight.yml
Normal file
144
roles/gitea/tasks/preflight.yml
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Preflight Checks
|
||||
# =============================================================================
|
||||
#
|
||||
# Validates prerequisites before making any changes.
|
||||
# Fails fast with clear error messages if requirements are not met.
|
||||
#
|
||||
# Reference: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/assert_module.html
|
||||
# =============================================================================
|
||||
|
||||
- name: Verify required variables are defined
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- gitea_domain is defined
|
||||
- gitea_domain | length > 0
|
||||
- gitea_db_password is defined
|
||||
- gitea_db_password | length > 0
|
||||
- gitea_install_dir is defined
|
||||
fail_msg: >-
|
||||
Required variables missing. Ensure vault.yml contains:
|
||||
vault_gitea_domain, vault_gitea_db_password.
|
||||
Ensure inventory contains: gitea_install_dir.
|
||||
quiet: true
|
||||
|
||||
- name: Check if Docker is installed
|
||||
ansible.builtin.command:
|
||||
cmd: docker --version
|
||||
register: gitea_docker_check
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
failed_when: gitea_docker_check.rc != 0
|
||||
|
||||
- name: Verify Docker daemon is running
|
||||
ansible.builtin.command:
|
||||
cmd: docker info
|
||||
register: gitea_docker_info
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
failed_when: gitea_docker_info.rc != 0
|
||||
|
||||
- name: Check if Gitea install directory exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_install_dir }}"
|
||||
register: gitea_dir_stat
|
||||
|
||||
- name: Verify Gitea install directory exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- gitea_dir_stat.stat.exists
|
||||
- gitea_dir_stat.stat.isdir
|
||||
fail_msg: "Gitea install directory not found: {{ gitea_install_dir }}"
|
||||
quiet: true
|
||||
|
||||
- name: Check if docker-compose.yml exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ gitea_install_dir }}/docker-compose.yml"
|
||||
register: gitea_compose_stat
|
||||
|
||||
- name: Verify docker-compose.yml exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- gitea_compose_stat.stat.exists
|
||||
fail_msg: "docker-compose.yml not found in {{ gitea_install_dir }}"
|
||||
quiet: true
|
||||
|
||||
# Find the mount point containing gitea_install_dir using df command.
|
||||
# This is more reliable than substring matching on ansible_mounts.
|
||||
# check_mode: false - df is read-only, safe to run even in --check mode
|
||||
- name: Find mount point for install directory
|
||||
ansible.builtin.command:
|
||||
cmd: "df --output=target {{ gitea_install_dir }}"
|
||||
register: gitea_df_result
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
# Parse mount point from df output (first line is header, second is mount)
|
||||
- name: Parse mount point from df output
|
||||
ansible.builtin.set_fact:
|
||||
gitea_mount_point: "{{ gitea_df_result.stdout_lines[-1] | trim }}"
|
||||
|
||||
# Look up full mount info (size_available, etc.) from gathered facts
|
||||
- name: Get mount info from ansible_facts
|
||||
ansible.builtin.set_fact:
|
||||
gitea_install_mount: "{{ ansible_facts['mounts'] | selectattr('mount', 'equalto', gitea_mount_point) | first }}"
|
||||
|
||||
# Check available space: 2GB = 2 * 1024^3 = 2147483648 bytes
|
||||
- name: Verify sufficient disk space (minimum 2GB)
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- gitea_install_mount.size_available > 2147483648
|
||||
fail_msg: >-
|
||||
Insufficient disk space on {{ gitea_install_mount.mount }}.
|
||||
Available: {{ (gitea_install_mount.size_available / 1073741824) | round(2) }}GB.
|
||||
Minimum required: 2GB.
|
||||
quiet: true
|
||||
|
||||
- name: Check if Gitea container is running
|
||||
ansible.builtin.command:
|
||||
cmd: docker ps --filter "name={{ gitea_container_name }}" --format "{{ '{{' }}.Names{{ '}}' }}"
|
||||
register: gitea_container_check
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Display Gitea container status
|
||||
ansible.builtin.debug:
|
||||
msg: "Gitea container status: {{ 'running' if gitea_container_name in gitea_container_check.stdout else 'not running' }}"
|
||||
|
||||
- name: Check if database container is running
|
||||
ansible.builtin.command:
|
||||
cmd: docker ps --filter "name={{ gitea_db_container_name }}" --format "{{ '{{' }}.Names{{ '}}' }}"
|
||||
register: gitea_db_container_check
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Display database container status
|
||||
ansible.builtin.debug:
|
||||
msg: "Database container status: {{ 'running' if gitea_db_container_name in gitea_db_container_check.stdout else 'not running' }}"
|
||||
|
||||
# Verify DNS is configured before proceeding.
|
||||
# ACME certificate issuance will fail without valid DNS.
|
||||
- name: Check DNS resolution for domain
|
||||
ansible.builtin.command:
|
||||
cmd: "dig +short {{ gitea_domain }}"
|
||||
register: gitea_dns_check
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
failed_when: false
|
||||
|
||||
- name: Display DNS resolution result
|
||||
ansible.builtin.debug:
|
||||
msg: "DNS for {{ gitea_domain }} resolves to: {{ gitea_dns_check.stdout_lines | default(['UNRESOLVED']) | join(', ') }}"
|
||||
|
||||
# Fail if DNS doesn't resolve (can be skipped with gitea_skip_gitea_dns_check=true)
|
||||
- name: Verify DNS resolves for domain
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
DNS for {{ gitea_domain }} does not resolve.
|
||||
ACME certificate issuance will fail without valid DNS.
|
||||
Ensure A record points to this server before proceeding.
|
||||
To skip this check, set gitea_skip_gitea_dns_check=true.
|
||||
when:
|
||||
- gitea_dns_check.stdout | length == 0
|
||||
- not (gitea_skip_gitea_dns_check | default(false) | bool)
|
||||
67
roles/gitea/tasks/upgrade.yml
Normal file
67
roles/gitea/tasks/upgrade.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Upgrade Tasks - Deploy New docker-compose.yml
|
||||
# =============================================================================
|
||||
#
|
||||
# Deploys the templated docker-compose.yml with the new Gitea version.
|
||||
#
|
||||
# This task uses Ansible's template module to generate docker-compose.yml
|
||||
# from a Jinja2 template. The template allows us to:
|
||||
# - Update the Gitea image version
|
||||
# - Update the PostgreSQL image version
|
||||
# - Apply configuration from Ansible variables
|
||||
#
|
||||
# The template is stored at: roles/gitea/templates/docker-compose.yml.j2
|
||||
#
|
||||
# After this task, the docker-compose.yml on the server will reference
|
||||
# the new image versions, but containers aren't restarted yet.
|
||||
# That happens in deploy.yml.
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Backup Current docker-compose.yml
|
||||
# -----------------------------------------------------------------------------
|
||||
# Even though we have a full gitea dump backup, it's useful to have
|
||||
# the docker-compose.yml readily available for quick rollback.
|
||||
|
||||
- name: Backup current docker-compose.yml before upgrade
|
||||
ansible.builtin.copy:
|
||||
src: "{{ gitea_install_dir }}/docker-compose.yml"
|
||||
dest: "{{ gitea_install_dir }}/docker-compose.yml.backup"
|
||||
remote_src: true
|
||||
mode: "0640"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Deploy New docker-compose.yml from Template
|
||||
# -----------------------------------------------------------------------------
|
||||
# The template module:
|
||||
# 1. Reads the Jinja2 template (docker-compose.yml.j2)
|
||||
# 2. Substitutes all {{ variable }} placeholders with actual values
|
||||
# 3. Writes the result to the destination path
|
||||
#
|
||||
# Key variables used in the template:
|
||||
# gitea_version - Docker image tag (e.g., "1.25")
|
||||
# postgres_version - PostgreSQL image tag (e.g., "17-alpine")
|
||||
# gitea_db_password - Database password (from vault)
|
||||
# gitea_user_uid/gid - Container user/group IDs
|
||||
# gitea_http_port - Internal HTTP port (3000)
|
||||
# gitea_ssh_port - Internal SSH port (22)
|
||||
# gitea_ssh_external_port - External SSH port (2222)
|
||||
|
||||
- name: Deploy docker-compose.yml from template
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ gitea_install_dir }}/docker-compose.yml"
|
||||
mode: "0640"
|
||||
# Validate the YAML syntax before deploying
|
||||
validate: "docker compose -f %s config --quiet"
|
||||
register: gitea_compose_deployed
|
||||
|
||||
# Display what changed for operator visibility
|
||||
- name: Display upgrade info
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
docker-compose.yml updated:
|
||||
Gitea version: {{ gitea_version }}
|
||||
PostgreSQL version: {{ gitea_postgres_version }}
|
||||
Template changed: {{ gitea_compose_deployed.changed }}
|
||||
72
roles/gitea/templates/docker-compose.yml.j2
Normal file
72
roles/gitea/templates/docker-compose.yml.j2
Normal file
@@ -0,0 +1,72 @@
|
||||
# Gitea with PostgreSQL - Docker Compose
|
||||
#
|
||||
# Based on: https://docs.gitea.com/installation/install-with-docker
|
||||
# Healthchecks from: https://docs.gitea.com/installation/install-on-kubernetes
|
||||
# https://github.com/go-gitea/gitea/pull/35513
|
||||
#
|
||||
# Generated by Ansible - do not edit manually.
|
||||
|
||||
networks:
|
||||
gitea:
|
||||
external: false
|
||||
|
||||
volumes:
|
||||
gitea:
|
||||
driver: local
|
||||
postgres:
|
||||
driver: local
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:{{ gitea_postgres_version }}
|
||||
container_name: {{ gitea_db_container_name }}
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER={{ gitea_db_user }}
|
||||
- POSTGRES_PASSWORD={{ gitea_db_password }}
|
||||
- POSTGRES_DB={{ gitea_db_name }}
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
# PostgreSQL readiness check
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "{{ gitea_db_user }}", "-d", "{{ gitea_db_name }}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
server:
|
||||
image: docker.io/gitea/gitea:{{ gitea_version }}
|
||||
container_name: {{ gitea_container_name }}
|
||||
restart: always
|
||||
environment:
|
||||
- USER_UID={{ gitea_user_uid }}
|
||||
- USER_GID={{ gitea_user_gid }}
|
||||
- GITEA__database__DB_TYPE={{ gitea_db_type }}
|
||||
- GITEA__database__HOST={{ gitea_db_host }}
|
||||
- GITEA__database__NAME={{ gitea_db_name }}
|
||||
- GITEA__database__USER={{ gitea_db_user }}
|
||||
- GITEA__database__PASSWD={{ gitea_db_password }}
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- /home/git/.ssh/:/data/git/.ssh
|
||||
- gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "443:{{ gitea_http_port }}"
|
||||
- "127.0.0.1:{{ gitea_ssh_external_port }}:{{ gitea_ssh_port }}"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
# Gitea health endpoint (per K8s docs and PR #35513)
|
||||
# start_period allows time for database migrations on upgrade
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fSs", "http://127.0.0.1:{{ gitea_http_port }}/api/healthz"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
23
site.yml
Normal file
23
site.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Site Playbook - Deploy All Services
|
||||
# =============================================================================
|
||||
#
|
||||
# Deploys all services in the correct order.
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory/hosts.yml site.yml --ask-vault-pass
|
||||
#
|
||||
# Deploy specific service:
|
||||
# ansible-playbook -i inventory/hosts.yml site.yml --tags gitea --ask-vault-pass
|
||||
# ansible-playbook -i inventory/hosts.yml site.yml --tags act_runner --ask-vault-pass
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
- name: Deploy Gitea
|
||||
ansible.builtin.import_playbook: gitea.yml
|
||||
tags: [gitea]
|
||||
|
||||
- name: Deploy Act Runner
|
||||
ansible.builtin.import_playbook: act-runner.yml
|
||||
tags: [act_runner]
|
||||
Reference in New Issue
Block a user