chore: Update deployment configuration and documentation

- Update Gitea configuration (remove DEFAULT_ACTIONS_URL)
- Fix deployment documentation
- Update Ansible playbooks
- Clean up deprecated files
- Add new deployment scripts and templates
This commit is contained in:
2025-10-31 21:11:11 +01:00
parent cf4748f8db
commit 16d586ecdf
92 changed files with 4601 additions and 10524 deletions

View File

@@ -0,0 +1,68 @@
---
- name: Check Git Deployment Logs
hosts: production
gather_facts: yes
become: no
tasks:
- name: Get full container logs
shell: |
docker logs app --tail 100
args:
executable: /bin/bash
register: container_logs
changed_when: false
- name: Get Git-related logs
shell: |
docker logs app --tail 100 | grep -E "(Git|Clone|Pull|✅|❌|📥|📦|🔄|🗑️)" || echo "No Git-related logs found"
args:
executable: /bin/bash
register: git_logs
changed_when: false
- name: Check GIT_REPOSITORY_URL environment variable
shell: |
docker exec app env | grep GIT_REPOSITORY_URL || echo "GIT_REPOSITORY_URL not set"
args:
executable: /bin/bash
register: git_env
changed_when: false
ignore_errors: yes
- name: Check if .git directory exists
shell: |
docker exec app test -d /var/www/html/.git && echo "✅ Git repo vorhanden" || echo "❌ Git repo fehlt"
args:
executable: /bin/bash
register: git_repo_check
changed_when: false
ignore_errors: yes
- name: Check entrypoint script for Git functionality
shell: |
docker exec app cat /usr/local/bin/entrypoint.sh | grep -A 5 "GIT_REPOSITORY_URL" | head -10 || echo "Entrypoint script not found or no Git functionality"
args:
executable: /bin/bash
register: entrypoint_check
changed_when: false
ignore_errors: yes
- name: Display Git-related logs
debug:
msg:
- "=== Git-Related Logs ==="
- "{{ git_logs.stdout }}"
- ""
- "=== Git Environment Variable ==="
- "{{ git_env.stdout }}"
- ""
- "=== Git Repository Check ==="
- "{{ git_repo_check.stdout }}"
- ""
- "=== Entrypoint Git Check ==="
- "{{ entrypoint_check.stdout }}"
- name: Display full logs (last 50 lines)
debug:
msg: "{{ container_logs.stdout_lines[-50:] | join('\n') }}"

View File

@@ -22,8 +22,8 @@
- name: Derive docker registry credentials from vault when not provided
set_fact:
docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(docker_registry_username_default | default('admin'))) }}"
docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(docker_registry_password_default | default('registry-secure-password-2025'))) }}"
docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(docker_registry_username_default)) }}"
docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(docker_registry_password_default)) }}"
- name: Verify Docker is running
systemd:

View File

@@ -0,0 +1,81 @@
---
- name: Fix Gitea Actions Configuration (non-destructive)
hosts: production
become: no
gather_facts: yes
tasks:
- name: Check current Gitea Actions configuration
shell: |
docker exec gitea cat /data/gitea/conf/app.ini 2>/dev/null | grep -A 5 "\[actions\]" || echo "No actions section found"
register: current_config
changed_when: false
ignore_errors: yes
- name: Backup existing app.ini
shell: |
docker exec gitea cp /data/gitea/conf/app.ini /data/gitea/conf/app.ini.backup.$(date +%Y%m%d_%H%M%S)
changed_when: false
ignore_errors: yes
- name: Copy app.ini from container for editing
shell: |
docker cp gitea:/data/gitea/conf/app.ini /tmp/gitea_app_ini_$$
register: copy_result
- name: Update app.ini Actions section
shell: |
# Remove DEFAULT_ACTIONS_URL line if it exists in [actions] section
sed -i '/^\[actions\]/,/^\[/{ /^DEFAULT_ACTIONS_URL/d; }' /tmp/gitea_app_ini_$$
# Ensure ENABLED = true in [actions] section
if grep -q "^\[actions\]" /tmp/gitea_app_ini_$$; then
# Section exists - ensure ENABLED = true
sed -i '/^\[actions\]/,/^\[/{ s/^ENABLED.*/ENABLED = true/; }' /tmp/gitea_app_ini_$$
# If ENABLED line doesn't exist, add it
if ! grep -A 10 "^\[actions\]" /tmp/gitea_app_ini_$$ | grep -q "^ENABLED"; then
sed -i '/^\[actions\]/a ENABLED = true' /tmp/gitea_app_ini_$$
fi
else
# Section doesn't exist - add it
echo "" >> /tmp/gitea_app_ini_$$
echo "[actions]" >> /tmp/gitea_app_ini_$$
echo "ENABLED = true" >> /tmp/gitea_app_ini_$$
fi
args:
executable: /bin/bash
register: config_updated
- name: Copy updated app.ini back to container
shell: |
docker cp /tmp/gitea_app_ini_$$ gitea:/data/gitea/conf/app.ini
rm -f /tmp/gitea_app_ini_$$
when: config_updated.changed | default(false)
- name: Verify Actions configuration after update
shell: |
docker exec gitea cat /data/gitea/conf/app.ini | grep -A 5 "\[actions\]"
register: updated_config
changed_when: false
- name: Restart Gitea to apply configuration
shell: |
cd {{ stacks_base_path }}/gitea
docker compose restart gitea
when: config_updated.changed | default(false)
- name: Wait for Gitea to be ready
wait_for:
timeout: 60
when: config_updated.changed | default(false)
- name: Display configuration result
debug:
msg:
- "=== Gitea Actions Configuration Fixed ==="
- ""
- "Current [actions] configuration:"
- "{{ updated_config.stdout }}"
- ""
- "Configuration updated: {{ 'Yes' if config_updated.changed else 'No changes needed' }}"
- "Gitea restarted: {{ 'Yes' if config_updated.changed else 'No' }}"

View File

@@ -0,0 +1,49 @@
---
- name: Remove DEFAULT_ACTIONS_URL from Gitea configuration
hosts: production
become: no
gather_facts: yes
tasks:
- name: Check if DEFAULT_ACTIONS_URL exists in app.ini
shell: |
docker exec gitea cat /data/gitea/conf/app.ini 2>/dev/null | grep -q "DEFAULT_ACTIONS_URL" && echo "exists" || echo "not_found"
register: url_check
changed_when: false
ignore_errors: yes
- name: Remove DEFAULT_ACTIONS_URL from app.ini
shell: |
docker exec gitea sh -c 'sed -i "/^DEFAULT_ACTIONS_URL/d" /data/gitea/conf/app.ini'
when: url_check.stdout == "exists"
register: url_removed
- name: Restart Gitea to apply configuration changes
shell: |
cd {{ stacks_base_path }}/gitea
docker compose restart gitea
when: url_removed.changed | default(false)
- name: Wait for Gitea to be ready
wait_for:
timeout: 60
when: url_removed.changed | default(false)
- name: Verify Gitea Actions configuration
shell: |
docker exec gitea cat /data/gitea/conf/app.ini 2>/dev/null | grep -A 3 "\[actions\]" || echo "Config not accessible"
register: gitea_config
changed_when: false
ignore_errors: yes
- name: Display Gitea Actions configuration
debug:
msg:
- "=== Gitea Configuration Fix Complete ==="
- "DEFAULT_ACTIONS_URL removed: {{ 'Yes' if url_removed.changed else 'No (not found or already removed)' }}"
- "Container restarted: {{ 'Yes' if url_removed.changed else 'No' }}"
- ""
- "Current Actions configuration:"
- "{{ gitea_config.stdout if gitea_config.stdout else 'Could not read config' }}"
- ""
- "Gitea will now use its own instance for actions by default (no GitHub fallback)."

View File

@@ -0,0 +1,165 @@
---
- name: Remove framework-production Stack from Production Server
hosts: production
become: no
gather_facts: yes
vars:
stack_name: framework-production
stack_path: "~/framework-production"
tasks:
- name: Check if Docker is running
systemd:
name: docker
state: started
register: docker_service
become: yes
- name: Fail if Docker is not running
fail:
msg: "Docker service is not running"
when: docker_service.status.ActiveState != 'active'
- name: Check if framework-production stack directory exists
stat:
path: "{{ stack_path }}"
register: stack_dir
- name: Check if framework-production containers exist (all states)
shell: |
docker ps -a --filter "name={{ stack_name }}" --format "{{ '{{' }}.Names{{ '}}' }}"
args:
executable: /bin/bash
register: all_containers
changed_when: false
failed_when: false
- name: Display all containers found
debug:
msg: "Found containers: {{ all_containers.stdout_lines if all_containers.stdout_lines | length > 0 else 'None' }}"
- name: List all containers to find framework-production related ones
shell: |
docker ps -a --format "{{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Image{{ '}}' }}\t{{ '{{' }}.Status{{ '}}' }}"
args:
executable: /bin/bash
register: all_containers_list
changed_when: false
failed_when: false
- name: Display all containers
debug:
msg: "{{ all_containers_list.stdout_lines }}"
- name: Check for containers with framework-production in name or image
shell: |
docker ps -a --format "{{ '{{' }}.Names{{ '}}' }}" | grep -iE "(framework-production|^db$|^php$|^web$)" || echo ""
args:
executable: /bin/bash
register: matching_containers
changed_when: false
failed_when: false
- name: Check for containers with framework-production images
shell: |
docker ps -a --format "{{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Image{{ '}}' }}" | grep -i "framework-production" | cut -f1 || echo ""
args:
executable: /bin/bash
register: image_based_containers
changed_when: false
failed_when: false
- name: Display found containers
debug:
msg:
- "Containers by name pattern: {{ matching_containers.stdout_lines if matching_containers.stdout_lines | length > 0 else 'None' }}"
- "Containers by image: {{ image_based_containers.stdout_lines if image_based_containers.stdout_lines | length > 0 else 'None' }}"
- name: Stop and remove containers using docker-compose if stack directory exists
shell: |
cd {{ stack_path }}
docker-compose down -v
args:
executable: /bin/bash
when: stack_dir.stat.exists
register: compose_down_result
changed_when: true
ignore_errors: yes
- name: Stop and remove containers by name pattern and image
shell: |
REMOVED_CONTAINERS=""
# Method 1: Remove containers with framework-production in image name
while IFS=$'\t' read -r container image; do
if [[ "$image" == *"framework-production"* ]]; then
echo "Stopping and removing container '$container' (image: $image)"
docker stop "$container" 2>/dev/null || true
docker rm "$container" 2>/dev/null || true
REMOVED_CONTAINERS="$REMOVED_CONTAINERS $container"
fi
done < <(docker ps -a --format "{{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Image{{ '}}' }}")
# Method 2: Remove containers with specific names that match the pattern
for container_name in "db" "php" "web"; do
# Check if container exists and has framework-production image
container_info=$(docker ps -a --filter "name=^${container_name}$" --format "{{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Image{{ '}}' }}" 2>/dev/null || echo "")
if [[ -n "$container_info" ]]; then
image=$(echo "$container_info" | cut -f2)
if [[ "$image" == *"framework-production"* ]] || [[ "$image" == *"mariadb"* ]]; then
echo "Stopping and removing container '$container_name' (image: $image)"
docker stop "$container_name" 2>/dev/null || true
docker rm "$container_name" 2>/dev/null || true
REMOVED_CONTAINERS="$REMOVED_CONTAINERS $container_name"
fi
fi
done
# Method 3: Remove containers with framework-production in name
docker ps -a --format "{{ '{{' }}.Names{{ '}}' }}" | grep -i framework-production | while read container; do
if [ ! -z "$container" ]; then
echo "Stopping and removing container '$container'"
docker stop "$container" 2>/dev/null || true
docker rm "$container" 2>/dev/null || true
REMOVED_CONTAINERS="$REMOVED_CONTAINERS $container"
fi
done
# Output removed containers
if [[ -n "$REMOVED_CONTAINERS" ]]; then
echo "Removed containers:$REMOVED_CONTAINERS"
else
echo "No containers were removed"
fi
args:
executable: /bin/bash
register: direct_remove_result
changed_when: "'Removed containers' in direct_remove_result.stdout"
failed_when: false
- name: Remove stack directory if it exists
file:
path: "{{ stack_path }}"
state: absent
when: stack_dir.stat.exists
register: dir_removed
- name: Verify all framework-production containers are removed
shell: |
docker ps -a --format "{{ '{{' }}.Names{{ '}}' }}" | grep -i framework-production || echo ""
args:
executable: /bin/bash
register: remaining_containers
changed_when: false
failed_when: false
- name: Display removal status
debug:
msg:
- "=== framework-production Stack Removal Complete ==="
- "Stack directory removed: {{ 'Yes' if dir_removed.changed else 'No (did not exist)' }}"
- "Containers removed: {{ 'Yes' if (compose_down_result.changed or direct_remove_result.changed) else 'No (none found)' }}"
- "Remaining containers: {{ remaining_containers.stdout if remaining_containers.stdout else 'None' }}"
- ""
- "Stack '{{ stack_name }}' has been successfully removed."

View File

@@ -6,7 +6,7 @@
vars:
rollback_to_version: "{{ rollback_to_version | default('previous') }}"
app_stack_path: "{{ deploy_user_home }}/deployment/stacks/application"
# app_stack_path is now defined in group_vars/production.yml
pre_tasks:
- name: Optionally load registry credentials from encrypted vault
@@ -19,8 +19,8 @@
- name: Derive docker registry credentials from vault when not provided
set_fact:
docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(docker_registry_username_default | default('admin'))) }}"
docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(docker_registry_password_default | default('registry-secure-password-2025'))) }}"
docker_registry_username: "{{ docker_registry_username | default(vault_docker_registry_username | default(docker_registry_username_default)) }}"
docker_registry_password: "{{ docker_registry_password | default(vault_docker_registry_password | default(docker_registry_password_default)) }}"
- name: Check Docker service
systemd:

View File

@@ -11,7 +11,7 @@
vars:
project_root: "{{ lookup('env', 'PWD') | default(playbook_dir + '/../..', true) }}"
ci_image_name: "php-ci:latest"
ci_image_registry: "{{ ci_registry | default('registry.michaelschiemer.de') }}"
ci_image_registry: "{{ ci_registry | default(docker_registry_external) }}"
ci_image_registry_path: "{{ ci_registry }}/ci/php-ci:latest"
gitea_runner_dir: "{{ project_root }}/deployment/gitea-runner"
docker_dind_container: "gitea-runner-dind"

View File

@@ -5,10 +5,17 @@
gather_facts: yes
vars:
stacks_base_path: "~/deployment/stacks"
wait_timeout: 60
# All deployment variables are now defined in group_vars/production.yml
# Variables can be overridden via -e flag if needed
tasks:
- name: Debug - Show variables
debug:
msg:
- "stacks_base_path: {{ stacks_base_path | default('NOT SET') }}"
- "deploy_user_home: {{ deploy_user_home | default('NOT SET') }}"
when: false # Only enable for debugging
- name: Check if deployment stacks directory exists
stat:
path: "{{ stacks_base_path }}"
@@ -83,22 +90,42 @@
# 3. Deploy Docker Registry (Private Registry)
- name: Ensure Registry auth directory exists
file:
path: "{{ stacks_base_path }}/registry/auth"
path: "{{ registry_auth_path }}"
state: directory
mode: '0755'
become: yes
- name: Optionally load registry credentials from vault
include_vars:
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
no_log: yes
ignore_errors: yes
delegate_to: localhost
become: no
- name: Set registry credentials from vault or defaults
set_fact:
registry_username: "{{ vault_docker_registry_username | default(docker_registry_username_default) }}"
registry_password: "{{ vault_docker_registry_password | default(docker_registry_password_default) }}"
no_log: true
- name: Fail if registry password is not set
fail:
msg: "Registry password must be set in vault or docker_registry_password_default"
when: registry_password is not defined or registry_password == ""
- name: Create Registry htpasswd file if missing
shell: |
if [ ! -f {{ stacks_base_path }}/registry/auth/htpasswd ]; then
docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin registry-secure-password-2025 > {{ stacks_base_path }}/registry/auth/htpasswd
chmod 644 {{ stacks_base_path }}/registry/auth/htpasswd
if [ ! -f {{ registry_auth_path }}/htpasswd ]; then
docker run --rm --entrypoint htpasswd httpd:2 -Bbn {{ registry_username }} {{ registry_password }} > {{ registry_auth_path }}/htpasswd
chmod 644 {{ registry_auth_path }}/htpasswd
fi
args:
executable: /bin/bash
become: yes
changed_when: true
register: registry_auth_created
no_log: true
- name: Deploy Docker Registry stack
community.docker.docker_compose_v2:
@@ -126,19 +153,95 @@
- name: Verify Registry is accessible
uri:
url: "http://127.0.0.1:5000/v2/_catalog"
user: admin
password: registry-secure-password-2025
user: "{{ registry_username }}"
password: "{{ registry_password }}"
status_code: 200
timeout: 5
register: registry_check
ignore_errors: yes
changed_when: false
no_log: true
- name: Display Registry status
debug:
msg: "Registry accessibility: {{ 'SUCCESS' if registry_check.status == 200 else 'FAILED - may need manual check' }}"
# 4. Deploy Gitea (CRITICAL - Git Server + MySQL + Redis)
# 4. Deploy MinIO (Object Storage)
- name: Optionally load MinIO secrets from vault
include_vars:
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
no_log: yes
ignore_errors: yes
delegate_to: localhost
become: no
- name: Set MinIO root password from vault or generate
set_fact:
minio_password: "{{ vault_minio_root_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation')) }}"
no_log: yes
- name: Set MinIO root user from vault or use default
set_fact:
minio_user: "{{ vault_minio_root_user | default('minioadmin') }}"
- name: Ensure MinIO stack directory exists
file:
path: "{{ stacks_base_path }}/minio"
state: directory
mode: '0755'
- name: Create MinIO stack .env file
template:
src: "{{ playbook_dir }}/../templates/minio.env.j2"
dest: "{{ stacks_base_path }}/minio/.env"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0600'
vars:
minio_root_user: "{{ minio_user }}"
minio_root_password: "{{ minio_password }}"
minio_api_domain: "{{ minio_api_domain }}"
minio_console_domain: "{{ minio_console_domain }}"
no_log: yes
- name: Deploy MinIO stack
community.docker.docker_compose_v2:
project_src: "{{ stacks_base_path }}/minio"
state: present
pull: always
register: minio_output
- name: Wait for MinIO to be ready
wait_for:
timeout: "{{ wait_timeout }}"
when: minio_output.changed
- name: Check MinIO logs for readiness
shell: docker compose logs minio 2>&1 | grep -Ei "(API:|WebUI:|MinIO Object Storage Server)" || true
args:
chdir: "{{ stacks_base_path }}/minio"
register: minio_logs
until: minio_logs.stdout != ""
retries: 6
delay: 10
changed_when: false
ignore_errors: yes
- name: Verify MinIO health endpoint
uri:
url: "http://127.0.0.1:9000/minio/health/live"
method: GET
status_code: [200, 404, 502, 503]
timeout: 5
register: minio_health_check
ignore_errors: yes
changed_when: false
- name: Display MinIO status
debug:
msg: "MinIO health check: {{ 'SUCCESS' if minio_health_check.status == 200 else 'FAILED - Status: ' + (minio_health_check.status|string) }}"
# 5. Deploy Gitea (CRITICAL - Git Server + MySQL + Redis)
- name: Deploy Gitea stack
community.docker.docker_compose_v2:
project_src: "{{ stacks_base_path }}/gitea"
@@ -162,7 +265,7 @@
changed_when: false
ignore_errors: yes
# 5. Deploy Monitoring (Portainer + Grafana + Prometheus)
# 6. Deploy Monitoring (Portainer + Grafana + Prometheus)
- name: Optionally load monitoring secrets from vault
include_vars:
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
@@ -229,7 +332,7 @@
- name: Verify Gitea accessibility via HTTPS
uri:
url: https://git.michaelschiemer.de
url: "https://{{ gitea_domain }}"
method: GET
validate_certs: no
status_code: 200
@@ -241,7 +344,7 @@
debug:
msg: "Gitea HTTPS check: {{ 'SUCCESS' if gitea_http_check.status == 200 else 'FAILED - Status: ' + (gitea_http_check.status|string) }}"
# 6. Deploy Application Stack
# 7. Deploy Application Stack
- name: Optionally load application secrets from vault
include_vars:
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
@@ -320,10 +423,10 @@
mode: '0600'
vars:
db_password: "{{ app_db_password }}"
db_user: "{{ db_user | default('postgres') }}"
db_name: "{{ db_name | default('michaelschiemer') }}"
db_user: "{{ db_user | default(db_user_default) }}"
db_name: "{{ db_name | default(db_name_default) }}"
redis_password: "{{ app_redis_password }}"
app_domain: "{{ app_domain | default('michaelschiemer.de') }}"
app_domain: "{{ app_domain }}"
no_log: yes
- name: Deploy Application stack
@@ -391,7 +494,7 @@
- name: Verify application accessibility via HTTPS
uri:
url: "https://{{ app_domain | default('michaelschiemer.de') }}/health"
url: "{{ health_check_url }}"
method: GET
validate_certs: no
status_code: [200, 404, 502, 503]
@@ -412,13 +515,14 @@
- "Traefik: {{ 'Deployed' if traefik_output.changed else 'Already running' }}"
- "PostgreSQL: {{ 'Deployed' if postgres_output.changed else 'Already running' }}"
- "Docker Registry: {{ 'Deployed' if registry_output.changed else 'Already running' }}"
- "MinIO: {{ 'Deployed' if minio_output.changed else 'Already running' }}"
- "Gitea: {{ 'Deployed' if gitea_output.changed else 'Already running' }}"
- "Monitoring: {{ 'Deployed' if monitoring_output.changed else 'Already running' }}"
- "Application: {{ 'Deployed' if application_output.changed else 'Already running' }}"
- ""
- "Next Steps:"
- "1. Access Gitea at: https://git.michaelschiemer.de"
- "1. Access Gitea at: https://{{ gitea_domain }}"
- "2. Complete Gitea setup wizard if first-time deployment"
- "3. Navigate to Admin > Actions > Runners to get registration token"
- "4. Continue with Phase 1 - Gitea Runner Setup"
- "5. Access Application at: https://{{ app_domain | default('michaelschiemer.de') }}"
- "5. Access Application at: https://{{ app_domain }}"

View File

@@ -5,10 +5,9 @@
gather_facts: yes
vars:
domains:
- git.michaelschiemer.de
- michaelschiemer.de
acme_email: kontakt@michaelschiemer.de
# ssl_domains and acme_email are defined in group_vars/production.yml
# Can be overridden via -e flag if needed
domains: "{{ ssl_domains | default([gitea_domain, app_domain]) }}"
tasks:
- name: Check if acme.json exists and is a file
@@ -70,7 +69,7 @@
- name: Check if acme.json contains certificates
stat:
path: "{{ deploy_user_home }}/deployment/stacks/traefik/acme.json"
path: "{{ stacks_base_path }}/traefik/acme.json"
register: acme_file
- name: Display certificate status
@@ -79,8 +78,9 @@
Certificate setup triggered.
Traefik will request Let's Encrypt certificates for:
{{ domains | join(', ') }}
ACME Email: {{ acme_email }}
Check Traefik logs to see certificate generation progress:
docker compose -f {{ deploy_user_home }}/deployment/stacks/traefik/docker-compose.yml logs traefik | grep -i acme
docker compose -f {{ stacks_base_path }}/traefik/docker-compose.yml logs traefik | grep -i acme
Certificates should be ready within 1-2 minutes.

View File

@@ -5,20 +5,13 @@
gather_facts: yes
vars:
wireguard_interface: "wg0"
wireguard_config_path: "/etc/wireguard"
wireguard_config_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}.conf"
wireguard_private_key_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}_private.key"
wireguard_public_key_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}_public.key"
wireguard_client_configs_path: "{{ wireguard_config_path }}/clients"
wireguard_enable_ip_forwarding: true
# WireGuard variables are defined in group_vars/production.yml
# Can be overridden via -e flag if needed
wireguard_port: "{{ wireguard_port | default(wireguard_port_default) }}"
wireguard_network: "{{ wireguard_network | default(wireguard_network_default) }}"
wireguard_server_ip: "{{ wireguard_server_ip | default(wireguard_server_ip_default) }}"
pre_tasks:
- name: Set WireGuard variables with defaults
set_fact:
wireguard_port: "{{ wireguard_port | default(51820) }}"
wireguard_network: "{{ wireguard_network | default('10.8.0.0/24') }}"
wireguard_server_ip: "{{ wireguard_server_ip | default('10.8.0.1') }}"
- name: Optionally load wireguard secrets from vault
include_vars:

View File

@@ -0,0 +1,102 @@
---
- name: Sync Code from Git Repository to Application Container
hosts: production
gather_facts: yes
become: no
vars:
# git_repository_url and git_branch are defined in group_vars/production.yml
# Can be overridden via -e flag if needed
git_repository_url: "{{ git_repo_url | default(git_repository_url_default) }}"
git_branch: "{{ git_branch | default(git_branch_default) }}"
pre_tasks:
- name: Optionally load secrets from vault
include_vars:
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
no_log: yes
ignore_errors: yes
delegate_to: localhost
become: no
tasks:
- name: Verify application stack directory exists
stat:
path: "{{ app_stack_path }}"
register: app_stack_dir
- name: Fail if application stack directory doesn't exist
fail:
msg: "Application stack directory not found at {{ app_stack_path }}"
when: not app_stack_dir.stat.exists
- name: Check if docker-compose.yml exists
stat:
path: "{{ app_stack_path }}/docker-compose.yml"
register: compose_file_exists
- name: Fail if docker-compose.yml doesn't exist
fail:
msg: "docker-compose.yml not found. Run setup-infrastructure.yml first."
when: not compose_file_exists.stat.exists
- name: Read current .env file
slurp:
src: "{{ app_stack_path }}/.env"
register: env_file_content
failed_when: false
changed_when: false
- name: Check if Git configuration exists in .env
set_fact:
has_git_config: "{{ env_file_content.content | b64decode | regex_search('GIT_REPOSITORY_URL=') is not none }}"
when: env_file_content.content is defined
- name: Update .env with Git configuration
lineinfile:
path: "{{ app_stack_path }}/.env"
regexp: "{{ item.regex }}"
line: "{{ item.line }}"
state: present
loop:
- { regex: '^GIT_REPOSITORY_URL=', line: 'GIT_REPOSITORY_URL={{ git_repository_url }}' }
- { regex: '^GIT_BRANCH=', line: 'GIT_BRANCH={{ git_branch }}' }
- { regex: '^GIT_TOKEN=', line: 'GIT_TOKEN={{ git_token | default("") }}' }
- { regex: '^GIT_USERNAME=', line: 'GIT_USERNAME={{ git_username | default("") }}' }
- { regex: '^GIT_PASSWORD=', line: 'GIT_PASSWORD={{ git_password | default("") }}' }
when: not has_git_config | default(true)
- name: Restart application container to trigger Git pull
shell: |
cd {{ app_stack_path }}
docker compose restart app
args:
executable: /bin/bash
register: container_restart
- name: Wait for container to be ready
wait_for:
timeout: 60
when: container_restart.changed
- name: Check container logs for Git operations
shell: |
cd {{ app_stack_path }}
docker compose logs app --tail 50 | grep -E "(Git|Clone|Pull|✅|❌)" || echo "No Git-related logs found"
args:
executable: /bin/bash
register: git_logs
changed_when: false
- name: Display Git sync result
debug:
msg:
- "=== Code Sync Summary ==="
- "Repository: {{ git_repository_url }}"
- "Branch: {{ git_branch }}"
- "Container restarted: {{ 'Yes' if container_restart.changed else 'No' }}"
- ""
- "Git Logs:"
- "{{ git_logs.stdout }}"
- ""
- "Next: Check application logs to verify code was synced"

View File

@@ -0,0 +1,62 @@
---
# Check Container Health Status
- name: Check nginx container logs
shell: |
docker logs nginx --tail 50 2>&1
args:
executable: /bin/bash
register: nginx_logs
failed_when: false
- name: Display nginx logs
debug:
msg: "{{ nginx_logs.stdout_lines }}"
- name: Test nginx health check manually
shell: |
docker exec nginx wget --spider -q http://localhost/health 2>&1 || echo "Health check failed"
args:
executable: /bin/bash
register: nginx_health_test
failed_when: false
- name: Display nginx health check result
debug:
msg: "{{ nginx_health_test.stdout }}"
- name: Check queue-worker container logs
shell: |
docker logs queue-worker --tail 50 2>&1
args:
executable: /bin/bash
register: queue_worker_logs
failed_when: false
- name: Display queue-worker logs
debug:
msg: "{{ queue_worker_logs.stdout_lines }}"
- name: Check scheduler container logs
shell: |
docker logs scheduler --tail 50 2>&1
args:
executable: /bin/bash
register: scheduler_logs
failed_when: false
- name: Display scheduler logs
debug:
msg: "{{ scheduler_logs.stdout_lines }}"
- name: Check container status
shell: |
cd {{ app_stack_path }}
docker compose ps
args:
executable: /bin/bash
register: container_status
failed_when: false
- name: Display container status
debug:
msg: "{{ container_status.stdout_lines }}"

View File

@@ -0,0 +1,73 @@
---
# Diagnose 404 Errors
- name: Check nginx logs for errors
shell: |
docker logs nginx --tail 100 2>&1
args:
executable: /bin/bash
register: nginx_logs
failed_when: false
- name: Display nginx logs
debug:
msg: "{{ nginx_logs.stdout_lines }}"
- name: Check app container logs
shell: |
docker logs app --tail 100 2>&1
args:
executable: /bin/bash
register: app_logs
failed_when: false
- name: Display app container logs
debug:
msg: "{{ app_logs.stdout_lines }}"
- name: Test nginx health endpoint directly
shell: |
docker exec nginx wget -q -O - http://127.0.0.1/health 2>&1 || echo "Health check failed"
args:
executable: /bin/bash
register: nginx_health_test
failed_when: false
- name: Display nginx health check result
debug:
msg: "{{ nginx_health_test.stdout }}"
- name: Check nginx configuration
shell: |
docker exec nginx cat /etc/nginx/conf.d/default.conf 2>&1
args:
executable: /bin/bash
register: nginx_config
failed_when: false
- name: Display nginx configuration
debug:
msg: "{{ nginx_config.stdout_lines }}"
- name: Check if app container has files in /var/www/html
shell: |
docker exec app ls -la /var/www/html/ 2>&1 | head -20
args:
executable: /bin/bash
register: app_files
failed_when: false
- name: Display app container files
debug:
msg: "{{ app_files.stdout_lines }}"
- name: Check container network connectivity
shell: |
docker exec nginx ping -c 1 app 2>&1 | head -5
args:
executable: /bin/bash
register: network_check
failed_when: false
- name: Display network connectivity
debug:
msg: "{{ network_check.stdout }}"

View File

@@ -0,0 +1,71 @@
---
# Fix Container Health Checks
- name: Check if application stack directory exists
stat:
path: "{{ app_stack_path }}"
register: app_stack_dir
- name: Fail if application stack directory doesn't exist
fail:
msg: "Application stack directory not found at {{ app_stack_path }}"
when: not app_stack_dir.stat.exists
- name: Copy updated docker-compose.yml to production
copy:
src: "{{ playbook_dir }}/../../stacks/application/docker-compose.yml"
dest: "{{ app_stack_path }}/docker-compose.yml"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
register: compose_updated
- name: Recreate containers with new health checks
shell: |
cd {{ app_stack_path }}
docker compose up -d --force-recreate nginx queue-worker scheduler
args:
executable: /bin/bash
when: compose_updated.changed
register: containers_recreated
- name: Wait for containers to be healthy
shell: |
cd {{ app_stack_path }}
timeout=120
elapsed=0
while [ $elapsed -lt $timeout ]; do
healthy=$(docker compose ps --format json | jq -r '[.[] | select(.Name=="nginx" or .Name=="queue-worker" or .Name=="scheduler") | .Health] | all(.=="healthy" or .=="")')
if [ "$healthy" = "true" ]; then
echo "All containers are healthy"
exit 0
fi
sleep 5
elapsed=$((elapsed + 5))
done
echo "Timeout waiting for containers to become healthy"
docker compose ps
exit 1
args:
executable: /bin/bash
register: health_wait
failed_when: false
changed_when: false
- name: Check final container status
shell: |
cd {{ app_stack_path }}
docker compose ps
args:
executable: /bin/bash
register: final_status
- name: Display final container status
debug:
msg: "{{ final_status.stdout_lines }}"
- name: Display summary
debug:
msg:
- "=== Health Check Fix Complete ==="
- "Containers recreated: {{ 'Yes' if containers_recreated.changed else 'No (no changes)' }}"
- "Health wait result: {{ 'SUCCESS' if health_wait.rc == 0 else 'TIMEOUT or ERROR - check logs' }}"

View File

@@ -0,0 +1,71 @@
---
# Fix Nginx 404 by setting up shared app-code volume
- name: Check if application stack directory exists
stat:
path: "{{ app_stack_path }}"
register: app_stack_dir
- name: Fail if application stack directory doesn't exist
fail:
msg: "Application stack directory not found at {{ app_stack_path }}"
when: not app_stack_dir.stat.exists
- name: Copy updated docker-compose.yml to production
copy:
src: "{{ playbook_dir }}/../../stacks/application/docker-compose.yml"
dest: "{{ app_stack_path }}/docker-compose.yml"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
register: compose_updated
- name: Initialize app-code volume with files from app image
shell: |
# Stop containers first
cd {{ app_stack_path }}
docker compose down nginx || true
# Create and initialize app-code volume
docker volume create app-code || true
# Copy files from app image to volume using temporary container
docker run --rm \
-v app-code:/target \
{{ app_image_external }}:latest \
sh -c "cp -r /var/www/html/* /target/ 2>/dev/null || true"
args:
executable: /bin/bash
register: volume_init
changed_when: true
failed_when: false
- name: Start containers
shell: |
cd {{ app_stack_path }}
docker compose up -d
args:
executable: /bin/bash
register: containers_started
- name: Wait for containers to be healthy
pause:
seconds: 15
- name: Check container status
shell: |
cd {{ app_stack_path }}
docker compose ps
args:
executable: /bin/bash
register: final_status
- name: Display container status
debug:
msg: "{{ final_status.stdout_lines }}"
- name: Display summary
debug:
msg:
- "=== Nginx 404 Fix Complete ==="
- "Volume initialized: {{ 'Yes' if volume_init.changed else 'No' }}"
- "Containers restarted: {{ 'Yes' if containers_started.changed else 'No' }}"

View File

@@ -0,0 +1,47 @@
---
- name: Application Troubleshooting
hosts: production
gather_facts: yes
become: no
# All variables are now defined in group_vars/production.yml
tasks:
- name: Check container health
include_tasks: tasks/check-health.yml
tags: ['health', 'check', 'all']
- name: Diagnose 404 errors
include_tasks: tasks/diagnose-404.yml
tags: ['404', 'diagnose', 'all']
- name: Fix container health checks
include_tasks: tasks/fix-health-checks.yml
tags: ['health', 'fix', 'all']
- name: Fix nginx 404
include_tasks: tasks/fix-nginx-404.yml
tags: ['nginx', '404', 'fix', 'all']
- name: Display usage information
debug:
msg:
- "=== Troubleshooting Playbook ==="
- ""
- "Usage examples:"
- " # Check health only:"
- " ansible-playbook troubleshoot.yml --tags health,check"
- ""
- " # Diagnose 404 only:"
- " ansible-playbook troubleshoot.yml --tags 404,diagnose"
- ""
- " # Fix health checks:"
- " ansible-playbook troubleshoot.yml --tags health,fix"
- ""
- " # Fix nginx 404:"
- " ansible-playbook troubleshoot.yml --tags nginx,404,fix"
- ""
- " # Run all checks:"
- " ansible-playbook troubleshoot.yml --tags all"
when: true
tags: ['never']

View File

@@ -0,0 +1,49 @@
---
- name: Update Gitea Configuration and Restart
hosts: production
become: no
gather_facts: yes
vars:
gitea_stack_path: "{{ stacks_base_path }}/gitea"
tasks:
- name: Copy updated docker-compose.yml to production server
copy:
src: "{{ playbook_dir }}/../../stacks/gitea/docker-compose.yml"
dest: "{{ gitea_stack_path }}/docker-compose.yml"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
- name: Restart Gitea stack with updated configuration
community.docker.docker_compose_v2:
project_src: "{{ gitea_stack_path }}"
state: present
pull: never
recreate: always
remove_orphans: no
register: gitea_restart
- name: Wait for Gitea to be ready
wait_for:
timeout: 60
when: gitea_restart.changed
- name: Verify Gitea Actions configuration
shell: |
docker exec gitea cat /data/gitea/conf/app.ini 2>/dev/null | grep -A 3 "\[actions\]" || echo "Config not accessible"
register: gitea_config
changed_when: false
ignore_errors: yes
- name: Display Gitea Actions configuration
debug:
msg:
- "=== Gitea Configuration Update Complete ==="
- "Container restarted: {{ 'Yes' if gitea_restart.changed else 'No' }}"
- ""
- "Current Actions configuration:"
- "{{ gitea_config.stdout if gitea_config.stdout else 'Could not read config (container may still be starting)' }}"
- ""
- "The DEFAULT_ACTIONS_URL should now point to your Gitea instance instead of GitHub."