feat: ansible deployment setup voor dt-prod-01
- Forgejo + Redis Docker stack (wetgit-forgejo role) - FastAPI + Celery systemd services (wetgit-app role) - Nginx vhosts voor git.wetgit.nl en api.wetgit.nl (wetgit-nginx role) - SSL via Let's Encrypt (certbot webroot) - Backup script (forgejo dump, geen downtime) - Codeberg mirror script - Cron jobs voor backup/mirror/log cleanup - Ansible vault voor secrets (encrypted) Geïsoleerd van dt-platform: eigen poorten, users, directories.
This commit is contained in:
parent
1dc93b0f89
commit
c481ebf9e7
20 changed files with 833 additions and 0 deletions
10
ansible/ansible.cfg
Normal file
10
ansible/ansible.cfg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[defaults]
|
||||
inventory = inventory/hosts
|
||||
remote_tmp = /tmp/.ansible/tmp
|
||||
host_key_checking = True
|
||||
retry_files_enabled = False
|
||||
roles_path = roles
|
||||
vault_password_file = .vault_pass
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = True
|
||||
45
ansible/group_vars/wetgit/main.yml
Normal file
45
ansible/group_vars/wetgit/main.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# WetGIT - Nederlandse wetgeving als code
|
||||
# Deployment variables for dt-prod-01
|
||||
#
|
||||
# IMPORTANT: This server is shared with dt-platform.
|
||||
# Do NOT use ports 8001 (dt-chatbot), 8200 (grimoire).
|
||||
# Do NOT modify /opt/dt-chatbot, /opt/dt-skills-portal, /opt/grimoire.
|
||||
# Do NOT modify the global nginx.conf — only add vhost configs.
|
||||
|
||||
# --- Application ---
|
||||
app_name: wetgit
|
||||
app_dir: /opt/wetgit
|
||||
data_dir: /data/wetgit
|
||||
|
||||
# FastAPI backend
|
||||
backend_port: 8002
|
||||
backend_workers: 1
|
||||
backend_host: "127.0.0.1"
|
||||
|
||||
# --- Domains ---
|
||||
server_name: "api.wetgit.nl"
|
||||
forgejo_domain: "git.wetgit.nl"
|
||||
|
||||
# --- Forgejo ---
|
||||
forgejo_port: 3000
|
||||
forgejo_data_dir: /opt/wetgit/data
|
||||
forgejo_admin_user: coornhert
|
||||
forgejo_admin_email: coornhert@wetgit.nl
|
||||
|
||||
# --- Redis (Docker, shared network with Forgejo) ---
|
||||
redis_port: 6379
|
||||
redis_host: "127.0.0.1"
|
||||
|
||||
# --- Celery ---
|
||||
celery_concurrency: 2
|
||||
|
||||
# --- Codeberg mirror ---
|
||||
codeberg_api_token: "{{ vault_codeberg_api_token | default('') }}"
|
||||
|
||||
# --- AgentMail ---
|
||||
agentmail_api_key: "{{ vault_agentmail_api_key }}"
|
||||
|
||||
# --- Secrets (from vault.yml) ---
|
||||
# vault_agentmail_api_key
|
||||
# vault_codeberg_api_token (add when Codeberg account is ready)
|
||||
# vault_forgejo_admin_password (initial admin password)
|
||||
14
ansible/group_vars/wetgit/vault.yml
Normal file
14
ansible/group_vars/wetgit/vault.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
$ANSIBLE_VAULT;1.1;AES256
|
||||
35323237613730303463313335643433616238663932643630636530356461323433666435653436
|
||||
3433343462343538333335343165353538613435613962650a656166366364393564353733343561
|
||||
66643462313261643538653839393365643634376432373665653133383464313636633762366163
|
||||
6562336332396535390a333062323534373963356439353336633964383832313431623934653739
|
||||
37646339376338623536323336353931343039323263666265363763373266343533333236346635
|
||||
37656436623764393037393138343536313666613439666535656631313031343061346130376136
|
||||
64383164643466643162393537343265313632343432336238393030306164636434356463396434
|
||||
34656334383731326131393061333138643435366534333965376666393535316334396662633561
|
||||
61386636336438383563326565336635643663313934326333323939663637653531363261613733
|
||||
38646631333739303737616630663337663265616462346637326539306338613866313762306662
|
||||
38633066323936623233336631653836656531633839643739313966623065313931356630613134
|
||||
39636539643065663963626437383637643932633164306337626330623466313737623532366631
|
||||
6435
|
||||
2
ansible/inventory/hosts
Normal file
2
ansible/inventory/hosts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[wetgit]
|
||||
dt-prod-01 ansible_host=100.98.29.89 ansible_user=deploy ansible_become=yes
|
||||
12
ansible/roles/wetgit-app/handlers/main.yml
Normal file
12
ansible/roles/wetgit-app/handlers/main.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
- name: restart wetgit
|
||||
systemd:
|
||||
name: wetgit
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
|
||||
- name: restart wetgit-celery
|
||||
systemd:
|
||||
name: wetgit-celery
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
79
ansible/roles/wetgit-app/tasks/main.yml
Normal file
79
ansible/roles/wetgit-app/tasks/main.yml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
# WetGIT FastAPI application + Celery worker
|
||||
# Deploys to /opt/wetgit/backend with own venv and systemd services
|
||||
#
|
||||
# Directories are created by wetgit-forgejo role (runs first).
|
||||
# This role only manages the FastAPI app and Celery worker.
|
||||
#
|
||||
# NOTE: Services are only enabled when application code exists.
|
||||
# On first deploy (no code yet), this role is effectively a no-op.
|
||||
|
||||
- name: Check if application code exists
|
||||
stat:
|
||||
path: "{{ app_dir }}/backend/requirements.txt"
|
||||
register: app_code
|
||||
|
||||
- name: Create Python venv
|
||||
command: python3 -m venv {{ app_dir }}/backend/venv
|
||||
args:
|
||||
creates: "{{ app_dir }}/backend/venv/bin/python"
|
||||
when: app_code.stat.exists
|
||||
|
||||
- name: Set venv ownership
|
||||
file:
|
||||
path: "{{ app_dir }}/backend/venv"
|
||||
owner: www-data
|
||||
group: www-data
|
||||
recurse: yes
|
||||
when: app_code.stat.exists
|
||||
|
||||
- name: Install Python dependencies
|
||||
pip:
|
||||
requirements: "{{ app_dir }}/backend/requirements.txt"
|
||||
virtualenv: "{{ app_dir }}/backend/venv"
|
||||
when: app_code.stat.exists
|
||||
notify: restart wetgit
|
||||
|
||||
- name: Deploy environment file
|
||||
template:
|
||||
src: wetgit.env.j2
|
||||
dest: "{{ app_dir }}/backend/.env"
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: "0600"
|
||||
notify: restart wetgit
|
||||
|
||||
- name: Deploy WetGIT systemd service
|
||||
template:
|
||||
src: wetgit.service.j2
|
||||
dest: /etc/systemd/system/wetgit.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart wetgit
|
||||
|
||||
- name: Deploy Celery worker systemd service
|
||||
template:
|
||||
src: wetgit-celery.service.j2
|
||||
dest: /etc/systemd/system/wetgit-celery.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: restart wetgit-celery
|
||||
|
||||
# Only start services when app code is deployed
|
||||
- name: Enable and start WetGIT service
|
||||
systemd:
|
||||
name: wetgit
|
||||
enabled: yes
|
||||
state: started
|
||||
daemon_reload: yes
|
||||
when: app_code.stat.exists
|
||||
|
||||
- name: Enable and start Celery worker
|
||||
systemd:
|
||||
name: wetgit-celery
|
||||
enabled: yes
|
||||
state: started
|
||||
daemon_reload: yes
|
||||
when: app_code.stat.exists
|
||||
17
ansible/roles/wetgit-app/templates/wetgit-celery.service.j2
Normal file
17
ansible/roles/wetgit-app/templates/wetgit-celery.service.j2
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=WetGIT Celery Worker
|
||||
After=network.target docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory={{ app_dir }}/backend
|
||||
EnvironmentFile={{ app_dir }}/backend/.env
|
||||
ExecStart={{ app_dir }}/backend/venv/bin/celery -A tasks worker --loglevel=info --concurrency={{ celery_concurrency }}
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
19
ansible/roles/wetgit-app/templates/wetgit.env.j2
Normal file
19
ansible/roles/wetgit-app/templates/wetgit.env.j2
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# WetGIT environment — managed by Ansible
|
||||
# Do NOT edit manually on the server
|
||||
|
||||
# FastAPI
|
||||
WETGIT_HOST={{ backend_host }}
|
||||
WETGIT_PORT={{ backend_port }}
|
||||
WETGIT_WORKERS={{ backend_workers }}
|
||||
|
||||
# Redis / Celery
|
||||
REDIS_URL=redis://{{ redis_host }}:{{ redis_port }}/0
|
||||
CELERY_BROKER_URL=redis://{{ redis_host }}:{{ redis_port }}/0
|
||||
CELERY_RESULT_BACKEND=redis://{{ redis_host }}:{{ redis_port }}/1
|
||||
|
||||
# AgentMail
|
||||
AGENTMAIL_API_KEY={{ agentmail_api_key }}
|
||||
|
||||
# Data
|
||||
WETGIT_DATA_DIR={{ data_dir }}
|
||||
WETGIT_GIT_REPOS_DIR={{ data_dir }}/git-repos
|
||||
17
ansible/roles/wetgit-app/templates/wetgit.service.j2
Normal file
17
ansible/roles/wetgit-app/templates/wetgit.service.j2
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=WetGIT API - Nederlandse wetgeving als code
|
||||
After=network.target docker.service
|
||||
Wants=wetgit-celery.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory={{ app_dir }}/backend
|
||||
EnvironmentFile={{ app_dir }}/backend/.env
|
||||
ExecStart={{ app_dir }}/backend/venv/bin/uvicorn main:app --host {{ backend_host }} --port {{ backend_port }} --workers {{ backend_workers }}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
5
ansible/roles/wetgit-forgejo/handlers/main.yml
Normal file
5
ansible/roles/wetgit-forgejo/handlers/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
- name: restart forgejo
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ app_dir }}/docker"
|
||||
state: restarted
|
||||
148
ansible/roles/wetgit-forgejo/tasks/main.yml
Normal file
148
ansible/roles/wetgit-forgejo/tasks/main.yml
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
---
|
||||
# WetGIT Forgejo (self-hosted Git) + Redis
|
||||
#
|
||||
# Deploys Forgejo and Redis as Docker containers.
|
||||
# Forgejo serves git.wetgit.nl (HTTPS-only, no SSH — blocked by firewall).
|
||||
# Redis provides Celery broker for the WetGIT pipeline.
|
||||
#
|
||||
# IMPORTANT: Does NOT touch dt-platform's Docker services (grimoire).
|
||||
# All containers use the 'wetgit-network' Docker network.
|
||||
|
||||
# --- System user ---
|
||||
|
||||
- name: Create wetgit system user
|
||||
user:
|
||||
name: wetgit
|
||||
system: yes
|
||||
home: /opt/wetgit
|
||||
shell: /bin/bash
|
||||
create_home: no
|
||||
|
||||
- name: Get wetgit user UID
|
||||
command: id -u wetgit
|
||||
register: wetgit_uid_result
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Get wetgit user GID
|
||||
command: id -g wetgit
|
||||
register: wetgit_gid_result
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Store wetgit UID/GID as facts
|
||||
set_fact:
|
||||
wetgit_uid: "{{ wetgit_uid_result.stdout }}"
|
||||
wetgit_gid: "{{ wetgit_gid_result.stdout }}"
|
||||
|
||||
# --- Directories ---
|
||||
|
||||
- name: Create WetGIT directories
|
||||
file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: "{{ item.owner }}"
|
||||
group: "{{ item.group }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
# Forgejo directories (owned by wetgit user)
|
||||
- { path: "{{ app_dir }}/docker", owner: wetgit, group: wetgit }
|
||||
- { path: "{{ forgejo_data_dir }}", owner: wetgit, group: wetgit }
|
||||
- { path: "{{ forgejo_data_dir }}/gitea/conf", owner: wetgit, group: wetgit }
|
||||
- { path: "{{ data_dir }}/redis", owner: wetgit, group: wetgit }
|
||||
- { path: "{{ app_dir }}/scripts", owner: wetgit, group: wetgit }
|
||||
- { path: "{{ app_dir }}/backups", owner: wetgit, group: wetgit }
|
||||
- { path: "{{ app_dir }}/logs", owner: wetgit, group: wetgit }
|
||||
- { path: "{{ app_dir }}/mirrors", owner: wetgit, group: wetgit }
|
||||
# Application directories (owned by www-data for FastAPI/Celery)
|
||||
- { path: "{{ app_dir }}", owner: root, group: root }
|
||||
- { path: "{{ app_dir }}/backend", owner: www-data, group: www-data }
|
||||
- { path: "{{ data_dir }}", owner: root, group: root }
|
||||
- { path: "{{ data_dir }}/git-repos", owner: www-data, group: www-data }
|
||||
|
||||
# --- Forgejo config ---
|
||||
|
||||
- name: Deploy Forgejo app.ini (initial seed)
|
||||
template:
|
||||
src: app.ini.j2
|
||||
dest: "{{ forgejo_data_dir }}/gitea/conf/app.ini"
|
||||
owner: wetgit
|
||||
group: wetgit
|
||||
mode: "0644"
|
||||
# Don't overwrite if Forgejo has already modified it
|
||||
force: no
|
||||
notify: restart forgejo
|
||||
|
||||
# --- Docker Compose ---
|
||||
|
||||
- name: Deploy Docker Compose stack
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ app_dir }}/docker/docker-compose.yml"
|
||||
owner: wetgit
|
||||
group: wetgit
|
||||
mode: "0644"
|
||||
notify: restart forgejo
|
||||
|
||||
- name: Start WetGIT Docker stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ app_dir }}/docker"
|
||||
state: present
|
||||
|
||||
# --- Backup script ---
|
||||
|
||||
- name: Deploy backup script
|
||||
template:
|
||||
src: backup.sh.j2
|
||||
dest: "{{ app_dir }}/scripts/backup.sh"
|
||||
owner: wetgit
|
||||
group: wetgit
|
||||
mode: "0755"
|
||||
|
||||
# --- Mirror script ---
|
||||
|
||||
- name: Deploy Codeberg mirror script
|
||||
template:
|
||||
src: mirror-to-codeberg.sh.j2
|
||||
dest: "{{ app_dir }}/scripts/mirror-to-codeberg.sh"
|
||||
owner: wetgit
|
||||
group: wetgit
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy Codeberg token
|
||||
copy:
|
||||
content: "{{ codeberg_api_token }}"
|
||||
dest: "{{ app_dir }}/.codeberg-token"
|
||||
owner: wetgit
|
||||
group: wetgit
|
||||
mode: "0600"
|
||||
when: codeberg_api_token is defined and codeberg_api_token | length > 0
|
||||
|
||||
# --- Cron jobs ---
|
||||
|
||||
- name: Configure backup cron (weekly Sunday 02:00)
|
||||
cron:
|
||||
name: "wetgit-backup"
|
||||
user: root
|
||||
weekday: "0"
|
||||
hour: "2"
|
||||
minute: "0"
|
||||
job: "{{ app_dir }}/scripts/backup.sh >> {{ app_dir }}/logs/backup.log 2>&1"
|
||||
|
||||
- name: Configure Codeberg mirror cron (daily 04:00)
|
||||
cron:
|
||||
name: "wetgit-codeberg-mirror"
|
||||
user: wetgit
|
||||
hour: "4"
|
||||
minute: "0"
|
||||
job: "{{ app_dir }}/scripts/mirror-to-codeberg.sh >> {{ app_dir }}/logs/mirror.log 2>&1"
|
||||
when: codeberg_api_token is defined and codeberg_api_token | length > 0
|
||||
|
||||
- name: Configure log cleanup cron (monthly)
|
||||
cron:
|
||||
name: "wetgit-log-cleanup"
|
||||
user: wetgit
|
||||
day: "1"
|
||||
hour: "5"
|
||||
minute: "0"
|
||||
job: "find {{ app_dir }}/logs -name '*.log' -mtime +30 -delete"
|
||||
75
ansible/roles/wetgit-forgejo/templates/app.ini.j2
Normal file
75
ansible/roles/wetgit-forgejo/templates/app.ini.j2
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
; WetGit Forgejo configuration — managed by Ansible
|
||||
; This file is merged with Forgejo's defaults on first boot.
|
||||
; After first boot, Forgejo writes its own app.ini in /data/gitea/conf/.
|
||||
; This template is used to seed initial configuration.
|
||||
|
||||
[DEFAULT]
|
||||
APP_NAME = WetGit
|
||||
|
||||
[server]
|
||||
DOMAIN = {{ forgejo_domain }}
|
||||
SSH_DOMAIN = {{ forgejo_domain }}
|
||||
ROOT_URL = https://{{ forgejo_domain }}/
|
||||
HTTP_PORT = 3000
|
||||
; HTTPS-only — no SSH, firewall blocks port 2222
|
||||
DISABLE_SSH = true
|
||||
LFS_START_SERVER = true
|
||||
OFFLINE_MODE = false
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /data/gitea/forgejo.db
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = true
|
||||
|
||||
[repository]
|
||||
DEFAULT_BRANCH = main
|
||||
PREFERRED_LICENSES = MIT License,CC0-1.0
|
||||
MAX_CREATION_LIMIT = -1
|
||||
ENABLE_PUSH_CREATE_USER = true
|
||||
ENABLE_PUSH_CREATE_ORG = true
|
||||
; 100 MB max file size for large law datasets
|
||||
MAX_FILE_SIZE = 104857600
|
||||
|
||||
[git]
|
||||
MAX_GIT_DIFF_LINES = 10000
|
||||
MAX_GIT_DIFF_FILES = 1000
|
||||
|
||||
[git.timeout]
|
||||
DEFAULT = 600
|
||||
MIGRATE = 1200
|
||||
MIRROR = 600
|
||||
CLONE = 600
|
||||
PULL = 600
|
||||
GC = 120
|
||||
|
||||
[lfs]
|
||||
PATH = /data/git/lfs
|
||||
|
||||
[ui]
|
||||
DEFAULT_THEME = forgejo-auto
|
||||
SHOW_USER_EMAIL = false
|
||||
|
||||
[actions]
|
||||
ENABLED = true
|
||||
|
||||
[indexer]
|
||||
REPO_INDEXER_ENABLED = true
|
||||
REPO_INDEXER_PATH = /data/gitea/indexers/repos.bleve
|
||||
REPO_INDEXER_EXCLUDE = node_modules/**
|
||||
|
||||
[markup.markdown]
|
||||
ENABLED = true
|
||||
FILE_EXTENSIONS = .md,.markdown
|
||||
|
||||
[mailer]
|
||||
ENABLED = true
|
||||
PROTOCOL = smtp+starttls
|
||||
SMTP_ADDR = {{ forgejo_smtp_host | default('smtp.email.undefined') }}
|
||||
SMTP_PORT = {{ forgejo_smtp_port | default(587) }}
|
||||
FROM = Coornhert <coornhert@wetgit.nl>
|
||||
USER = {{ forgejo_smtp_user | default('') }}
|
||||
PASSWD = {{ forgejo_smtp_password | default('') }}
|
||||
37
ansible/roles/wetgit-forgejo/templates/backup.sh.j2
Normal file
37
ansible/roles/wetgit-forgejo/templates/backup.sh.j2
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# WetGIT Forgejo backup — managed by Ansible
|
||||
# Uses Forgejo's built-in dump command (no downtime).
|
||||
|
||||
BACKUP_DIR="{{ app_dir }}/backups"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
RETENTION_DAYS=14
|
||||
LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')]"
|
||||
|
||||
echo "$LOG_PREFIX Starting WetGit backup..."
|
||||
|
||||
# Forgejo dump (runs inside container, no service stop needed)
|
||||
docker exec wetgit-forgejo forgejo dump \
|
||||
--type tar.gz \
|
||||
--file /data/backup-${TIMESTAMP}.tar.gz \
|
||||
2>&1 || {
|
||||
echo "$LOG_PREFIX ERROR: Forgejo dump failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Move dump from container volume to backup dir
|
||||
mv "{{ forgejo_data_dir }}/backup-${TIMESTAMP}.tar.gz" \
|
||||
"$BACKUP_DIR/wetgit-forgejo-${TIMESTAMP}.tar.gz"
|
||||
|
||||
# Also backup Redis AOF
|
||||
docker exec wetgit-redis redis-cli BGSAVE 2>/dev/null || true
|
||||
sleep 2
|
||||
cp "{{ data_dir }}/redis/dump.rdb" \
|
||||
"$BACKUP_DIR/wetgit-redis-${TIMESTAMP}.rdb" 2>/dev/null || true
|
||||
|
||||
# Clean old backups
|
||||
find "$BACKUP_DIR" -name "wetgit-forgejo-*.tar.gz" -mtime +${RETENTION_DAYS} -delete
|
||||
find "$BACKUP_DIR" -name "wetgit-redis-*.rdb" -mtime +${RETENTION_DAYS} -delete
|
||||
|
||||
echo "$LOG_PREFIX Backup complete: wetgit-forgejo-${TIMESTAMP}.tar.gz"
|
||||
45
ansible/roles/wetgit-forgejo/templates/docker-compose.yml.j2
Normal file
45
ansible/roles/wetgit-forgejo/templates/docker-compose.yml.j2
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
services:
|
||||
forgejo:
|
||||
image: codeberg.org/forgejo/forgejo:10
|
||||
container_name: wetgit-forgejo
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID={{ wetgit_uid }}
|
||||
- USER_GID={{ wetgit_gid }}
|
||||
volumes:
|
||||
- {{ forgejo_data_dir }}:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "{{ backend_host }}:{{ forgejo_port }}:3000"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: "2.0"
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: "0.5"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/v1/version"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- wetgit
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: wetgit-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "{{ backend_host }}:{{ redis_port }}:6379"
|
||||
volumes:
|
||||
- {{ data_dir }}/redis:/data
|
||||
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
|
||||
networks:
|
||||
- wetgit
|
||||
|
||||
networks:
|
||||
wetgit:
|
||||
name: wetgit-network
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Mirror WetGit repos from self-hosted Forgejo to Codeberg
|
||||
# Managed by Ansible — runs daily at 04:00
|
||||
|
||||
CODEBERG_USER="coornhert"
|
||||
CODEBERG_TOKEN_FILE="{{ app_dir }}/.codeberg-token"
|
||||
MIRROR_DIR="{{ app_dir }}/mirrors"
|
||||
LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')]"
|
||||
|
||||
REPOS=(
|
||||
"wetgit/meta"
|
||||
"wetgit/rijk"
|
||||
# Add more as they are created:
|
||||
# "wetgit/cvdr-noord-holland"
|
||||
# "wetgit/eu"
|
||||
)
|
||||
|
||||
if [ ! -f "$CODEBERG_TOKEN_FILE" ]; then
|
||||
echo "$LOG_PREFIX ERROR: Codeberg token not found at $CODEBERG_TOKEN_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CODEBERG_TOKEN=$(cat "$CODEBERG_TOKEN_FILE")
|
||||
mkdir -p "$MIRROR_DIR"
|
||||
|
||||
for REPO in "${REPOS[@]}"; do
|
||||
REPO_NAME=$(basename "$REPO")
|
||||
REPO_MIRROR_DIR="$MIRROR_DIR/$REPO_NAME.git"
|
||||
FORGEJO_URL="https://{{ forgejo_domain }}/${REPO}.git"
|
||||
CODEBERG_URL="https://${CODEBERG_USER}:${CODEBERG_TOKEN}@codeberg.org/${REPO}.git"
|
||||
|
||||
echo "$LOG_PREFIX Mirroring $REPO..."
|
||||
|
||||
if [ ! -d "$REPO_MIRROR_DIR" ]; then
|
||||
echo "$LOG_PREFIX Initial clone from Forgejo..."
|
||||
git clone --bare "$FORGEJO_URL" "$REPO_MIRROR_DIR"
|
||||
cd "$REPO_MIRROR_DIR"
|
||||
git remote add codeberg "$CODEBERG_URL"
|
||||
else
|
||||
cd "$REPO_MIRROR_DIR"
|
||||
echo "$LOG_PREFIX Fetching from Forgejo..."
|
||||
git fetch origin --prune '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*'
|
||||
fi
|
||||
|
||||
echo "$LOG_PREFIX Pushing to Codeberg..."
|
||||
git push codeberg --mirror --force 2>&1 || {
|
||||
echo "$LOG_PREFIX WARNING: Push to Codeberg failed for $REPO (non-fatal)"
|
||||
}
|
||||
|
||||
echo "$LOG_PREFIX Done: $REPO"
|
||||
done
|
||||
|
||||
echo "$LOG_PREFIX Mirror complete."
|
||||
5
ansible/roles/wetgit-nginx/handlers/main.yml
Normal file
5
ansible/roles/wetgit-nginx/handlers/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
- name: reload nginx
|
||||
systemd:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
118
ansible/roles/wetgit-nginx/tasks/main.yml
Normal file
118
ansible/roles/wetgit-nginx/tasks/main.yml
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
# Nginx vhosts for WetGIT
|
||||
# IMPORTANT: Only adds vhost configs. Does NOT touch global nginx.conf
|
||||
# (managed by dt-platform's nginx role).
|
||||
#
|
||||
# Strategy: Deploy HTTP-only first → get SSL certs → deploy full HTTPS config.
|
||||
|
||||
# --- Step 1: Check existing SSL certificates ---
|
||||
|
||||
- name: Check if API SSL certificate exists
|
||||
stat:
|
||||
path: "/etc/letsencrypt/live/{{ server_name }}/fullchain.pem"
|
||||
register: ssl_cert_api
|
||||
|
||||
- name: Check if Forgejo SSL certificate exists
|
||||
stat:
|
||||
path: "/etc/letsencrypt/live/{{ forgejo_domain }}/fullchain.pem"
|
||||
register: ssl_cert_git
|
||||
|
||||
# --- Step 2: Deploy HTTP-only configs for domains without certs ---
|
||||
|
||||
- name: Deploy API HTTP-only vhost (pre-SSL)
|
||||
copy:
|
||||
content: |
|
||||
# Temporary HTTP-only config for SSL provisioning — managed by Ansible
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ server_name }};
|
||||
location /.well-known/acme-challenge/ { root /var/www/certbot; }
|
||||
location / { return 503; }
|
||||
}
|
||||
dest: /etc/nginx/sites-available/wetgit-api.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: not ssl_cert_api.stat.exists
|
||||
notify: reload nginx
|
||||
|
||||
- name: Deploy Forgejo HTTP-only vhost (pre-SSL)
|
||||
copy:
|
||||
content: |
|
||||
# Temporary HTTP-only config for SSL provisioning — managed by Ansible
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ forgejo_domain }};
|
||||
location /.well-known/acme-challenge/ { root /var/www/certbot; }
|
||||
location / { return 503; }
|
||||
}
|
||||
dest: /etc/nginx/sites-available/wetgit-git.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: not ssl_cert_git.stat.exists
|
||||
notify: reload nginx
|
||||
|
||||
# --- Step 3: Enable vhosts and reload nginx ---
|
||||
|
||||
- name: Enable API vhost
|
||||
file:
|
||||
src: /etc/nginx/sites-available/wetgit-api.conf
|
||||
dest: /etc/nginx/sites-enabled/wetgit-api.conf
|
||||
state: link
|
||||
notify: reload nginx
|
||||
|
||||
- name: Enable Forgejo vhost
|
||||
file:
|
||||
src: /etc/nginx/sites-available/wetgit-git.conf
|
||||
dest: /etc/nginx/sites-enabled/wetgit-git.conf
|
||||
state: link
|
||||
notify: reload nginx
|
||||
|
||||
# Force handler to run now so nginx has the HTTP configs before certbot
|
||||
- name: Flush handlers (reload nginx for certbot)
|
||||
meta: flush_handlers
|
||||
|
||||
# --- Step 4: Obtain SSL certificates via webroot ---
|
||||
|
||||
- name: Obtain SSL certificate for {{ server_name }}
|
||||
command: >
|
||||
certbot certonly --webroot
|
||||
-w /var/www/certbot
|
||||
-d {{ server_name }}
|
||||
--non-interactive --agree-tos
|
||||
--email coornhert@wetgit.nl
|
||||
when: not ssl_cert_api.stat.exists
|
||||
register: certbot_api
|
||||
|
||||
- name: Obtain SSL certificate for {{ forgejo_domain }}
|
||||
command: >
|
||||
certbot certonly --webroot
|
||||
-w /var/www/certbot
|
||||
-d {{ forgejo_domain }}
|
||||
--non-interactive --agree-tos
|
||||
--email coornhert@wetgit.nl
|
||||
when: not ssl_cert_git.stat.exists
|
||||
register: certbot_git
|
||||
|
||||
# --- Step 5: Deploy full HTTPS configs ---
|
||||
|
||||
- name: Deploy API nginx vhost (full HTTPS)
|
||||
template:
|
||||
src: wetgit-api.conf.j2
|
||||
dest: /etc/nginx/sites-available/wetgit-api.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: reload nginx
|
||||
|
||||
- name: Deploy Forgejo nginx vhost (full HTTPS)
|
||||
template:
|
||||
src: wetgit-git.conf.j2
|
||||
dest: /etc/nginx/sites-available/wetgit-git.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: reload nginx
|
||||
51
ansible/roles/wetgit-nginx/templates/wetgit-api.conf.j2
Normal file
51
ansible/roles/wetgit-nginx/templates/wetgit-api.conf.j2
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# WetGIT API — managed by WetGIT Ansible (not dt-platform)
|
||||
# Do NOT edit manually
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ server_name }};
|
||||
|
||||
# ACME challenge (reuse existing certbot webroot)
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name {{ server_name }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ server_name }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ server_name }}/privkey.pem;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# API proxy
|
||||
location / {
|
||||
proxy_pass http://{{ backend_host }}:{{ backend_port }};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Timeouts for long-running legislation processing
|
||||
proxy_read_timeout 120s;
|
||||
proxy_connect_timeout 10s;
|
||||
}
|
||||
|
||||
# Health check (no rate limit)
|
||||
location = /health {
|
||||
proxy_pass http://{{ backend_host }}:{{ backend_port }}/health;
|
||||
access_log off;
|
||||
}
|
||||
}
|
||||
52
ansible/roles/wetgit-nginx/templates/wetgit-git.conf.j2
Normal file
52
ansible/roles/wetgit-nginx/templates/wetgit-git.conf.j2
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Forgejo (git.wetgit.nl) — managed by WetGIT Ansible (not dt-platform)
|
||||
# Do NOT edit manually
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ forgejo_domain }};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name {{ forgejo_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ forgejo_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ forgejo_domain }}/privkey.pem;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Large body size for git pushes (law datasets can be large)
|
||||
client_max_body_size 512M;
|
||||
|
||||
# Timeouts for large git operations
|
||||
proxy_connect_timeout 300;
|
||||
proxy_send_timeout 300;
|
||||
proxy_read_timeout 300;
|
||||
|
||||
location / {
|
||||
proxy_pass http://{{ backend_host }}:{{ forgejo_port }};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# WebSocket support (Forgejo live features)
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
27
ansible/site.yml
Normal file
27
ansible/site.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
# WetGIT - Nederlandse wetgeving als code
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook ansible/site.yml
|
||||
# ansible-playbook ansible/site.yml --tags forgejo
|
||||
# ansible-playbook ansible/site.yml --tags app
|
||||
# ansible-playbook ansible/site.yml --tags nginx
|
||||
# ansible-playbook ansible/site.yml --check (dry-run)
|
||||
#
|
||||
# NOTE: This server is shared with dt-platform.
|
||||
# This playbook only manages WetGIT resources.
|
||||
# System-level config (users, packages, firewall) is managed by dt-platform.
|
||||
|
||||
- name: Deploy WetGIT
|
||||
hosts: wetgit
|
||||
become: yes
|
||||
|
||||
roles:
|
||||
- role: wetgit-forgejo
|
||||
tags: [forgejo, docker]
|
||||
|
||||
- role: wetgit-app
|
||||
tags: [app]
|
||||
|
||||
- role: wetgit-nginx
|
||||
tags: [nginx]
|
||||
Loading…
Add table
Reference in a new issue