Add Template to deploy forgejo.
This template allows deploying a forgejo en either Scaleway or Hetzner (untested) without much knowledge about them. It DOES require knowledge about Terragrunt and ansible. A wizard of sorts is provided but it will not guarantee success without some knowledge about the underlying technology.
This commit is contained in:
parent
a9f546f92a
commit
822e42dbb8
48 changed files with 6846 additions and 2 deletions
145
ansible/roles/forgejo/defaults/main.yml
Normal file
145
ansible/roles/forgejo/defaults/main.yml
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
# Default variables for Forgejo role
|
||||
|
||||
# Forgejo version
|
||||
forgejo_version: "9.0.2"
|
||||
forgejo_docker_image: "codeberg.org/forgejo/forgejo"
|
||||
|
||||
# System user and group
|
||||
forgejo_user: git
|
||||
forgejo_group: git
|
||||
forgejo_uid: 1100
|
||||
forgejo_gid: 1100
|
||||
|
||||
# Installation paths
|
||||
forgejo_base_path: /opt/forgejo
|
||||
forgejo_data_path: "{{ forgejo_base_path }}/data"
|
||||
forgejo_config_path: "{{ forgejo_base_path }}/config"
|
||||
forgejo_custom_path: "{{ forgejo_base_path }}/custom"
|
||||
forgejo_backup_path: "{{ forgejo_base_path }}/backups"
|
||||
|
||||
# External volume (if using cloud provider block storage)
|
||||
forgejo_use_external_volume: false
|
||||
forgejo_volume_device: /dev/sdb
|
||||
forgejo_volume_mount: /mnt/forgejo-data
|
||||
|
||||
# Network configuration
|
||||
# NOTE: Set your actual domain in ansible/inventory/production/hosts.yml
|
||||
# The value here is just a fallback default.
|
||||
forgejo_domain: git.example.com
|
||||
forgejo_http_port: 3000
|
||||
forgejo_ssh_port: 2222
|
||||
forgejo_protocol: https
|
||||
|
||||
# Database configuration
|
||||
forgejo_db_type: postgres
|
||||
forgejo_db_host: localhost
|
||||
forgejo_db_port: 5432
|
||||
forgejo_db_name: forgejo
|
||||
forgejo_db_user: forgejo
|
||||
forgejo_db_password: "{{ vault_forgejo_db_password | default('changeme') }}"
|
||||
|
||||
# PostgreSQL settings
|
||||
postgres_version: "16"
|
||||
postgres_data_dir: "{{ forgejo_data_path }}/postgres"
|
||||
postgres_max_connections: 100
|
||||
postgres_shared_buffers: "256MB"
|
||||
postgres_effective_cache_size: "1GB"
|
||||
|
||||
# Redis configuration (optional, for caching)
|
||||
forgejo_use_redis: true
|
||||
redis_host: localhost
|
||||
redis_port: 6379
|
||||
|
||||
# Admin user (created on first setup)
|
||||
# NOTE: "admin" is a reserved name in Forgejo, use something else
|
||||
forgejo_admin_username: forgejo_admin
|
||||
forgejo_admin_password: "{{ vault_forgejo_admin_password | default('changeme') }}"
|
||||
forgejo_admin_email: "admin@{{ forgejo_domain }}"
|
||||
|
||||
# HTTPS/SSL configuration
|
||||
forgejo_enable_letsencrypt: true
|
||||
letsencrypt_email: "admin@{{ forgejo_domain }}"
|
||||
certbot_create_if_missing: true
|
||||
certbot_auto_renew: true
|
||||
|
||||
# Object storage (S3-compatible)
|
||||
forgejo_enable_s3: false
|
||||
forgejo_s3_endpoint: ""
|
||||
forgejo_s3_bucket: ""
|
||||
forgejo_s3_region: ""
|
||||
forgejo_s3_access_key: "{{ vault_s3_access_key | default('') }}"
|
||||
forgejo_s3_secret_key: "{{ vault_s3_secret_key | default('') }}"
|
||||
|
||||
# Backup configuration
|
||||
forgejo_enable_backups: true
|
||||
forgejo_backup_schedule: "0 2 * * *" # Daily at 2 AM
|
||||
forgejo_backup_retention_days: 30
|
||||
forgejo_backup_to_s3: false
|
||||
forgejo_backup_s3_bucket: ""
|
||||
|
||||
# Security settings
|
||||
forgejo_disable_registration: false
|
||||
forgejo_require_signin_view: false
|
||||
forgejo_enable_2fa: true
|
||||
|
||||
# Tailscale VPN configuration
|
||||
# Recommended: Enable for secure SSH access
|
||||
forgejo_enable_tailscale: true
|
||||
tailscale_interface: tailscale0
|
||||
|
||||
# UFW Firewall configuration
|
||||
# When enabled with Tailscale, SSH is only accessible via Tailscale
|
||||
forgejo_enable_ufw: true
|
||||
ufw_reset_on_configure: false # Set to true to reset all rules before configuring
|
||||
|
||||
# Email configuration (optional)
|
||||
forgejo_enable_email: false
|
||||
forgejo_email_host: ""
|
||||
forgejo_email_port: 587
|
||||
forgejo_email_user: ""
|
||||
forgejo_email_password: "{{ vault_email_password | default('') }}"
|
||||
forgejo_email_from: "noreply@{{ forgejo_domain }}"
|
||||
|
||||
# Git configuration
|
||||
forgejo_disable_http_git: false
|
||||
forgejo_enable_lfs: true
|
||||
forgejo_lfs_max_file_size: 100 # MB
|
||||
|
||||
# Performance tuning
|
||||
forgejo_log_level: Info
|
||||
forgejo_disable_gravatar: false
|
||||
|
||||
# Docker Compose configuration
|
||||
docker_compose_version: "2.24.0"
|
||||
docker_install_compose: true
|
||||
|
||||
# Firewall configuration
|
||||
firewall_allowed_tcp_ports:
|
||||
- "22"
|
||||
- "80"
|
||||
- "443"
|
||||
- "{{ forgejo_ssh_port }}"
|
||||
|
||||
# System packages to install
|
||||
system_packages:
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- htop
|
||||
- vim
|
||||
- tmux
|
||||
- unzip
|
||||
- jq
|
||||
- python3-pip
|
||||
- python3-docker
|
||||
- python3-psycopg2
|
||||
- acl
|
||||
|
||||
# Monitoring (optional)
|
||||
forgejo_enable_prometheus: false
|
||||
prometheus_port: 9090
|
||||
|
||||
# Restore from backup
|
||||
forgejo_restore_from_backup: false
|
||||
forgejo_restore_backup_file: ""
|
||||
81
ansible/roles/forgejo/handlers/main.yml
Normal file
81
ansible/roles/forgejo/handlers/main.yml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
# Handlers for Forgejo role
|
||||
|
||||
- name: Restart Docker
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
become: yes
|
||||
|
||||
- name: Restart Forgejo
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ forgejo_base_path }}"
|
||||
state: restarted
|
||||
become: yes
|
||||
|
||||
- name: Reload Forgejo
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ forgejo_base_path }}"
|
||||
reloaded: yes
|
||||
become: yes
|
||||
|
||||
- name: Restart PostgreSQL
|
||||
ansible.builtin.systemd:
|
||||
name: postgresql
|
||||
state: restarted
|
||||
become: yes
|
||||
when: forgejo_db_type == 'postgres'
|
||||
|
||||
- name: Restart Redis
|
||||
ansible.builtin.systemd:
|
||||
name: redis-server
|
||||
state: restarted
|
||||
become: yes
|
||||
when: forgejo_use_redis | bool
|
||||
|
||||
- name: Reload Caddy
|
||||
ansible.builtin.systemd:
|
||||
name: caddy
|
||||
state: reloaded
|
||||
become: yes
|
||||
|
||||
- name: Restart Caddy
|
||||
ansible.builtin.systemd:
|
||||
name: caddy
|
||||
state: restarted
|
||||
become: yes
|
||||
|
||||
- name: Reload Systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: yes
|
||||
become: yes
|
||||
|
||||
- name: Restart UFW
|
||||
ansible.builtin.systemd:
|
||||
name: ufw
|
||||
state: restarted
|
||||
become: yes
|
||||
|
||||
- name: Restart sshd
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
state: restarted
|
||||
become: yes
|
||||
|
||||
- name: Renew SSL Certificate
|
||||
ansible.builtin.debug:
|
||||
msg: "Caddy handles certificate renewal automatically - no manual action needed"
|
||||
when: forgejo_enable_letsencrypt | bool
|
||||
|
||||
- name: Restart Prometheus
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ forgejo_base_path }}"
|
||||
files:
|
||||
- docker-compose.yml
|
||||
- docker-compose.monitoring.yml
|
||||
services:
|
||||
- prometheus
|
||||
state: restarted
|
||||
become: yes
|
||||
when: forgejo_enable_prometheus | bool
|
||||
32
ansible/roles/forgejo/tasks/backup.yml
Normal file
32
ansible/roles/forgejo/tasks/backup.yml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
# Backup configuration tasks
|
||||
|
||||
- name: Create backup script
|
||||
ansible.builtin.template:
|
||||
src: forgejo_backup.sh.j2
|
||||
dest: /usr/local/bin/forgejo_backup.sh
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Set up backup cron job
|
||||
ansible.builtin.cron:
|
||||
name: "Forgejo daily backup"
|
||||
minute: "{{ forgejo_backup_schedule.split()[0] }}"
|
||||
hour: "{{ forgejo_backup_schedule.split()[1] }}"
|
||||
job: "/usr/local/bin/forgejo_backup.sh >> /var/log/forgejo-backup.log 2>&1"
|
||||
become: yes
|
||||
|
||||
- name: Create log rotation for backup logs
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/logrotate.d/forgejo-backup
|
||||
content: |
|
||||
/var/log/forgejo-backup.log {
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
}
|
||||
mode: '0644'
|
||||
become: yes
|
||||
71
ansible/roles/forgejo/tasks/caddy.yml
Normal file
71
ansible/roles/forgejo/tasks/caddy.yml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
# Caddy web server setup tasks
|
||||
# Caddy handles HTTPS certificates automatically via Let's Encrypt
|
||||
|
||||
- name: Install dependencies for Caddy
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- debian-keyring
|
||||
- debian-archive-keyring
|
||||
- apt-transport-https
|
||||
- curl
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Add Caddy GPG key
|
||||
ansible.builtin.shell: |
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
args:
|
||||
creates: /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
become: yes
|
||||
|
||||
- name: Add Caddy repository
|
||||
ansible.builtin.shell: |
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
|
||||
args:
|
||||
creates: /etc/apt/sources.list.d/caddy-stable.list
|
||||
become: yes
|
||||
|
||||
- name: Install Caddy
|
||||
ansible.builtin.apt:
|
||||
name: caddy
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Create Caddy configuration directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/caddy
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Create Caddy log directory
|
||||
ansible.builtin.file:
|
||||
path: /var/log/caddy
|
||||
state: directory
|
||||
owner: caddy
|
||||
group: caddy
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Create Caddyfile for Forgejo
|
||||
ansible.builtin.template:
|
||||
src: Caddyfile.j2
|
||||
dest: /etc/caddy/Caddyfile
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
validate: 'caddy validate --adapter caddyfile --config %s'
|
||||
become: yes
|
||||
notify: Reload Caddy
|
||||
|
||||
- name: Ensure Caddy is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: caddy
|
||||
state: started
|
||||
enabled: yes
|
||||
become: yes
|
||||
100
ansible/roles/forgejo/tasks/docker.yml
Normal file
100
ansible/roles/forgejo/tasks/docker.yml
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
# Docker installation tasks
|
||||
|
||||
- name: Check if Docker is already installed
|
||||
ansible.builtin.command: docker --version
|
||||
register: docker_installed
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Install Docker
|
||||
when: docker_installed.rc != 0
|
||||
block:
|
||||
- name: Install Docker dependencies
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- lsb-release
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Create directory for Docker GPG key
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Add Docker GPG key
|
||||
ansible.builtin.apt_key:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
keyring: /etc/apt/keyrings/docker.gpg
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Add Docker repository
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
state: present
|
||||
filename: docker
|
||||
become: yes
|
||||
|
||||
- name: Install Docker Engine
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Add Forgejo user to Docker group
|
||||
ansible.builtin.user:
|
||||
name: "{{ forgejo_user }}"
|
||||
groups: docker
|
||||
append: yes
|
||||
become: yes
|
||||
|
||||
- name: Ensure Docker service is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
become: yes
|
||||
|
||||
- name: Configure Docker daemon
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/docker/daemon.json
|
||||
content: |
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
},
|
||||
"storage-driver": "overlay2",
|
||||
"userland-proxy": false,
|
||||
"live-restore": true
|
||||
}
|
||||
mode: '0644'
|
||||
become: yes
|
||||
notify: Restart Docker
|
||||
|
||||
- name: Verify Docker installation
|
||||
ansible.builtin.command: docker run --rm hello-world
|
||||
register: docker_test
|
||||
changed_when: false
|
||||
become: yes
|
||||
|
||||
- name: Display Docker version
|
||||
ansible.builtin.debug:
|
||||
msg: "Docker is installed and working"
|
||||
when: docker_test.rc == 0
|
||||
138
ansible/roles/forgejo/tasks/forgejo.yml
Normal file
138
ansible/roles/forgejo/tasks/forgejo.yml
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
# Forgejo deployment tasks
|
||||
|
||||
- name: Ensure Forgejo data directories have correct ownership
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ forgejo_uid }}"
|
||||
group: "{{ forgejo_gid }}"
|
||||
mode: '0755'
|
||||
recurse: yes
|
||||
become: yes
|
||||
loop:
|
||||
- "{{ forgejo_data_path }}"
|
||||
- "{{ forgejo_config_path }}"
|
||||
- "{{ forgejo_custom_path }}"
|
||||
|
||||
- name: Create .ssh directory for Forgejo
|
||||
ansible.builtin.file:
|
||||
path: "{{ forgejo_data_path }}/git/.ssh"
|
||||
state: directory
|
||||
owner: "{{ forgejo_uid }}"
|
||||
group: "{{ forgejo_gid }}"
|
||||
mode: '0700'
|
||||
become: yes
|
||||
|
||||
- name: Create Forgejo configuration from template
|
||||
ansible.builtin.template:
|
||||
src: app.ini.j2
|
||||
dest: "{{ forgejo_config_path }}/app.ini"
|
||||
owner: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
mode: '0640'
|
||||
become: yes
|
||||
notify: Restart Forgejo
|
||||
|
||||
- name: Create Docker Compose file
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ forgejo_base_path }}/docker-compose.yml"
|
||||
owner: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
mode: '0640'
|
||||
become: yes
|
||||
notify: Restart Forgejo
|
||||
|
||||
- name: Pull Forgejo Docker image
|
||||
community.docker.docker_image:
|
||||
name: "{{ forgejo_docker_image }}:{{ forgejo_version }}"
|
||||
source: pull
|
||||
become: yes
|
||||
|
||||
- name: Start Forgejo with Docker Compose
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ forgejo_base_path }}"
|
||||
state: present
|
||||
become: yes
|
||||
register: forgejo_started
|
||||
|
||||
- name: Wait for Forgejo to be ready
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:{{ forgejo_http_port }}"
|
||||
status_code: 200
|
||||
register: forgejo_health
|
||||
until: forgejo_health.status == 200
|
||||
retries: 30
|
||||
delay: 5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Get Forgejo container logs if startup failed
|
||||
ansible.builtin.command:
|
||||
cmd: docker logs forgejo --tail 50
|
||||
register: forgejo_logs
|
||||
become: yes
|
||||
when: forgejo_health.status is not defined or forgejo_health.status != 200
|
||||
|
||||
- name: Show Forgejo container logs
|
||||
ansible.builtin.debug:
|
||||
var: forgejo_logs.stdout_lines
|
||||
when: forgejo_logs is defined and forgejo_logs.stdout_lines is defined
|
||||
|
||||
- name: Fail if Forgejo is not ready
|
||||
ansible.builtin.fail:
|
||||
msg: "Forgejo failed to start. Check logs above."
|
||||
when: forgejo_health.status is not defined or forgejo_health.status != 200
|
||||
|
||||
- name: Check if admin user exists
|
||||
ansible.builtin.command:
|
||||
cmd: docker exec --user git forgejo forgejo admin user list --admin
|
||||
register: admin_user_check
|
||||
become: yes
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Create admin user
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
docker exec --user git forgejo forgejo admin user create
|
||||
--admin
|
||||
--username "{{ forgejo_admin_username }}"
|
||||
--password "{{ forgejo_admin_password }}"
|
||||
--email "{{ forgejo_admin_email }}"
|
||||
--must-change-password=false
|
||||
become: yes
|
||||
when: forgejo_admin_username not in admin_user_check.stdout
|
||||
register: admin_created
|
||||
no_log: yes
|
||||
|
||||
- name: Display admin credentials
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
=====================================================
|
||||
ADMIN USER CREATED
|
||||
=====================================================
|
||||
Username: {{ forgejo_admin_username }}
|
||||
Email: {{ forgejo_admin_email }}
|
||||
Password: (from your secrets.yml vault)
|
||||
|
||||
IMPORTANT: Change this password after first login!
|
||||
=====================================================
|
||||
when: admin_created is defined and admin_created.changed
|
||||
|
||||
- name: Create Forgejo systemd service
|
||||
ansible.builtin.template:
|
||||
src: forgejo.service.j2
|
||||
dest: /etc/systemd/system/forgejo.service
|
||||
mode: '0644'
|
||||
become: yes
|
||||
notify:
|
||||
- Reload Systemd
|
||||
- Restart Forgejo
|
||||
|
||||
- name: Enable Forgejo service
|
||||
ansible.builtin.systemd:
|
||||
name: forgejo
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
become: yes
|
||||
94
ansible/roles/forgejo/tasks/main.yml
Normal file
94
ansible/roles/forgejo/tasks/main.yml
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
---
|
||||
# Main tasks for Forgejo deployment
|
||||
|
||||
- name: Include system preparation tasks
|
||||
ansible.builtin.include_tasks: prepare.yml
|
||||
tags:
|
||||
- prepare
|
||||
- system
|
||||
|
||||
- name: Include Tailscale VPN setup tasks
|
||||
ansible.builtin.include_tasks: tailscale.yml
|
||||
when: forgejo_enable_tailscale | bool
|
||||
tags:
|
||||
- tailscale
|
||||
- security
|
||||
- vpn
|
||||
|
||||
- name: Include volume setup tasks
|
||||
ansible.builtin.include_tasks: volume.yml
|
||||
when: forgejo_use_external_volume | bool
|
||||
tags:
|
||||
- volume
|
||||
- storage
|
||||
|
||||
- name: Include Docker installation tasks
|
||||
ansible.builtin.include_tasks: docker.yml
|
||||
tags:
|
||||
- docker
|
||||
- install
|
||||
|
||||
- name: Include PostgreSQL setup tasks
|
||||
ansible.builtin.include_tasks: postgres.yml
|
||||
when: forgejo_db_type == 'postgres'
|
||||
tags:
|
||||
- postgres
|
||||
- database
|
||||
|
||||
- name: Include Redis setup tasks
|
||||
ansible.builtin.include_tasks: redis.yml
|
||||
when: forgejo_use_redis | bool
|
||||
tags:
|
||||
- redis
|
||||
- cache
|
||||
|
||||
# Ensure PostgreSQL is restarted with new config before Forgejo connects
|
||||
- name: Flush handlers before starting Forgejo
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: Include Forgejo configuration tasks
|
||||
ansible.builtin.include_tasks: forgejo.yml
|
||||
tags:
|
||||
- forgejo
|
||||
- config
|
||||
|
||||
- name: Include Caddy setup tasks
|
||||
ansible.builtin.include_tasks: caddy.yml
|
||||
tags:
|
||||
- caddy
|
||||
- webserver
|
||||
|
||||
- name: Include SSL certificate tasks
|
||||
ansible.builtin.include_tasks: ssl.yml
|
||||
when: forgejo_enable_letsencrypt | bool
|
||||
tags:
|
||||
- ssl
|
||||
- certificates
|
||||
|
||||
- name: Include backup configuration tasks
|
||||
ansible.builtin.include_tasks: backup.yml
|
||||
when: forgejo_enable_backups | bool
|
||||
tags:
|
||||
- backup
|
||||
|
||||
- name: Include restore tasks
|
||||
ansible.builtin.include_tasks: restore.yml
|
||||
when: forgejo_restore_from_backup | bool
|
||||
tags:
|
||||
- restore
|
||||
- never # Only run when explicitly requested
|
||||
|
||||
- name: Include monitoring setup tasks
|
||||
ansible.builtin.include_tasks: monitoring.yml
|
||||
when: forgejo_enable_prometheus | bool
|
||||
tags:
|
||||
- monitoring
|
||||
- prometheus
|
||||
|
||||
- name: Include UFW firewall configuration tasks
|
||||
ansible.builtin.include_tasks: ufw.yml
|
||||
when: forgejo_enable_ufw | bool
|
||||
tags:
|
||||
- ufw
|
||||
- firewall
|
||||
- security
|
||||
66
ansible/roles/forgejo/tasks/monitoring.yml
Normal file
66
ansible/roles/forgejo/tasks/monitoring.yml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
# Prometheus monitoring setup for Forgejo
|
||||
# This is INTERNAL monitoring - metrics are only accessible locally or via authenticated endpoint
|
||||
|
||||
- name: Create monitoring directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ forgejo_base_path }}/monitoring"
|
||||
state: directory
|
||||
owner: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Create Prometheus configuration
|
||||
ansible.builtin.template:
|
||||
src: prometheus.yml.j2
|
||||
dest: "{{ forgejo_base_path }}/monitoring/prometheus.yml"
|
||||
owner: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
mode: '0644'
|
||||
become: yes
|
||||
notify: Restart Prometheus
|
||||
|
||||
- name: Create Prometheus Docker Compose override
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.monitoring.yml.j2
|
||||
dest: "{{ forgejo_base_path }}/docker-compose.monitoring.yml"
|
||||
owner: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
mode: '0644'
|
||||
become: yes
|
||||
notify: Restart Prometheus
|
||||
|
||||
- name: Create Prometheus data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ forgejo_base_path }}/monitoring/data"
|
||||
state: directory
|
||||
owner: "65534" # nobody user in Prometheus container
|
||||
group: "65534"
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Start Prometheus container
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ forgejo_base_path }}"
|
||||
files:
|
||||
- docker-compose.yml
|
||||
- docker-compose.monitoring.yml
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Display monitoring access information
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Prometheus monitoring is now enabled!
|
||||
|
||||
Internal access (from server):
|
||||
- Prometheus UI: http://localhost:9090
|
||||
- Forgejo metrics: http://localhost:3000/metrics (requires token)
|
||||
|
||||
The metrics endpoint is protected by a token configured in your secrets.yml
|
||||
(vault_forgejo_metrics_token). Use this token in the Authorization header
|
||||
or as a query parameter: /metrics?token=YOUR_TOKEN
|
||||
|
||||
Prometheus scrapes Forgejo metrics every 15 seconds.
|
||||
Data is retained for 15 days by default.
|
||||
163
ansible/roles/forgejo/tasks/postgres.yml
Normal file
163
ansible/roles/forgejo/tasks/postgres.yml
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
---
|
||||
# PostgreSQL setup tasks
|
||||
|
||||
- name: Install PostgreSQL
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- "postgresql-{{ postgres_version }}"
|
||||
- "postgresql-contrib-{{ postgres_version }}"
|
||||
- python3-psycopg2
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Ensure PostgreSQL is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: postgresql
|
||||
state: started
|
||||
enabled: yes
|
||||
become: yes
|
||||
|
||||
- name: Create PostgreSQL data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ postgres_data_dir }}"
|
||||
state: directory
|
||||
owner: postgres
|
||||
group: postgres
|
||||
mode: '0700'
|
||||
become: yes
|
||||
when: forgejo_use_external_volume | bool
|
||||
|
||||
- name: Check if PostgreSQL database exists
|
||||
ansible.builtin.command:
|
||||
cmd: psql -U postgres -lqt
|
||||
register: postgres_db_list
|
||||
changed_when: false
|
||||
become: yes
|
||||
become_user: postgres
|
||||
|
||||
- name: Create Forgejo PostgreSQL database
|
||||
community.postgresql.postgresql_db:
|
||||
name: "{{ forgejo_db_name }}"
|
||||
encoding: UTF8
|
||||
lc_collate: en_US.UTF-8
|
||||
lc_ctype: en_US.UTF-8
|
||||
template: template0
|
||||
state: present
|
||||
become: yes
|
||||
become_user: postgres
|
||||
when: forgejo_db_name not in postgres_db_list.stdout
|
||||
|
||||
- name: Create Forgejo PostgreSQL user
|
||||
community.postgresql.postgresql_user:
|
||||
name: "{{ forgejo_db_user }}"
|
||||
password: "{{ forgejo_db_password }}"
|
||||
state: present
|
||||
become: yes
|
||||
become_user: postgres
|
||||
no_log: yes
|
||||
|
||||
- name: Grant database privileges to Forgejo user
|
||||
community.postgresql.postgresql_privs:
|
||||
database: "{{ forgejo_db_name }}"
|
||||
roles: "{{ forgejo_db_user }}"
|
||||
type: database
|
||||
privs: ALL
|
||||
become: yes
|
||||
become_user: postgres
|
||||
|
||||
- name: Grant schema privileges to Forgejo user
|
||||
community.postgresql.postgresql_privs:
|
||||
database: "{{ forgejo_db_name }}"
|
||||
roles: "{{ forgejo_db_user }}"
|
||||
type: schema
|
||||
objs: public
|
||||
privs: ALL
|
||||
become: yes
|
||||
become_user: postgres
|
||||
|
||||
- name: Set Forgejo user as owner of public schema
|
||||
community.postgresql.postgresql_owner:
|
||||
db: "{{ forgejo_db_name }}"
|
||||
new_owner: "{{ forgejo_db_user }}"
|
||||
obj_name: public
|
||||
obj_type: schema
|
||||
become: yes
|
||||
become_user: postgres
|
||||
|
||||
- name: Configure PostgreSQL for optimal performance
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/etc/postgresql/{{ postgres_version }}/main/postgresql.conf"
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
state: present
|
||||
become: yes
|
||||
loop:
|
||||
- { regexp: '^max_connections', line: "max_connections = {{ postgres_max_connections }}" }
|
||||
- { regexp: '^shared_buffers', line: "shared_buffers = {{ postgres_shared_buffers }}" }
|
||||
- { regexp: '^effective_cache_size', line: "effective_cache_size = {{ postgres_effective_cache_size }}" }
|
||||
- { regexp: '^maintenance_work_mem', line: "maintenance_work_mem = 128MB" }
|
||||
- { regexp: '^checkpoint_completion_target', line: "checkpoint_completion_target = 0.9" }
|
||||
- { regexp: '^wal_buffers', line: "wal_buffers = 16MB" }
|
||||
- { regexp: '^default_statistics_target', line: "default_statistics_target = 100" }
|
||||
- { regexp: '^random_page_cost', line: "random_page_cost = 1.1" }
|
||||
- { regexp: '^effective_io_concurrency', line: "effective_io_concurrency = 200" }
|
||||
- { regexp: '^work_mem', line: "work_mem = 8MB" }
|
||||
- { regexp: '^min_wal_size', line: "min_wal_size = 1GB" }
|
||||
- { regexp: '^max_wal_size', line: "max_wal_size = 4GB" }
|
||||
notify: Restart PostgreSQL
|
||||
|
||||
- name: Configure PostgreSQL to listen on all interfaces
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/etc/postgresql/{{ postgres_version }}/main/postgresql.conf"
|
||||
regexp: "^#?listen_addresses"
|
||||
line: "listen_addresses = '*'"
|
||||
state: present
|
||||
become: yes
|
||||
notify: Restart PostgreSQL
|
||||
|
||||
- name: Configure PostgreSQL authentication
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/etc/postgresql/{{ postgres_version }}/main/pg_hba.conf"
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
state: present
|
||||
become: yes
|
||||
loop:
|
||||
- regexp: '^local\s+all\s+postgres'
|
||||
line: 'local all postgres peer'
|
||||
- regexp: '^local\s+all\s+all'
|
||||
line: 'local all all peer'
|
||||
- regexp: '^host\s+all\s+all\s+127\.0\.0\.1'
|
||||
line: 'host all all 127.0.0.1/32 scram-sha-256'
|
||||
- regexp: '^host\s+all\s+all\s+::1'
|
||||
line: 'host all all ::1/128 scram-sha-256'
|
||||
notify: Restart PostgreSQL
|
||||
|
||||
- name: Allow Docker network to connect to PostgreSQL
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/etc/postgresql/{{ postgres_version }}/main/pg_hba.conf"
|
||||
line: 'host all all 172.16.0.0/12 scram-sha-256'
|
||||
insertafter: '^host\s+all\s+all\s+127'
|
||||
state: present
|
||||
become: yes
|
||||
notify: Restart PostgreSQL
|
||||
|
||||
- name: Enable PostgreSQL extensions
|
||||
community.postgresql.postgresql_ext:
|
||||
name: "{{ item }}"
|
||||
db: "{{ forgejo_db_name }}"
|
||||
state: present
|
||||
become: yes
|
||||
become_user: postgres
|
||||
loop:
|
||||
- pg_trgm
|
||||
- btree_gin
|
||||
|
||||
- name: Create PostgreSQL backup script
|
||||
ansible.builtin.template:
|
||||
src: postgres_backup.sh.j2
|
||||
dest: /usr/local/bin/postgres_backup.sh
|
||||
mode: '0755'
|
||||
become: yes
|
||||
when: forgejo_enable_backups | bool
|
||||
194
ansible/roles/forgejo/tasks/prepare.yml
Normal file
194
ansible/roles/forgejo/tasks/prepare.yml
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
---
|
||||
# System preparation tasks
|
||||
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
become: yes
|
||||
|
||||
- name: Upgrade all packages
|
||||
ansible.builtin.apt:
|
||||
upgrade: safe
|
||||
become: yes
|
||||
tags:
|
||||
- upgrade
|
||||
|
||||
- name: Install system packages
|
||||
ansible.builtin.apt:
|
||||
name: "{{ system_packages }}"
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Check if Forgejo group exists
|
||||
ansible.builtin.getent:
|
||||
database: group
|
||||
key: "{{ forgejo_group }}"
|
||||
register: forgejo_group_check
|
||||
ignore_errors: yes
|
||||
become: yes
|
||||
|
||||
- name: Create Forgejo system group
|
||||
ansible.builtin.group:
|
||||
name: "{{ forgejo_group }}"
|
||||
gid: "{{ forgejo_gid }}"
|
||||
system: yes
|
||||
state: present
|
||||
become: yes
|
||||
when: forgejo_group_check.failed | default(false)
|
||||
|
||||
- name: Ensure Forgejo group exists (if already created with different GID)
|
||||
ansible.builtin.group:
|
||||
name: "{{ forgejo_group }}"
|
||||
system: yes
|
||||
state: present
|
||||
become: yes
|
||||
when: not (forgejo_group_check.failed | default(false))
|
||||
|
||||
- name: Check if Forgejo user exists
|
||||
ansible.builtin.getent:
|
||||
database: passwd
|
||||
key: "{{ forgejo_user }}"
|
||||
register: forgejo_user_check
|
||||
ignore_errors: yes
|
||||
become: yes
|
||||
|
||||
- name: Create Forgejo system user
|
||||
ansible.builtin.user:
|
||||
name: "{{ forgejo_user }}"
|
||||
uid: "{{ forgejo_uid }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
system: yes
|
||||
shell: /bin/bash
|
||||
home: "{{ forgejo_base_path }}"
|
||||
create_home: no
|
||||
state: present
|
||||
become: yes
|
||||
when: forgejo_user_check.failed | default(false)
|
||||
|
||||
- name: Ensure Forgejo user exists (if already created with different UID)
|
||||
ansible.builtin.user:
|
||||
name: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
system: yes
|
||||
shell: /bin/bash
|
||||
home: "{{ forgejo_base_path }}"
|
||||
create_home: no
|
||||
state: present
|
||||
become: yes
|
||||
when: not (forgejo_user_check.failed | default(false))
|
||||
|
||||
- name: Create Forgejo directory structure
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
mode: '0755'
|
||||
become: yes
|
||||
loop:
|
||||
- "{{ forgejo_base_path }}"
|
||||
- "{{ forgejo_data_path }}"
|
||||
- "{{ forgejo_config_path }}"
|
||||
- "{{ forgejo_custom_path }}"
|
||||
- "{{ forgejo_backup_path }}"
|
||||
- "{{ forgejo_data_path }}/git"
|
||||
- "{{ forgejo_data_path }}/attachments"
|
||||
- "{{ forgejo_data_path }}/lfs"
|
||||
- "{{ forgejo_data_path }}/avatars"
|
||||
|
||||
- name: Configure system limits for Forgejo
|
||||
ansible.builtin.pam_limits:
|
||||
domain: "{{ forgejo_user }}"
|
||||
limit_type: "{{ item.limit_type }}"
|
||||
limit_item: "{{ item.limit_item }}"
|
||||
value: "{{ item.value }}"
|
||||
become: yes
|
||||
loop:
|
||||
- { limit_type: 'soft', limit_item: 'nofile', value: '65535' }
|
||||
- { limit_type: 'hard', limit_item: 'nofile', value: '65535' }
|
||||
- { limit_type: 'soft', limit_item: 'nproc', value: '65535' }
|
||||
- { limit_type: 'hard', limit_item: 'nproc', value: '65535' }
|
||||
|
||||
- name: Configure kernel parameters
|
||||
ansible.builtin.sysctl:
|
||||
name: "{{ item.name }}"
|
||||
value: "{{ item.value }}"
|
||||
state: present
|
||||
reload: yes
|
||||
become: yes
|
||||
loop:
|
||||
- { name: 'net.core.somaxconn', value: '1024' }
|
||||
- { name: 'net.ipv4.tcp_max_syn_backlog', value: '2048' }
|
||||
- { name: 'net.ipv4.ip_forward', value: '1' }
|
||||
- { name: 'vm.swappiness', value: '10' }
|
||||
- { name: 'fs.file-max', value: '65535' }
|
||||
|
||||
# NOTE: UFW firewall configuration is handled by ufw.yml
|
||||
# We only set up minimal rules here for Docker access during deployment
|
||||
# The full secure configuration (Tailscale-only SSH) is applied in ufw.yml
|
||||
|
||||
- name: Install UFW
|
||||
ansible.builtin.apt:
|
||||
name: ufw
|
||||
state: present
|
||||
become: yes
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Allow Docker network to access host services
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
from_ip: 172.16.0.0/12
|
||||
comment: "Allow Docker containers to access host services (PostgreSQL, etc.)"
|
||||
become: yes
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
- name: Set timezone to UTC
|
||||
community.general.timezone:
|
||||
name: UTC
|
||||
become: yes
|
||||
|
||||
- name: Enable automatic security updates
|
||||
ansible.builtin.apt:
|
||||
name: unattended-upgrades
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Configure unattended upgrades
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/apt.conf.d/50unattended-upgrades
|
||||
content: |
|
||||
Unattended-Upgrade::Allowed-Origins {
|
||||
"${distro_id}:${distro_codename}-security";
|
||||
"${distro_id}ESMApps:${distro_codename}-apps-security";
|
||||
"${distro_id}ESM:${distro_codename}-infra-security";
|
||||
};
|
||||
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
||||
Unattended-Upgrade::MinimalSteps "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
mode: '0644'
|
||||
become: yes
|
||||
|
||||
- name: Ensure SSH is properly configured
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
state: present
|
||||
validate: '/usr/sbin/sshd -t -f %s'
|
||||
become: yes
|
||||
loop:
|
||||
- { regexp: '^PermitRootLogin', line: 'PermitRootLogin prohibit-password' }
|
||||
- { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' }
|
||||
- { regexp: '^PubkeyAuthentication', line: 'PubkeyAuthentication yes' }
|
||||
notify: Restart sshd
|
||||
when: ansible_connection != 'local'
|
||||
|
||||
- name: Create systemd service for sshd
|
||||
ansible.builtin.systemd:
|
||||
name: sshd
|
||||
enabled: yes
|
||||
state: started
|
||||
become: yes
|
||||
when: ansible_connection != 'local'
|
||||
40
ansible/roles/forgejo/tasks/redis.yml
Normal file
40
ansible/roles/forgejo/tasks/redis.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
# Redis setup tasks
|
||||
|
||||
- name: Install Redis
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- redis-server
|
||||
- redis-tools
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Configure Redis
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/redis/redis.conf
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
state: present
|
||||
become: yes
|
||||
loop:
|
||||
- { regexp: '^bind', line: 'bind 127.0.0.1 ::1' }
|
||||
- { regexp: '^protected-mode', line: 'protected-mode yes' }
|
||||
- { regexp: '^maxmemory', line: 'maxmemory 256mb' }
|
||||
- { regexp: '^maxmemory-policy', line: 'maxmemory-policy allkeys-lru' }
|
||||
- { regexp: '^save', line: 'save 900 1' }
|
||||
notify: Restart Redis
|
||||
|
||||
- name: Ensure Redis is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: redis-server
|
||||
state: started
|
||||
enabled: yes
|
||||
become: yes
|
||||
|
||||
- name: Test Redis connection
|
||||
ansible.builtin.command:
|
||||
cmd: redis-cli ping
|
||||
register: redis_ping
|
||||
changed_when: false
|
||||
failed_when: redis_ping.stdout != "PONG"
|
||||
29
ansible/roles/forgejo/tasks/ssl.yml
Normal file
29
ansible/roles/forgejo/tasks/ssl.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
# SSL/TLS setup for Caddy
|
||||
# Note: Caddy handles Let's Encrypt certificates automatically!
|
||||
# This file only sets up log directories and verifies configuration.
|
||||
|
||||
- name: Create Caddy log directory
|
||||
ansible.builtin.file:
|
||||
path: /var/log/caddy
|
||||
state: directory
|
||||
owner: caddy
|
||||
group: caddy
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Verify Caddy is configured for HTTPS
|
||||
ansible.builtin.debug:
|
||||
msg: >
|
||||
Caddy will automatically obtain and renew TLS certificates for {{ forgejo_domain }}
|
||||
using Let's Encrypt. The email {{ letsencrypt_email }} will be used for renewal
|
||||
notifications. No manual certificate management is required.
|
||||
|
||||
- name: Ensure Caddy data directory exists (for certificates)
|
||||
ansible.builtin.file:
|
||||
path: /var/lib/caddy/.local/share/caddy
|
||||
state: directory
|
||||
owner: caddy
|
||||
group: caddy
|
||||
mode: '0700'
|
||||
become: yes
|
||||
76
ansible/roles/forgejo/tasks/tailscale.yml
Normal file
76
ansible/roles/forgejo/tasks/tailscale.yml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
# Tailscale VPN installation and configuration
|
||||
# Provides secure access to SSH and internal services
|
||||
|
||||
- name: Install prerequisites for Tailscale
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- curl
|
||||
- gnupg
|
||||
- apt-transport-https
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Add Tailscale GPG key
|
||||
ansible.builtin.shell: |
|
||||
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg > /dev/null
|
||||
args:
|
||||
creates: /usr/share/keyrings/tailscale-archive-keyring.gpg
|
||||
become: yes
|
||||
|
||||
- name: Add Tailscale repository
|
||||
ansible.builtin.shell: |
|
||||
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list > /dev/null
|
||||
args:
|
||||
creates: /etc/apt/sources.list.d/tailscale.list
|
||||
become: yes
|
||||
|
||||
- name: Install Tailscale
|
||||
ansible.builtin.apt:
|
||||
name: tailscale
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Enable Tailscale service
|
||||
ansible.builtin.systemd:
|
||||
name: tailscaled
|
||||
state: started
|
||||
enabled: yes
|
||||
become: yes
|
||||
|
||||
- name: Check if Tailscale is already authenticated
|
||||
ansible.builtin.command: tailscale status
|
||||
register: tailscale_status
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
become: yes
|
||||
|
||||
- name: Display Tailscale authentication instructions
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
===============================================================
|
||||
TAILSCALE AUTHENTICATION REQUIRED
|
||||
===============================================================
|
||||
|
||||
Tailscale is installed but needs to be authenticated.
|
||||
|
||||
SSH into the server and run:
|
||||
sudo tailscale up --ssh
|
||||
|
||||
This will:
|
||||
1. Open a browser URL for authentication
|
||||
2. Connect to your Tailnet
|
||||
3. Enable Tailscale SSH (optional but recommended)
|
||||
|
||||
For headless servers, use an auth key:
|
||||
sudo tailscale up --authkey=tskey-auth-XXXXX
|
||||
|
||||
Generate an auth key at: https://login.tailscale.com/admin/settings/keys
|
||||
|
||||
After authentication, you can access this server via:
|
||||
- Tailscale IP (shown in 'tailscale ip')
|
||||
- Tailscale hostname (from admin console)
|
||||
===============================================================
|
||||
when: tailscale_status.rc != 0
|
||||
142
ansible/roles/forgejo/tasks/ufw.yml
Normal file
142
ansible/roles/forgejo/tasks/ufw.yml
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
# UFW Firewall configuration for Forgejo
|
||||
# Restricts SSH access to Tailscale interface only
|
||||
# Only exposes HTTP/HTTPS to the public internet
|
||||
|
||||
- name: Install UFW
|
||||
ansible.builtin.apt:
|
||||
name: ufw
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
tags:
|
||||
- install
|
||||
- ufw
|
||||
|
||||
- name: Deploy Forgejo UFW application profile
|
||||
ansible.builtin.template:
|
||||
src: ufw-forgejo.j2
|
||||
dest: /etc/ufw/applications.d/forgejo
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Reset UFW to default (clean slate)
|
||||
community.general.ufw:
|
||||
state: reset
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Set default incoming policy to deny
|
||||
community.general.ufw:
|
||||
direction: incoming
|
||||
policy: deny
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Set default outgoing policy to allow
|
||||
community.general.ufw:
|
||||
direction: outgoing
|
||||
policy: allow
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Allow all traffic on Tailscale interface
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
interface: "{{ tailscale_interface }}"
|
||||
direction: in
|
||||
comment: "Allow all Tailscale traffic (SSH, monitoring, internal services)"
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Allow Docker network to access host services
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
from_ip: 172.16.0.0/12
|
||||
comment: "Allow Docker containers to access host services (PostgreSQL, etc.)"
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
# Public-facing ports (Caddy handles HTTPS)
|
||||
- name: Allow HTTP (Caddy)
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "80"
|
||||
proto: tcp
|
||||
comment: "HTTP - Caddy (redirects to HTTPS)"
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Allow HTTPS (Caddy)
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "443"
|
||||
proto: tcp
|
||||
comment: "HTTPS - Caddy/Forgejo"
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
# Git SSH is only accessible via Tailscale (through the interface rule above)
|
||||
# Regular SSH is only accessible via Tailscale (through the interface rule above)
|
||||
|
||||
- name: Enable UFW logging
|
||||
community.general.ufw:
|
||||
logging: "on"
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Enable UFW
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
become: yes
|
||||
tags:
|
||||
- configure
|
||||
- ufw
|
||||
|
||||
- name: Display UFW security configuration
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
===============================================================
|
||||
FIREWALL CONFIGURED - SECURITY SUMMARY
|
||||
===============================================================
|
||||
|
||||
PUBLIC ACCESS (from anywhere):
|
||||
- Port 80/tcp (HTTP - redirects to HTTPS)
|
||||
- Port 443/tcp (HTTPS - Forgejo web interface)
|
||||
|
||||
TAILSCALE-ONLY ACCESS (via {{ tailscale_interface }}):
|
||||
- Port 22/tcp (SSH - system administration)
|
||||
- Port 2222/tcp (Git SSH - clone/push/pull)
|
||||
- Port 3000/tcp (Forgejo internal - for debugging)
|
||||
- Port 9090/tcp (Prometheus - if enabled)
|
||||
- All other internal services
|
||||
|
||||
Git clone URLs:
|
||||
- HTTPS (public): https://{{ forgejo_domain }}/user/repo.git
|
||||
- SSH (Tailscale): git@<tailscale-hostname>:user/repo.git
|
||||
|
||||
To access SSH after this change:
|
||||
ssh root@<tailscale-ip-or-hostname>
|
||||
|
||||
===============================================================
|
||||
60
ansible/roles/forgejo/tasks/volume.yml
Normal file
60
ansible/roles/forgejo/tasks/volume.yml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
# External volume setup tasks
|
||||
|
||||
- name: Check if volume device exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ forgejo_volume_device }}"
|
||||
register: volume_device
|
||||
|
||||
- name: Fail if volume device not found
|
||||
ansible.builtin.fail:
|
||||
msg: "Volume device {{ forgejo_volume_device }} not found"
|
||||
when: not volume_device.stat.exists
|
||||
|
||||
- name: Check if volume is already formatted
|
||||
ansible.builtin.command:
|
||||
cmd: "blkid {{ forgejo_volume_device }}"
|
||||
register: volume_formatted
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Format volume with ext4
|
||||
ansible.builtin.filesystem:
|
||||
fstype: ext4
|
||||
dev: "{{ forgejo_volume_device }}"
|
||||
become: yes
|
||||
when: volume_formatted.rc != 0
|
||||
|
||||
- name: Create mount point
|
||||
ansible.builtin.file:
|
||||
path: "{{ forgejo_volume_mount }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Mount volume
|
||||
ansible.posix.mount:
|
||||
path: "{{ forgejo_volume_mount }}"
|
||||
src: "{{ forgejo_volume_device }}"
|
||||
fstype: ext4
|
||||
opts: defaults,nofail
|
||||
state: mounted
|
||||
become: yes
|
||||
|
||||
- name: Update data path to use volume
|
||||
ansible.builtin.set_fact:
|
||||
forgejo_data_path: "{{ forgejo_volume_mount }}/data"
|
||||
|
||||
- name: Create data directories on volume
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ forgejo_user }}"
|
||||
group: "{{ forgejo_group }}"
|
||||
mode: '0755'
|
||||
become: yes
|
||||
loop:
|
||||
- "{{ forgejo_data_path }}"
|
||||
- "{{ forgejo_data_path }}/git"
|
||||
- "{{ forgejo_data_path }}/attachments"
|
||||
- "{{ forgejo_data_path }}/lfs"
|
||||
0
ansible/roles/forgejo/tasks/{restore.yml,monitoring.yml}
Normal file
0
ansible/roles/forgejo/tasks/{restore.yml,monitoring.yml}
Normal file
63
ansible/roles/forgejo/templates/Caddyfile.j2
Normal file
63
ansible/roles/forgejo/templates/Caddyfile.j2
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Caddyfile for Forgejo
|
||||
# Caddy automatically obtains and renews TLS certificates via Let's Encrypt
|
||||
|
||||
{% if forgejo_enable_letsencrypt %}
|
||||
{{ forgejo_domain }} {
|
||||
# Reverse proxy to Forgejo
|
||||
reverse_proxy localhost:{{ forgejo_http_port }} {
|
||||
# WebSocket support (needed for real-time features)
|
||||
header_up X-Real-IP {remote_host}
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
|
||||
# Timeouts for large Git operations
|
||||
transport http {
|
||||
read_timeout 600s
|
||||
write_timeout 600s
|
||||
}
|
||||
}
|
||||
|
||||
# Security headers
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
}
|
||||
|
||||
# Request body size for large uploads (Git push, LFS)
|
||||
request_body {
|
||||
max_size 100MB
|
||||
}
|
||||
|
||||
# Logging
|
||||
log {
|
||||
output file /var/log/caddy/forgejo_access.log {
|
||||
roll_size 100mb
|
||||
roll_keep 5
|
||||
}
|
||||
format json
|
||||
}
|
||||
|
||||
# TLS configuration (automatic via Let's Encrypt)
|
||||
tls {{ letsencrypt_email }}
|
||||
}
|
||||
{% else %}
|
||||
# HTTP-only configuration (not recommended for production)
|
||||
:80 {
|
||||
reverse_proxy localhost:{{ forgejo_http_port }} {
|
||||
header_up X-Real-IP {remote_host}
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
}
|
||||
|
||||
request_body {
|
||||
max_size 100MB
|
||||
}
|
||||
|
||||
log {
|
||||
output file /var/log/caddy/forgejo_access.log
|
||||
format json
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
219
ansible/roles/forgejo/templates/app.ini.j2
Normal file
219
ansible/roles/forgejo/templates/app.ini.j2
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
; Forgejo Configuration File
|
||||
; Generated by Ansible
|
||||
|
||||
APP_NAME = Forgejo: {{ forgejo_domain }}
|
||||
RUN_MODE = prod
|
||||
RUN_USER = {{ forgejo_user }}
|
||||
WORK_PATH = /data/gitea
|
||||
|
||||
[repository]
|
||||
ROOT = /data/git/repositories
|
||||
SCRIPT_TYPE = bash
|
||||
DEFAULT_BRANCH = main
|
||||
DEFAULT_PRIVATE = last
|
||||
MAX_CREATION_LIMIT = -1
|
||||
ENABLE_PUSH_CREATE_USER = true
|
||||
ENABLE_PUSH_CREATE_ORG = true
|
||||
DISABLE_HTTP_GIT = {{ forgejo_disable_http_git | lower }}
|
||||
|
||||
[repository.local]
|
||||
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
|
||||
|
||||
[repository.upload]
|
||||
ENABLED = true
|
||||
TEMP_PATH = /data/gitea/uploads
|
||||
FILE_MAX_SIZE = 100
|
||||
MAX_FILES = 10
|
||||
|
||||
[lfs]
|
||||
ENABLED = {{ forgejo_enable_lfs | lower }}
|
||||
PATH = /data/lfs
|
||||
MAX_FILE_SIZE = {{ forgejo_lfs_max_file_size }}
|
||||
|
||||
[server]
|
||||
; Forgejo listens on HTTP internally; Caddy handles TLS termination
|
||||
PROTOCOL = http
|
||||
DOMAIN = {{ forgejo_domain }}
|
||||
ROOT_URL = {{ forgejo_protocol }}://{{ forgejo_domain }}/
|
||||
HTTP_ADDR = 0.0.0.0
|
||||
HTTP_PORT = 3000
|
||||
DISABLE_SSH = false
|
||||
SSH_DOMAIN = {{ forgejo_domain }}
|
||||
SSH_PORT = {{ forgejo_ssh_port }}
|
||||
SSH_LISTEN_PORT = 22
|
||||
OFFLINE_MODE = false
|
||||
APP_DATA_PATH = /data/gitea
|
||||
LANDING_PAGE = explore
|
||||
LFS_START_SERVER = {{ forgejo_enable_lfs | lower }}
|
||||
|
||||
[database]
|
||||
DB_TYPE = {{ forgejo_db_type }}
|
||||
; Use host.docker.internal to reach host PostgreSQL from container
|
||||
HOST = host.docker.internal:{{ forgejo_db_port }}
|
||||
NAME = {{ forgejo_db_name }}
|
||||
USER = {{ forgejo_db_user }}
|
||||
PASSWD = {{ forgejo_db_password }}
|
||||
SCHEMA =
|
||||
SSL_MODE = disable
|
||||
CHARSET = utf8mb4
|
||||
LOG_SQL = false
|
||||
MAX_IDLE_CONNS = 30
|
||||
MAX_OPEN_CONNS = 100
|
||||
CONN_MAX_LIFETIME = 3600
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = {{ vault_forgejo_secret_key | default('') }}
|
||||
INTERNAL_TOKEN = {{ vault_forgejo_internal_token | default('') }}
|
||||
PASSWORD_COMPLEXITY = lower,upper,digit,spec
|
||||
MIN_PASSWORD_LENGTH = 10
|
||||
PASSWORD_HASH_ALGO = argon2
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = {{ forgejo_disable_registration | lower }}
|
||||
REQUIRE_SIGNIN_VIEW = {{ forgejo_require_signin_view | lower }}
|
||||
REGISTER_EMAIL_CONFIRM = {{ forgejo_enable_email | lower }}
|
||||
ENABLE_NOTIFY_MAIL = {{ forgejo_enable_email | lower }}
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = true
|
||||
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
|
||||
DEFAULT_ORG_VISIBILITY = private
|
||||
ENABLE_CAPTCHA = true
|
||||
ENABLE_TIMETRACKING = true
|
||||
DEFAULT_ENABLE_TIMETRACKING = true
|
||||
ENABLE_USER_HEATMAP = true
|
||||
|
||||
[service.explore]
|
||||
REQUIRE_SIGNIN_VIEW = {{ forgejo_require_signin_view | lower }}
|
||||
DISABLE_USERS_PAGE = false
|
||||
|
||||
{% if forgejo_enable_email %}
|
||||
[mailer]
|
||||
ENABLED = true
|
||||
SMTP_ADDR = {{ forgejo_email_host }}
|
||||
SMTP_PORT = {{ forgejo_email_port }}
|
||||
FROM = {{ forgejo_email_from }}
|
||||
USER = {{ forgejo_email_user }}
|
||||
PASSWD = {{ forgejo_email_password }}
|
||||
SUBJECT_PREFIX = [{{ forgejo_domain }}]
|
||||
MAILER_TYPE = smtp
|
||||
IS_TLS_ENABLED = true
|
||||
{% endif %}
|
||||
|
||||
[session]
|
||||
PROVIDER = file
|
||||
PROVIDER_CONFIG = /data/gitea/sessions
|
||||
COOKIE_SECURE = {{ (forgejo_protocol == 'https') | lower }}
|
||||
COOKIE_NAME = i_like_forgejo
|
||||
COOKIE_DOMAIN = {{ forgejo_domain }}
|
||||
GC_INTERVAL_TIME = 86400
|
||||
SESSION_LIFE_TIME = 86400
|
||||
|
||||
[picture]
|
||||
DISABLE_GRAVATAR = {{ forgejo_disable_gravatar | lower }}
|
||||
ENABLE_FEDERATED_AVATAR = false
|
||||
|
||||
[attachment]
|
||||
ENABLED = true
|
||||
PATH = /data/attachments
|
||||
MAX_SIZE = 100
|
||||
MAX_FILES = 10
|
||||
|
||||
[time]
|
||||
DEFAULT_UI_LOCATION = UTC
|
||||
|
||||
[log]
|
||||
MODE = console, file
|
||||
LEVEL = {{ forgejo_log_level }}
|
||||
ROOT_PATH = /data/gitea/log
|
||||
ENABLE_XORM_LOG = false
|
||||
|
||||
[log.console]
|
||||
LEVEL = {{ forgejo_log_level }}
|
||||
COLORIZE = false
|
||||
|
||||
[log.file]
|
||||
LEVEL = {{ forgejo_log_level }}
|
||||
FILE_NAME = forgejo.log
|
||||
MAX_SIZE_SHIFT = 28
|
||||
DAILY_ROTATE = true
|
||||
MAX_DAYS = 7
|
||||
|
||||
[git]
|
||||
MAX_GIT_DIFF_LINES = 1000
|
||||
MAX_GIT_DIFF_LINE_CHARACTERS = 5000
|
||||
MAX_GIT_DIFF_FILES = 100
|
||||
GC_ARGS =
|
||||
|
||||
[git.timeout]
|
||||
DEFAULT = 360
|
||||
MIGRATE = 600
|
||||
MIRROR = 300
|
||||
CLONE = 300
|
||||
PULL = 300
|
||||
GC = 60
|
||||
|
||||
{% if forgejo_enable_2fa %}
|
||||
[two_factor]
|
||||
ENABLED = true
|
||||
{% endif %}
|
||||
|
||||
[openid]
|
||||
ENABLE_OPENID_SIGNIN = false
|
||||
ENABLE_OPENID_SIGNUP = false
|
||||
|
||||
[cron]
|
||||
ENABLED = true
|
||||
RUN_AT_START = false
|
||||
|
||||
[cron.update_mirrors]
|
||||
SCHEDULE = @every 10m
|
||||
|
||||
[cron.repo_health_check]
|
||||
SCHEDULE = @every 24h
|
||||
TIMEOUT = 60s
|
||||
|
||||
[cron.check_repo_stats]
|
||||
SCHEDULE = @every 24h
|
||||
|
||||
[cron.cleanup_hook_task_table]
|
||||
SCHEDULE = @every 24h
|
||||
CLEANUP_TYPE = OlderThan
|
||||
OLDER_THAN = 168h
|
||||
|
||||
[cron.update_migration_poster_id]
|
||||
SCHEDULE = @every 24h
|
||||
|
||||
[cron.sync_external_users]
|
||||
SCHEDULE = @every 24h
|
||||
UPDATE_EXISTING = true
|
||||
|
||||
[api]
|
||||
ENABLE_SWAGGER = false
|
||||
MAX_RESPONSE_ITEMS = 50
|
||||
DEFAULT_PAGING_NUM = 30
|
||||
DEFAULT_GIT_TREES_PER_PAGE = 1000
|
||||
DEFAULT_MAX_BLOB_SIZE = 10485760
|
||||
|
||||
[oauth2]
|
||||
ENABLED = true
|
||||
JWT_SECRET = {{ vault_forgejo_jwt_secret | default('') }}
|
||||
|
||||
[webhook]
|
||||
QUEUE_LENGTH = 1000
|
||||
DELIVER_TIMEOUT = 15
|
||||
SKIP_TLS_VERIFY = false
|
||||
PAGING_NUM = 10
|
||||
|
||||
[metrics]
|
||||
ENABLED = {{ forgejo_enable_prometheus | lower }}
|
||||
TOKEN = {{ vault_forgejo_metrics_token | default('') }}
|
||||
|
||||
[task]
|
||||
QUEUE_TYPE = channel
|
||||
QUEUE_LENGTH = 10000
|
||||
QUEUE_CONN_STR =
|
||||
QUEUE_BATCH_NUMBER = 20
|
||||
|
||||
[indexer]
|
||||
ISSUE_INDEXER_TYPE = db
|
||||
REPO_INDEXER_ENABLED = true
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Docker Compose override for Prometheus monitoring
|
||||
# Generated by Ansible
|
||||
# This file extends the main docker-compose.yml
|
||||
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: prometheus
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
- ./monitoring/data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--storage.tsdb.retention.time=15d'
|
||||
- '--web.enable-lifecycle'
|
||||
ports:
|
||||
# Only bind to localhost for security - not exposed externally
|
||||
- "127.0.0.1:{{ prometheus_port | default(9090) }}:9090"
|
||||
networks:
|
||||
- forgejo-network
|
||||
|
||||
networks:
|
||||
forgejo-network:
|
||||
external: true
|
||||
name: {{ forgejo_base_path | basename }}_default
|
||||
76
ansible/roles/forgejo/templates/docker-compose.yml.j2
Normal file
76
ansible/roles/forgejo/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
services:
|
||||
forgejo:
|
||||
image: {{ forgejo_docker_image }}:{{ forgejo_version }}
|
||||
container_name: forgejo
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID={{ forgejo_uid }}
|
||||
- USER_GID={{ forgejo_gid }}
|
||||
- FORGEJO__database__DB_TYPE={{ forgejo_db_type }}
|
||||
- FORGEJO__database__HOST=host.docker.internal:{{ forgejo_db_port }}
|
||||
- FORGEJO__database__NAME={{ forgejo_db_name }}
|
||||
- FORGEJO__database__USER={{ forgejo_db_user }}
|
||||
- FORGEJO__database__PASSWD={{ forgejo_db_password }}
|
||||
{% if forgejo_use_redis %}
|
||||
- FORGEJO__cache__ENABLED=true
|
||||
- FORGEJO__cache__ADAPTER=redis
|
||||
- FORGEJO__cache__HOST=redis://redis:{{ redis_port }}/0
|
||||
- FORGEJO__session__PROVIDER=redis
|
||||
- FORGEJO__session__PROVIDER_CONFIG=redis://redis:{{ redis_port }}/0
|
||||
{% endif %}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- {{ forgejo_data_path }}/git:/data/git
|
||||
- {{ forgejo_data_path }}/attachments:/data/attachments
|
||||
- {{ forgejo_data_path }}/lfs:/data/lfs
|
||||
- {{ forgejo_config_path }}/app.ini:/data/gitea/conf/app.ini
|
||||
- {{ forgejo_custom_path }}:/data/gitea/custom
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "127.0.0.1:{{ forgejo_http_port }}:3000"
|
||||
- "{{ forgejo_ssh_port }}:22"
|
||||
networks:
|
||||
- forgejo
|
||||
{% if forgejo_use_redis %}
|
||||
depends_on:
|
||||
- redis
|
||||
{% endif %}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
{% if forgejo_use_redis %}
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: forgejo-redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- {{ forgejo_data_path }}/redis:/data
|
||||
networks:
|
||||
- forgejo
|
||||
command: redis-server --appendonly yes
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "5m"
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
{% endif %}
|
||||
|
||||
networks:
|
||||
forgejo:
|
||||
driver: bridge
|
||||
19
ansible/roles/forgejo/templates/forgejo.service.j2
Normal file
19
ansible/roles/forgejo/templates/forgejo.service.j2
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=Forgejo Git Server (Docker Compose)
|
||||
Documentation=https://forgejo.org/docs/latest/
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory={{ forgejo_base_path }}
|
||||
ExecStart=/usr/bin/docker compose up -d
|
||||
ExecStop=/usr/bin/docker compose down
|
||||
ExecReload=/usr/bin/docker compose restart
|
||||
TimeoutStartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
33
ansible/roles/forgejo/templates/forgejo_backup.sh.j2
Normal file
33
ansible/roles/forgejo/templates/forgejo_backup.sh.j2
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
# Forgejo Backup Script
|
||||
# Generated by Ansible
|
||||
|
||||
set -e
|
||||
|
||||
BACKUP_DIR="{{ forgejo_backup_path }}"
|
||||
TIMESTAMP=$(date +%Y%m%dT%H%M%S)
|
||||
LOG_FILE="/var/log/forgejo-backup.log"
|
||||
|
||||
echo "[$(date)] Starting Forgejo backup..." | tee -a "$LOG_FILE"
|
||||
|
||||
# Create database backup
|
||||
pg_dump -U {{ forgejo_db_user }} {{ forgejo_db_name }} | gzip > "$BACKUP_DIR/database-$TIMESTAMP.sql.gz"
|
||||
echo "[$(date)] Database backed up" | tee -a "$LOG_FILE"
|
||||
|
||||
# Backup repositories
|
||||
tar -czf "$BACKUP_DIR/repositories-$TIMESTAMP.tar.gz" -C {{ forgejo_data_path }} git
|
||||
echo "[$(date)] Repositories backed up" | tee -a "$LOG_FILE"
|
||||
|
||||
# Backup configuration
|
||||
tar -czf "$BACKUP_DIR/config-$TIMESTAMP.tar.gz" {{ forgejo_config_path }} {{ forgejo_base_path }}/docker-compose.yml
|
||||
echo "[$(date)] Configuration backed up" | tee -a "$LOG_FILE"
|
||||
|
||||
# Backup data
|
||||
tar -czf "$BACKUP_DIR/data-$TIMESTAMP.tar.gz" -C {{ forgejo_data_path }} attachments lfs avatars
|
||||
echo "[$(date)] Data backed up" | tee -a "$LOG_FILE"
|
||||
|
||||
# Clean old backups
|
||||
find "$BACKUP_DIR" -type f -name "*.gz" -mtime +{{ forgejo_backup_retention_days }} -delete
|
||||
echo "[$(date)] Old backups cleaned" | tee -a "$LOG_FILE"
|
||||
|
||||
echo "[$(date)] Backup completed successfully" | tee -a "$LOG_FILE"
|
||||
24
ansible/roles/forgejo/templates/postgres_backup.sh.j2
Normal file
24
ansible/roles/forgejo/templates/postgres_backup.sh.j2
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
# PostgreSQL Backup Script
|
||||
# Generated by Ansible
|
||||
|
||||
set -e
|
||||
|
||||
BACKUP_DIR="{{ forgejo_backup_path }}"
|
||||
TIMESTAMP=$(date +%Y%m%dT%H%M%S)
|
||||
LOG_FILE="/var/log/postgres-backup.log"
|
||||
|
||||
# Ensure backup directory exists
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
echo "[$(date)] Starting PostgreSQL backup..." | tee -a "$LOG_FILE"
|
||||
|
||||
# Create database backup
|
||||
sudo -u postgres pg_dump {{ forgejo_db_name }} | gzip > "$BACKUP_DIR/postgres-$TIMESTAMP.sql.gz"
|
||||
|
||||
echo "[$(date)] PostgreSQL backup completed: postgres-$TIMESTAMP.sql.gz" | tee -a "$LOG_FILE"
|
||||
|
||||
# Clean old PostgreSQL backups (keep last {{ forgejo_backup_retention_days }} days)
|
||||
find "$BACKUP_DIR" -type f -name "postgres-*.sql.gz" -mtime +{{ forgejo_backup_retention_days }} -delete
|
||||
|
||||
echo "[$(date)] Old PostgreSQL backups cleaned" | tee -a "$LOG_FILE"
|
||||
42
ansible/roles/forgejo/templates/prometheus.yml.j2
Normal file
42
ansible/roles/forgejo/templates/prometheus.yml.j2
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Prometheus configuration for Forgejo monitoring
|
||||
# Generated by Ansible
|
||||
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
# Forgejo metrics endpoint
|
||||
- job_name: 'forgejo'
|
||||
scheme: http
|
||||
static_configs:
|
||||
- targets: ['forgejo:3000']
|
||||
metrics_path: /metrics
|
||||
bearer_token: '{{ vault_forgejo_metrics_token | default("") }}'
|
||||
|
||||
# Prometheus self-monitoring
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
{% if forgejo_db_type == 'postgres' %}
|
||||
# PostgreSQL metrics (if postgres_exporter is enabled)
|
||||
# Uncomment and configure if you add postgres_exporter
|
||||
# - job_name: 'postgres'
|
||||
# static_configs:
|
||||
# - targets: ['postgres_exporter:9187']
|
||||
{% endif %}
|
||||
|
||||
{% if forgejo_use_redis %}
|
||||
# Redis metrics (if redis_exporter is enabled)
|
||||
# Uncomment and configure if you add redis_exporter
|
||||
# - job_name: 'redis'
|
||||
# static_configs:
|
||||
# - targets: ['redis_exporter:9121']
|
||||
{% endif %}
|
||||
|
||||
# Node metrics (if node_exporter is enabled)
|
||||
# Uncomment and configure if you add node_exporter
|
||||
# - job_name: 'node'
|
||||
# static_configs:
|
||||
# - targets: ['node_exporter:9100']
|
||||
14
ansible/roles/forgejo/templates/ufw-forgejo.j2
Normal file
14
ansible/roles/forgejo/templates/ufw-forgejo.j2
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[Forgejo]
|
||||
title=Forgejo Git Forge
|
||||
description=Forgejo self-hosted Git service (web interface)
|
||||
ports=80,443/tcp
|
||||
|
||||
[Forgejo-SSH]
|
||||
title=Forgejo Git SSH
|
||||
description=Forgejo Git operations over SSH
|
||||
ports={{ forgejo_ssh_port }}/tcp
|
||||
|
||||
[Forgejo-Full]
|
||||
title=Forgejo Full
|
||||
description=Forgejo web interface and Git SSH
|
||||
ports=80,443,{{ forgejo_ssh_port }}/tcp
|
||||
Loading…
Add table
Add a link
Reference in a new issue