ansible rework
This commit is contained in:
parent
67414814c0
commit
eecff4345a
47 changed files with 1995 additions and 538 deletions
|
|
@ -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"
|
||||
34
ansible/roles/docker_service/defaults/main.yaml
Normal file
34
ansible/roles/docker_service/defaults/main.yaml
Normal 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
|
||||
81
ansible/roles/docker_service/tasks/main.yaml
Normal file
81
ansible/roles/docker_service/tasks/main.yaml
Normal 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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
45
ansible/roles/forgejo/tasks/validate.yaml
Normal file
45
ansible/roles/forgejo/tasks/validate.yaml
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
26
ansible/roles/forgejo_runner/tasks/validate.yaml
Normal file
26
ansible/roles/forgejo_runner/tasks/validate.yaml
Normal 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)"
|
||||
52
ansible/roles/s3fs/defaults/main.yaml
Normal file
52
ansible/roles/s3fs/defaults/main.yaml
Normal 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
|
||||
96
ansible/roles/s3fs/tasks/main.yaml
Normal file
96
ansible/roles/s3fs/tasks/main.yaml
Normal 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
|
||||
1
ansible/roles/s3fs/templates/passwd-s3fs.j2
Normal file
1
ansible/roles/s3fs/templates/passwd-s3fs.j2
Normal file
|
|
@ -0,0 +1 @@
|
|||
{{ s3_access_key }}:{{ s3_secret_key }}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
23
ansible/roles/traefik/tasks/validate.yaml
Normal file
23
ansible/roles/traefik/tasks/validate.yaml
Normal 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"
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
---
|
||||
- name: Validate WireGuard variables
|
||||
ansible.builtin.import_tasks: validate.yaml
|
||||
|
||||
- name: Install WireGuard
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
|
|
|
|||
19
ansible/roles/wireguard/tasks/validate.yaml
Normal file
19
ansible/roles/wireguard/tasks/validate.yaml
Normal 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"
|
||||
|
|
@ -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 %}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue