ansible rework

This commit is contained in:
Sergei Poljanski 2025-11-04 11:06:35 +02:00
commit eecff4345a
47 changed files with 1995 additions and 538 deletions

View file

@ -1,16 +0,0 @@
# Cloud-init already handles:
# - Package installation (fail2ban, curl, git, htop, ncdu, tree, tmux, etc.)
# - fail2ban service enabled and started
# - User creation (asxpi with sudo + docker, deploy with sudo + docker)
# - SSH hardening (key-only auth, prohibit root password)
# - Docker installation and configuration
# - Docker image pruning cron job (weekly at 3am Monday)
# - Directory structure creation
#
# This role is now for post-cloud-init configuration only
- name: Verify cloud-init completed successfully
command: cloud-init status --wait
register: cloud_init_status
changed_when: false
failed_when: "'done' not in cloud_init_status.stdout"

View file

@ -0,0 +1,34 @@
---
# Default variables for docker_service role
# These can be overridden when including this role
# Service configuration
service_name: "" # REQUIRED: Name of the service (e.g., "traefik", "forgejo")
service_base_path: "" # REQUIRED: Base installation path (e.g., "/opt/traefik")
# Directories to create under service_base_path
# Format: list of dicts with 'path' and 'mode' keys
service_directories: []
# Example:
# - { path: "data", mode: "0755" }
# - { path: "config", mode: "0644" }
# Template files to deploy
# Format: list of dicts with 'src', 'dest', and optional 'mode' keys
service_templates: []
# Example:
# - { src: "docker-compose.yml.j2", dest: "docker-compose.yml", mode: "0644" }
# - { src: "config.yaml.j2", dest: "config/config.yaml", mode: "0600" }
# Docker compose configuration
docker_compose_file: "docker-compose.yml" # Name of the compose file
docker_compose_project_name: "{{ service_name }}" # Docker compose project name
# Health check configuration
health_check_enabled: true # Whether to perform health check after deployment
health_check_url: "" # URL to check (empty = skip HTTP check)
health_check_host: "localhost" # Host to check port on (default: localhost)
health_check_port: "" # Port to check (empty = skip port check)
health_check_delay: 10 # Seconds to wait before health check
health_check_retries: 30 # Number of retries for health check
health_check_retry_delay: 2 # Seconds between retries

View file

@ -0,0 +1,81 @@
---
# Shared tasks for deploying Docker-based services
# This role provides a standardized pattern for service deployment
- name: Validate required variables
ansible.builtin.assert:
that:
- service_name is defined
- service_name | length > 0
- service_base_path is defined
- service_base_path | length > 0
fail_msg: "service_name and service_base_path are required variables"
success_msg: "Required variables validated for {{ service_name }}"
- name: Create base service directory
ansible.builtin.file:
path: "{{ service_base_path }}"
state: directory
mode: '0755'
owner: root
group: root
- name: Create service subdirectories
ansible.builtin.file:
path: "{{ service_base_path }}/{{ item.path }}"
state: directory
mode: "{{ item.mode | default('0755') }}"
owner: root
group: root
loop: "{{ service_directories }}"
when: service_directories | length > 0
- name: Deploy service template files
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ service_base_path }}/{{ item.dest }}"
mode: "{{ item.mode | default('0644') }}"
owner: root
group: root
loop: "{{ service_templates }}"
when: service_templates | length > 0
register: template_result
- name: Start service with Docker Compose
community.docker.docker_compose_v2:
project_src: "{{ service_base_path }}"
project_name: "{{ docker_compose_project_name }}"
files:
- "{{ docker_compose_file }}"
state: present
register: compose_result
- name: Wait before health check
ansible.builtin.pause:
seconds: "{{ health_check_delay }}"
when: health_check_enabled and (health_check_url != "" or health_check_port != "")
- name: Wait for service HTTP endpoint
ansible.builtin.uri:
url: "{{ health_check_url }}"
status_code: [200, 301, 302, 401, 404] # Accept various codes (service-dependent)
timeout: 5
register: health_result
until: health_result is not failed
retries: "{{ health_check_retries }}"
delay: "{{ health_check_retry_delay }}"
when: health_check_enabled and health_check_url != ""
failed_when: false
- name: Wait for service port
ansible.builtin.wait_for:
host: "{{ health_check_host }}"
port: "{{ health_check_port }}"
delay: 0
timeout: "{{ health_check_retries * health_check_retry_delay }}"
state: started
when: health_check_enabled and health_check_port != ""
- name: Display deployment status
ansible.builtin.debug:
msg: "{{ service_name }} deployment completed successfully"

View file

@ -1,42 +1,23 @@
---
- name: Validate Forgejo variables
ansible.builtin.import_tasks: validate.yaml
- name: Configure SSH passthrough
import_tasks: ssh-passthrough.yaml
ansible.builtin.import_tasks: ssh-passthrough.yaml
- name: Create Forgejo directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/forgejo
- /opt/forgejo/data
- /opt/forgejo/config
- name: Deploy Forgejo app.ini configuration
template:
src: app.ini.j2
dest: /opt/forgejo/config/app.ini
mode: '0644'
notify: Restart Forgejo
- name: Deploy Forgejo docker-compose
template:
src: docker-compose.yml.j2
dest: /opt/forgejo/docker-compose.yml
mode: '0644'
notify: Restart Forgejo
- name: Start Forgejo
community.docker.docker_compose_v2:
project_src: /opt/forgejo
state: present
register: forgejo_output
- name: Wait for Forgejo to be healthy
uri:
url: http://localhost:{{ forgejo_http_port }}/
status_code: 200
retries: 15
delay: 5
register: result
until: result.status == 200
- name: Deploy Forgejo using shared docker_service role
ansible.builtin.include_role:
name: docker_service
vars:
service_name: "forgejo"
service_base_path: "{{ forgejo_base_path }}"
service_directories:
- { path: "data", mode: "0755" }
- { path: "config", mode: "0755" }
service_templates:
- { src: "app.ini.j2", dest: "config/app.ini", mode: "0644" }
- { src: "docker-compose.yml.j2", dest: "docker-compose.yml", mode: "0644" }
health_check_enabled: true
health_check_url: "http://localhost:{{ forgejo_http_port }}/"
health_check_delay: 5
health_check_retries: 15

View file

@ -0,0 +1,45 @@
---
# Variable validation for forgejo role
- name: Validate Forgejo required variables
ansible.builtin.assert:
that:
- forgejo_base_path is defined
- forgejo_base_path | length > 0
- forgejo_http_port is defined
- forgejo_http_port | int > 0
- forgejo_http_port | int < 65536
- forgejo_ssh_port is defined
- forgejo_ssh_port | int > 0
- forgejo_ssh_port | int < 65536
- forgejo_domain is defined
- forgejo_domain | length > 0
fail_msg: |
Forgejo role requires:
- forgejo_base_path (installation directory)
- forgejo_http_port (1-65535)
- forgejo_ssh_port (1-65535)
- forgejo_domain (domain name)
success_msg: "Forgejo basic variables validated successfully"
- name: Validate Forgejo secrets (if not using vault)
ansible.builtin.assert:
that:
- forgejo_secret_key is defined
- forgejo_secret_key | length >= 32
- forgejo_internal_token is defined
- forgejo_internal_token | length > 0
- forgejo_jwt_secret is defined
- forgejo_jwt_secret | length >= 32
- forgejo_lfs_jwt_secret is defined
- forgejo_lfs_jwt_secret | length >= 32
fail_msg: |
Forgejo role requires secrets:
- forgejo_secret_key (min 32 chars)
- forgejo_internal_token
- forgejo_jwt_secret (min 32 chars)
- forgejo_lfs_jwt_secret (min 32 chars)
WARNING: These should be stored in Ansible Vault!
success_msg: "Forgejo secrets validated (WARNING: should use Ansible Vault)"
when: not ansible_check_mode

View file

@ -40,7 +40,7 @@ INTERNAL_TOKEN = {{ forgejo_internal_token | default('CHANGE-ME-PLEASE-GENERATE-
PASSWORD_HASH_ALGO = pbkdf2
[service]
DISABLE_REGISTRATION = false
DISABLE_REGISTRATION = true
REQUIRE_SIGNIN_VIEW = false
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false

View file

@ -16,19 +16,54 @@
file_type: file
register: existing_dumps
- name: Create backup archive from old server
- name: Create temporary staging directory for backup
file:
path: /tmp/forgejo-backup-staging
state: directory
mode: '0755'
become: true
- name: Copy database to staging
copy:
src: /srv/gitea/gitea.db
dest: /tmp/forgejo-backup-staging/gitea.db
remote_src: true
become: true
- name: Create data directory in staging
file:
path: /tmp/forgejo-backup-staging/data
state: directory
mode: '0755'
become: true
- name: Copy data directories to staging
copy:
src: "/srv/gitea/data/{{ item }}/"
dest: "/tmp/forgejo-backup-staging/data/{{ item }}/"
remote_src: true
loop:
- avatars
- repo-avatars
- repo-archive
- attachments
- gitea-repositories
become: true
ignore_errors: true
- name: Create backup archive from staging
archive:
path:
- /srv/gitea/gitea.db
- /srv/gitea/data/avatars
- /srv/gitea/data/repo-avatars
- /srv/gitea/data/repo-archive
- /srv/gitea/data/attachments
- /srv/gitea/data/gitea-repositories
path: /tmp/forgejo-backup-staging
dest: /tmp/forgejo-full-backup-{{ ansible_date_time.date }}.tar.gz
format: gz
become: true
- name: Clean up staging directory
file:
path: /tmp/forgejo-backup-staging
state: absent
become: true
- name: Display found backup files
debug:
msg: |

View file

@ -63,59 +63,45 @@
dest: /tmp/forgejo-restore/
remote_src: true
- name: Set backup source path
set_fact:
backup_source: "/tmp/forgejo-restore/forgejo-backup-staging"
- name: Copy database from backup
copy:
src: /tmp/forgejo-restore/gitea.db
src: "{{ backup_source }}/gitea.db"
dest: /opt/forgejo/data/gitea/gitea.db
remote_src: true
owner: 1000
group: 1000
mode: '0644'
- name: Copy avatars from backup
synchronize:
src: /tmp/forgejo-restore/data/avatars/
dest: /opt/forgejo/data/gitea/data/avatars/
delegate_to: "{{ inventory_hostname }}"
ignore_errors: true
- name: Copy repo-avatars from backup
synchronize:
src: /tmp/forgejo-restore/data/repo-avatars/
dest: /opt/forgejo/data/gitea/data/repo-avatars/
delegate_to: "{{ inventory_hostname }}"
ignore_errors: true
- name: Copy repo-archive from backup
synchronize:
src: /tmp/forgejo-restore/data/repo-archive/
dest: /opt/forgejo/data/gitea/data/repo-archive/
delegate_to: "{{ inventory_hostname }}"
ignore_errors: true
- name: Copy attachments from backup
synchronize:
src: /tmp/forgejo-restore/data/attachments/
dest: /opt/forgejo/data/gitea/data/attachments/
delegate_to: "{{ inventory_hostname }}"
ignore_errors: true
- name: Copy repositories from backup
synchronize:
src: /tmp/forgejo-restore/data/gitea-repositories/
dest: /opt/forgejo/data/git/repositories/
delegate_to: "{{ inventory_hostname }}"
- name: Copy data directories from backup
ansible.builtin.copy:
src: "{{ backup_source }}/data/{{ item.src }}/"
dest: "{{ forgejo_base_path | default('/opt/forgejo') }}/data/{{ item.dest }}/"
remote_src: true
owner: 1000
group: 1000
mode: preserve
loop:
- { src: "avatars", dest: "gitea/data/avatars" }
- { src: "repo-avatars", dest: "gitea/data/repo-avatars" }
- { src: "repo-archive", dest: "gitea/data/repo-archive" }
- { src: "attachments", dest: "gitea/data/attachments" }
- { src: "gitea-repositories", dest: "git/repositories" }
ignore_errors: "{{ item.src != 'gitea-repositories' }}" # Only fail on repositories copy failure
- name: Set correct ownership for all Forgejo data
file:
path: /opt/forgejo/data
ansible.builtin.file:
path: "{{ forgejo_base_path | default('/opt/forgejo') }}/data"
owner: 1000
group: 1000
recurse: true
- name: Start Forgejo container
community.docker.docker_compose_v2:
project_src: /opt/forgejo
project_src: "{{ forgejo_base_path | default('/opt/forgejo') }}"
state: present
- name: Wait for Forgejo to be healthy
@ -131,8 +117,9 @@
state: absent
- name: Verify users in database
shell: docker exec -u git forgejo forgejo admin user list
ansible.builtin.command: docker exec -u git forgejo forgejo admin user list
register: user_list
changed_when: false
ignore_errors: true
- name: Display restore information
@ -141,10 +128,10 @@
Forgejo restore completed successfully!
Users:
{{ user_list.stdout }}
{{ user_list.stdout_lines | join('\n ') }}
Forgejo is now running at:
- Internal: http://localhost:{{ forgejo_http_port }}
- Internal: http://{{ asxpio_ops_wireguard_ip }}:{{ forgejo_http_port }}
- External: https://{{ forgejo_domain }}
Next steps:

View file

@ -1,42 +1,29 @@
---
- name: Create Forgejo Runner directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/forgejo-runner
- /opt/forgejo-runner/data
- /opt/forgejo-runner/config
- name: Validate Forgejo Runner variables
ansible.builtin.import_tasks: validate.yaml
- name: Deploy Forgejo Runner configuration
template:
src: config.yaml.j2
dest: /opt/forgejo-runner/config/config.yaml
mode: '0644'
notify: Restart Forgejo Runner
- name: Deploy Forgejo Runner docker-compose
template:
src: docker-compose.yml.j2
dest: /opt/forgejo-runner/docker-compose.yml
mode: '0644'
notify: Restart Forgejo Runner
- name: Start Forgejo Runner
community.docker.docker_compose_v2:
project_src: /opt/forgejo-runner
state: present
register: runner_output
- name: Deploy Forgejo Runner using shared docker_service role
ansible.builtin.include_role:
name: docker_service
vars:
service_name: "forgejo-runner"
service_base_path: "{{ forgejo_runner_base_path }}"
service_directories:
- { path: "data", mode: "0755" }
- { path: "config", mode: "0755" }
service_templates:
- { src: "config.yaml.j2", dest: "config/config.yaml", mode: "0644" }
- { src: "docker-compose.yml.j2", dest: "docker-compose.yml", mode: "0644" }
health_check_enabled: false # Runner doesn't expose HTTP port
- name: Check if runner is already registered
ansible.builtin.stat:
path: /opt/forgejo-runner/data/.runner
path: "{{ forgejo_runner_base_path }}/data/.runner"
register: runner_registered
- name: Set permissions on data directory
ansible.builtin.file:
path: /opt/forgejo-runner/data
path: "{{ forgejo_runner_base_path }}/data"
state: directory
mode: '0777'
recurse: true
@ -44,7 +31,7 @@
- name: Stop runner for registration
community.docker.docker_compose_v2:
project_src: /opt/forgejo-runner
project_src: "{{ forgejo_runner_base_path }}"
state: stopped
when: not runner_registered.stat.exists
@ -52,31 +39,32 @@
ansible.builtin.command:
cmd: >
docker run --rm
-v /opt/forgejo-runner/data:/data
-v /opt/forgejo-runner/config/config.yaml:/config.yaml
-v {{ forgejo_runner_base_path }}/data:/data
-v {{ forgejo_runner_base_path }}/config/config.yaml:/config.yaml
code.forgejo.org/forgejo/runner:{{ forgejo_runner_version }}
forgejo-runner register
--no-interactive
--token {{ forgejo_runner_token }}
--name runner-{{ inventory_hostname }}
--instance http://10.10.0.10:{{ forgejo_http_port }}
--instance http://{{ asxpio_ops_vpc_ip }}:{{ forgejo_http_port }}
--labels ubuntu-latest:docker://node:20-bookworm,ubuntu-22.04:docker://node:20-bookworm,ubuntu-24.04:docker://node:22-bookworm,debian-latest:docker://debian:bookworm,debian-12:docker://debian:bookworm,debian-11:docker://debian:bullseye,arch-latest:docker://archlinux:latest,nginx-latest:docker://nginx:latest,nginx-alpine:docker://nginx:alpine,node-20:docker://node:20,node-22:docker://node:22,python-3.11:docker://python:3.11,python-3.12:docker://python:3.12,alpine-latest:docker://alpine:latest
when: not runner_registered.stat.exists
changed_when: true
register: registration_result
- name: Start runner after registration
community.docker.docker_compose_v2:
project_src: /opt/forgejo-runner
project_src: "{{ forgejo_runner_base_path }}"
state: present
when: not runner_registered.stat.exists
- name: Display runner status
debug:
ansible.builtin.debug:
msg: |
Forgejo Runner deployed successfully!
Runner name: runner-{{ inventory_hostname }}
Forgejo instance: http://10.10.0.10:{{ forgejo_http_port }}
Forgejo instance: http://{{ asxpio_ops_vpc_ip }}:{{ forgejo_http_port }}
{% if runner_registered.stat.exists %}
Status: Already registered (using existing registration)
{% else %}

View file

@ -0,0 +1,26 @@
---
# Variable validation for forgejo_runner role
- name: Validate Forgejo Runner required variables
ansible.builtin.assert:
that:
- forgejo_runner_base_path is defined
- forgejo_runner_base_path | length > 0
- forgejo_runner_token is defined
- forgejo_runner_token | length > 0
- forgejo_runner_version is defined
- forgejo_runner_version | length > 0
- forgejo_http_port is defined
- forgejo_http_port | int > 0
- asxpio_ops_vpc_ip is defined
- asxpio_ops_vpc_ip | length > 0
fail_msg: |
Forgejo Runner role requires:
- forgejo_runner_base_path (installation directory)
- forgejo_runner_token (runner registration token)
- forgejo_runner_version (e.g., '3.5.1')
- forgejo_http_port (Forgejo HTTP port)
- asxpio_ops_vpc_ip (Forgejo server IP)
WARNING: forgejo_runner_token should be stored in Ansible Vault!
success_msg: "Forgejo Runner variables validated (WARNING: token should use Ansible Vault)"

View file

@ -0,0 +1,52 @@
---
# S3FS role defaults
# S3 Configuration
s3_endpoint: "https://{{ s3_region }}.your-objectstorage.com"
s3_region: "hel1"
# S3 Credentials (should be overridden in group_vars or vault)
s3_access_key: ""
s3_secret_key: ""
# S3 Buckets to mount (non-replication buckets only)
s3_buckets:
- name: asxpio-terraform-main
mount_point: /mnt/s3/terraform
owner: root
group: root
mode: "0755"
- name: asxpio-backups-main
mount_point: /mnt/s3/backups
owner: root
group: root
mode: "0755"
- name: asxpio-immich-main
mount_point: /mnt/s3/immich
owner: root
group: docker
mode: "0775"
# S3FS mount options
# Note: s3fs_ensure_diskfree is defined per-host in host_vars/
s3fs_options:
- "passwd_file=/etc/s3fs/.passwd-s3fs"
- "use_path_request_style"
- "url={{ s3_endpoint }}"
- "allow_other"
- "umask=0022"
- "uid={{ s3fs_uid | default('0') }}"
- "gid={{ s3fs_gid | default('0') }}"
- "use_cache=/var/cache/s3fs"
- "ensure_diskfree={{ s3fs_ensure_diskfree | default(81920) }}"
- "max_stat_cache_size=10000"
- "stat_cache_expire=900"
- "parallel_count=15"
- "multipart_size=52"
- "nocopyapi"
- "enable_noobj_cache"
# Package dependencies
s3fs_packages:
- s3fs
- fuse3

View file

@ -0,0 +1,96 @@
---
# S3FS role tasks - Mount S3 buckets using s3fs
- name: Install s3fs and dependencies
ansible.builtin.apt:
name: "{{ s3fs_packages }}"
state: present
update_cache: true
- name: Create s3fs credentials directory
ansible.builtin.file:
path: /etc/s3fs
state: directory
mode: '0700'
owner: root
group: root
- name: Create s3fs password file
ansible.builtin.template:
src: passwd-s3fs.j2
dest: /etc/s3fs/.passwd-s3fs
mode: '0600'
owner: root
group: root
no_log: true
- name: Create S3 cache directory
ansible.builtin.file:
path: /var/cache/s3fs
state: directory
mode: '0755'
owner: root
group: root
- name: Create mount points for S3 buckets
ansible.builtin.file:
path: "{{ item.mount_point }}"
state: directory
mode: "{{ item.mode }}"
owner: "{{ item.owner }}"
group: "{{ item.group }}"
loop: "{{ s3_buckets }}"
loop_control:
label: "{{ item.name }} -> {{ item.mount_point }}"
- name: Add S3 buckets to /etc/fstab
ansible.builtin.lineinfile:
path: /etc/fstab
line: "{{ item.name }} {{ item.mount_point }} fuse.s3fs _netdev,{{ s3fs_options | join(',') }} 0 0"
regexp: "^{{ item.name }}\\s+{{ item.mount_point }}"
state: present
loop: "{{ s3_buckets }}"
loop_control:
label: "{{ item.name }} -> {{ item.mount_point }}"
- name: Mount all S3 buckets
ansible.builtin.command:
cmd: "mount {{ item.mount_point }}"
loop: "{{ s3_buckets }}"
loop_control:
label: "{{ item.name }} -> {{ item.mount_point }}"
register: mount_result
changed_when: mount_result.rc == 0
failed_when: false
- name: Verify S3 buckets are mounted
ansible.builtin.command:
cmd: "mountpoint -q {{ item.mount_point }}"
loop: "{{ s3_buckets }}"
loop_control:
label: "{{ item.name }} -> {{ item.mount_point }}"
register: verify_mount
changed_when: false
failed_when: verify_mount.rc != 0
- name: Display mounted S3 buckets
ansible.builtin.debug:
msg: |
✅ S3 Buckets mounted successfully:
{% for bucket in s3_buckets %}
- {{ bucket.name }} -> {{ bucket.mount_point }}
{% endfor %}
- name: Test write access to Immich bucket
ansible.builtin.copy:
content: "S3FS test file created on {{ ansible_date_time.iso8601 }}"
dest: "{{ s3_buckets | selectattr('name', 'equalto', 'asxpio-immich-main') | map(attribute='mount_point') | first }}/.s3fs-test"
mode: '0644'
when: "'asxpio-immich-main' in s3_buckets | map(attribute='name')"
register: write_test
- name: Remove test file
ansible.builtin.file:
path: "{{ s3_buckets | selectattr('name', 'equalto', 'asxpio-immich-main') | map(attribute='mount_point') | first }}/.s3fs-test"
state: absent
when: write_test is changed

View file

@ -0,0 +1 @@
{{ s3_access_key }}:{{ s3_secret_key }}

View file

@ -2,9 +2,10 @@
# SSH Hardening Role Default Variables
# Networks allowed to access SSH (VPC and WireGuard)
# These reference the centralized network variables from group_vars/network.yaml
ssh_allowed_networks:
- 10.20.0.0/24 # WireGuard VPN
- 10.10.0.0/20 # Internal VPC
- "{{ wireguard_network }}" # WireGuard VPN
- "{{ vpc_network }}" # Internal VPC
# Whether to allow public SSH access for git operations (Forgejo)
# Set to true on Forgejo host, false on other servers

View file

@ -16,7 +16,30 @@
owner: root
group: root
validate: 'sshd -t -f %s'
notify: restart sshd
register: ssh_config_changed
- name: Restart SSH daemon asynchronously (to maintain connection)
ansible.builtin.shell: "nohup systemctl restart sshd &"
when: ssh_config_changed.changed
changed_when: true
- name: Wait for SSH port to become available
ansible.builtin.wait_for:
host: "{{ ansible_host | default(inventory_hostname) }}"
port: 22
delay: 2
timeout: 30
state: started
when: ssh_config_changed.changed
delegate_to: localhost
- name: Reset SSH connection to pick up new configuration
ansible.builtin.meta: reset_connection
when: ssh_config_changed.changed
- name: Verify SSH connection after restart
ansible.builtin.ping:
when: ssh_config_changed.changed
- name: Configure iptables for SSH restrictions (non-Forgejo servers)
block:
@ -25,7 +48,7 @@
chain: INPUT
protocol: tcp
destination_port: '22'
source: 10.20.0.0/24
source: "{{ wireguard_network }}"
jump: ACCEPT
comment: Allow SSH from WireGuard VPN
@ -34,7 +57,7 @@
chain: INPUT
protocol: tcp
destination_port: '22'
source: 10.10.0.0/20
source: "{{ vpc_network }}"
jump: ACCEPT
comment: Allow SSH from internal VPC

View file

@ -20,13 +20,10 @@ KexAlgorithms {{ ssh_kex_algorithms | join(',') }}
PubkeyAcceptedAlgorithms {{ ssh_pubkey_accepted_algorithms | join(',') }}
# Access control - restrict shell users to trusted networks only
{% for user in ssh_shell_users %}
{% for network in ssh_allowed_networks %}
AllowUsers {{ user }}@{{ network }}
{% endfor %}
{% endfor %}
{% if ssh_shell_users | length > 0 and ssh_allowed_networks | length > 0 %}
AllowUsers {% for user in ssh_shell_users %}{% for network in ssh_allowed_networks %}{{ user }}@{{ network }} {% endfor %}{% endfor %}{% if ssh_allow_public_git %}{{ ssh_git_user }}{% endif %}
{% if ssh_allow_public_git %}
{% elif ssh_allow_public_git %}
# Allow Forgejo git user from anywhere for git operations
AllowUsers {{ ssh_git_user }}
{% endif %}

View file

@ -1,14 +1,6 @@
---
- name: Create Traefik directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- /opt/traefik
- /opt/traefik/config
- /opt/traefik/letsencrypt
- /opt/traefik/logs
- name: Validate Traefik variables
ansible.builtin.import_tasks: validate.yaml
- name: Create Traefik Docker network
community.docker.docker_network:
@ -16,36 +8,21 @@
driver: bridge
state: present
- name: Deploy Traefik static configuration
template:
src: traefik.yml.j2
dest: /opt/traefik/traefik.yml
mode: '0644'
notify: Restart Traefik
- name: Deploy Traefik dynamic configuration
template:
src: dynamic.yml.j2
dest: /opt/traefik/config/dynamic.yml
mode: '0644'
notify: Restart Traefik
- name: Deploy Traefik docker-compose
template:
src: docker-compose.yml.j2
dest: /opt/traefik/docker-compose.yml
mode: '0644'
notify: Restart Traefik
- name: Start Traefik
community.docker.docker_compose_v2:
project_src: /opt/traefik
state: present
register: traefik_output
- name: Wait for Traefik to be healthy
wait_for:
host: "{{ wireguard_ipv4 }}"
port: 8080
timeout: 30
delay: 3
- name: Deploy Traefik using shared docker_service role
ansible.builtin.include_role:
name: docker_service
vars:
service_name: "traefik"
service_base_path: "{{ traefik_base_path }}"
service_directories:
- { path: "config", mode: "0755" }
- { path: "letsencrypt", mode: "0755" }
- { path: "logs", mode: "0755" }
service_templates:
- { src: "traefik.yml.j2", dest: "traefik.yml", mode: "0644" }
- { src: "dynamic.yml.j2", dest: "config/dynamic.yml", mode: "0644" }
- { src: "docker-compose.yml.j2", dest: "docker-compose.yml", mode: "0644" }
health_check_enabled: true
health_check_host: "{{ wireguard_ip }}" # Dashboard only accessible on WireGuard IP
health_check_port: "{{ traefik_dashboard_port }}"
health_check_delay: 3

View file

@ -0,0 +1,23 @@
---
# Variable validation for traefik role
- name: Validate Traefik required variables
ansible.builtin.assert:
that:
- traefik_base_path is defined
- traefik_base_path | length > 0
- traefik_network is defined
- traefik_network | length > 0
- traefik_dashboard_port is defined
- traefik_dashboard_port | int > 0
- traefik_dashboard_port | int < 65536
- letsencrypt_email is defined
- letsencrypt_email | length > 0
- letsencrypt_email is match("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
fail_msg: |
Traefik role requires:
- traefik_base_path (installation directory)
- traefik_network (Docker network name)
- traefik_dashboard_port (1-65535)
- letsencrypt_email (valid email address)
success_msg: "Traefik variables validated successfully"

View file

@ -1,4 +1,7 @@
---
- name: Validate WireGuard variables
ansible.builtin.import_tasks: validate.yaml
- name: Install WireGuard
ansible.builtin.apt:
name:

View file

@ -0,0 +1,19 @@
---
# Variable validation for wireguard role
- name: Validate WireGuard required variables
ansible.builtin.assert:
that:
- wireguard_network is defined
- wireguard_network | length > 0
- wireguard_port is defined
- wireguard_port | int > 0
- wireguard_port | int < 65536
- wireguard_interface is defined
- wireguard_interface | length > 0
fail_msg: |
WireGuard role requires:
- wireguard_network (CIDR notation, e.g., 10.20.0.0/24)
- wireguard_port (1-65535)
- wireguard_interface (e.g., wg0)
success_msg: "WireGuard variables validated successfully"

View file

@ -14,7 +14,7 @@ PublicKey = {{ wireguard_server_public_key }}
{% if item.preshared_key %}
PresharedKey = {{ item.preshared_key }}
{% endif %}
Endpoint = {{ ansible_host }}:{{ wireguard_port }}
Endpoint = {{ public_ip }}:{{ wireguard_port }}
# Allow all VPN traffic (peer-to-peer connectivity)
AllowedIPs = {{ wireguard_ipv4_network }},{{ wireguard_ipv6_network }}{% if wireguard_additional_routes | length > 0 %},{{ wireguard_additional_routes | join(',') }}{% endif %}