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:
Horacio Duran 2026-01-09 16:07:44 +01:00
parent a9f546f92a
commit 822e42dbb8
48 changed files with 6846 additions and 2 deletions

View 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 %}

View 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

View file

@ -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

View 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

View 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

View 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"

View 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"

View 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']

View 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