- Fix Enter key detection: handle multiple Enter key formats (\n, \r, \r\n) - Reduce flickering: lower render frequency from 60 FPS to 30 FPS - Fix menu bar visibility: re-render menu bar after content to prevent overwriting - Fix content positioning: explicit line positioning for categories and commands - Fix line shifting: clear lines before writing, control newlines manually - Limit visible items: prevent overflow with maxVisibleCategories/Commands - Improve CPU usage: increase sleep interval when no events processed This fixes: - Enter key not working for selection - Strong flickering of the application - Menu bar not visible or being overwritten - Top half of selection list not displayed - Lines being shifted/misaligned
322 lines
12 KiB
YAML
322 lines
12 KiB
YAML
---
|
||
# Clean Redeploy Traefik and Gitea Stacks
|
||
# Complete redeployment with backup, container recreation, and verification
|
||
#
|
||
# Usage:
|
||
# # With automatic backup
|
||
# ansible-playbook -i inventory/production.yml playbooks/setup/redeploy-traefik-gitea-clean.yml \
|
||
# --vault-password-file secrets/.vault_pass
|
||
#
|
||
# # With existing backup
|
||
# ansible-playbook -i inventory/production.yml playbooks/setup/redeploy-traefik-gitea-clean.yml \
|
||
# --vault-password-file secrets/.vault_pass \
|
||
# -e "backup_name=redeploy-backup-1234567890" \
|
||
# -e "skip_backup=true"
|
||
|
||
- name: Clean Redeploy Traefik and Gitea
|
||
hosts: production
|
||
gather_facts: yes
|
||
become: no
|
||
vars:
|
||
traefik_stack_path: "{{ stacks_base_path }}/traefik"
|
||
gitea_stack_path: "{{ stacks_base_path }}/gitea"
|
||
gitea_url: "https://{{ gitea_domain }}"
|
||
traefik_container_name: "traefik"
|
||
gitea_container_name: "gitea"
|
||
backup_base_path: "{{ backups_path | default('/home/deploy/backups') }}"
|
||
skip_backup: "{{ skip_backup | default(false) | bool }}"
|
||
backup_name: "{{ backup_name | default('') }}"
|
||
|
||
tasks:
|
||
# ========================================
|
||
# 1. BACKUP (unless skipped)
|
||
# ========================================
|
||
- name: Set backup name fact
|
||
ansible.builtin.set_fact:
|
||
actual_backup_name: "{{ backup_name | default('redeploy-backup-' + ansible_date_time.epoch) }}"
|
||
when: not skip_backup
|
||
|
||
- name: Display backup note
|
||
ansible.builtin.debug:
|
||
msg: |
|
||
⚠️ NOTE: Backup should be run separately before redeploy:
|
||
ansible-playbook -i inventory/production.yml playbooks/maintenance/backup-before-redeploy.yml \
|
||
--vault-password-file secrets/.vault_pass \
|
||
-e "backup_name={{ actual_backup_name }}"
|
||
|
||
Or use existing backup with: -e "backup_name=redeploy-backup-XXXXX" -e "skip_backup=true"
|
||
when: not skip_backup
|
||
|
||
- name: Display redeployment plan
|
||
ansible.builtin.debug:
|
||
msg: |
|
||
================================================================================
|
||
CLEAN REDEPLOY TRAEFIK AND GITEA
|
||
================================================================================
|
||
|
||
This playbook will:
|
||
1. ✅ Backup ({% if skip_backup %}SKIPPED{% else %}Performed{% endif %})
|
||
2. ✅ Stop and remove Traefik containers (keeps acme.json)
|
||
3. ✅ Stop and remove Gitea containers (keeps volumes/data)
|
||
4. ✅ Sync latest stack configurations
|
||
5. ✅ Redeploy Traefik stack
|
||
6. ✅ Redeploy Gitea stack
|
||
7. ✅ Restore Gitea configuration (app.ini)
|
||
8. ✅ Verify service discovery
|
||
9. ✅ Test Gitea accessibility
|
||
|
||
⚠️ IMPORTANT:
|
||
- SSL certificates (acme.json) will be preserved
|
||
- Gitea data (volumes) will be preserved
|
||
- Only containers will be recreated
|
||
- Expected downtime: ~2-5 minutes
|
||
{% if not skip_backup %}
|
||
- Backup location: {{ backup_base_path }}/{{ actual_backup_name }}
|
||
{% endif %}
|
||
|
||
================================================================================
|
||
|
||
# ========================================
|
||
# 2. STOP AND REMOVE CONTAINERS
|
||
# ========================================
|
||
- name: Stop Traefik stack
|
||
ansible.builtin.shell: |
|
||
cd {{ traefik_stack_path }}
|
||
docker compose down
|
||
register: traefik_stop
|
||
changed_when: traefik_stop.rc == 0
|
||
failed_when: false
|
||
|
||
- name: Remove Traefik containers (if any remain)
|
||
ansible.builtin.shell: |
|
||
docker ps -a --filter "name={{ traefik_container_name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true
|
||
register: traefik_remove
|
||
changed_when: traefik_remove.rc == 0
|
||
failed_when: false
|
||
|
||
- name: Stop Gitea stack (preserves volumes)
|
||
ansible.builtin.shell: |
|
||
cd {{ gitea_stack_path }}
|
||
docker compose down
|
||
register: gitea_stop
|
||
changed_when: gitea_stop.rc == 0
|
||
failed_when: false
|
||
|
||
- name: Remove Gitea containers (if any remain, volumes are preserved)
|
||
ansible.builtin.shell: |
|
||
docker ps -a --filter "name={{ gitea_container_name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true
|
||
register: gitea_remove
|
||
changed_when: gitea_remove.rc == 0
|
||
failed_when: false
|
||
|
||
# ========================================
|
||
# 3. SYNC CONFIGURATIONS
|
||
# ========================================
|
||
- name: Get stacks directory path
|
||
ansible.builtin.set_fact:
|
||
stacks_source_path: "{{ playbook_dir | dirname | dirname | dirname }}/stacks"
|
||
delegate_to: localhost
|
||
run_once: true
|
||
|
||
- name: Sync stacks directory to production server
|
||
ansible.builtin.synchronize:
|
||
src: "{{ stacks_source_path }}/"
|
||
dest: "{{ stacks_base_path }}/"
|
||
delete: no
|
||
recursive: yes
|
||
rsync_opts:
|
||
- "--chmod=D755,F644"
|
||
- "--exclude=.git"
|
||
- "--exclude=*.log"
|
||
- "--exclude=data/"
|
||
- "--exclude=volumes/"
|
||
- "--exclude=acme.json" # Preserve SSL certificates
|
||
- "--exclude=*.key"
|
||
- "--exclude=*.pem"
|
||
|
||
# ========================================
|
||
# 4. ENSURE ACME.JSON EXISTS
|
||
# ========================================
|
||
- name: Check if acme.json exists
|
||
ansible.builtin.stat:
|
||
path: "{{ traefik_stack_path }}/acme.json"
|
||
register: acme_json_stat
|
||
|
||
- name: Ensure acme.json exists and has correct permissions
|
||
ansible.builtin.file:
|
||
path: "{{ traefik_stack_path }}/acme.json"
|
||
state: touch
|
||
mode: '0600'
|
||
owner: "{{ ansible_user }}"
|
||
group: "{{ ansible_user }}"
|
||
become: yes
|
||
register: acme_json_ensure
|
||
|
||
# ========================================
|
||
# 5. REDEPLOY TRAEFIK
|
||
# ========================================
|
||
- name: Deploy Traefik stack
|
||
community.docker.docker_compose_v2:
|
||
project_src: "{{ traefik_stack_path }}"
|
||
state: present
|
||
pull: always
|
||
register: traefik_deploy
|
||
|
||
- name: Wait for Traefik to be ready
|
||
ansible.builtin.shell: |
|
||
cd {{ traefik_stack_path }}
|
||
docker compose ps {{ traefik_container_name }} | grep -Eiq "Up|running"
|
||
register: traefik_ready
|
||
changed_when: false
|
||
until: traefik_ready.rc == 0
|
||
retries: 12
|
||
delay: 5
|
||
failed_when: traefik_ready.rc != 0
|
||
|
||
# ========================================
|
||
# 6. REDEPLOY GITEA
|
||
# ========================================
|
||
- name: Deploy Gitea stack
|
||
community.docker.docker_compose_v2:
|
||
project_src: "{{ gitea_stack_path }}"
|
||
state: present
|
||
pull: always
|
||
register: gitea_deploy
|
||
|
||
- name: Wait for Gitea to be ready
|
||
ansible.builtin.shell: |
|
||
cd {{ gitea_stack_path }}
|
||
docker compose ps {{ gitea_container_name }} | grep -Eiq "Up|running"
|
||
register: gitea_ready
|
||
changed_when: false
|
||
until: gitea_ready.rc == 0
|
||
retries: 12
|
||
delay: 5
|
||
failed_when: gitea_ready.rc != 0
|
||
|
||
- name: Wait for Gitea to be healthy
|
||
ansible.builtin.shell: |
|
||
cd {{ gitea_stack_path }}
|
||
docker compose exec -T {{ gitea_container_name }} curl -f http://localhost:3000/api/healthz 2>&1 | grep -q "status.*pass" && echo "HEALTHY" || echo "NOT_HEALTHY"
|
||
register: gitea_health
|
||
changed_when: false
|
||
until: gitea_health.stdout == "HEALTHY"
|
||
retries: 30
|
||
delay: 2
|
||
failed_when: false
|
||
|
||
# ========================================
|
||
# 7. RESTORE GITEA CONFIGURATION
|
||
# ========================================
|
||
- name: Restore Gitea app.ini from backup
|
||
ansible.builtin.shell: |
|
||
if [ -f "{{ backup_base_path }}/{{ actual_backup_name }}/gitea-app.ini" ]; then
|
||
cd {{ gitea_stack_path }}
|
||
docker compose exec -T {{ gitea_container_name }} sh -c "cat > /data/gitea/conf/app.ini" < "{{ backup_base_path }}/{{ actual_backup_name }}/gitea-app.ini"
|
||
docker compose restart {{ gitea_container_name }}
|
||
echo "app.ini restored and Gitea restarted"
|
||
else
|
||
echo "No app.ini backup found, using default configuration"
|
||
fi
|
||
when: not skip_backup
|
||
register: gitea_app_ini_restore
|
||
changed_when: false
|
||
failed_when: false
|
||
|
||
# ========================================
|
||
# 8. VERIFY SERVICE DISCOVERY
|
||
# ========================================
|
||
- name: Wait for service discovery (Traefik needs time to discover Gitea)
|
||
ansible.builtin.pause:
|
||
seconds: 15
|
||
|
||
- name: Check if Gitea is in traefik-public network
|
||
ansible.builtin.shell: |
|
||
docker network inspect traefik-public --format '{{ '{{' }}range .Containers{{ '}}' }}{{ '{{' }}.Name{{ '}}' }} {{ '{{' }}end{{ '}}' }}' 2>/dev/null | grep -q {{ gitea_container_name }} && echo "YES" || echo "NO"
|
||
register: gitea_in_network
|
||
changed_when: false
|
||
|
||
- name: Test direct connection from Traefik to Gitea
|
||
ansible.builtin.shell: |
|
||
cd {{ traefik_stack_path }}
|
||
docker compose exec -T {{ traefik_container_name }} wget -qO- --timeout=5 http://{{ gitea_container_name }}:3000/api/healthz 2>&1 | head -5 || echo "CONNECTION_FAILED"
|
||
register: traefik_gitea_direct
|
||
changed_when: false
|
||
failed_when: false
|
||
|
||
# ========================================
|
||
# 9. FINAL VERIFICATION
|
||
# ========================================
|
||
- name: Test Gitea via HTTPS (with retries)
|
||
ansible.builtin.uri:
|
||
url: "{{ gitea_url }}/api/healthz"
|
||
method: GET
|
||
status_code: [200]
|
||
validate_certs: false
|
||
timeout: 10
|
||
register: gitea_https_test
|
||
until: gitea_https_test.status == 200
|
||
retries: 20
|
||
delay: 3
|
||
changed_when: false
|
||
failed_when: false
|
||
|
||
- name: Check SSL certificate status
|
||
ansible.builtin.shell: |
|
||
cd {{ traefik_stack_path }}
|
||
if [ -f acme.json ] && [ -s acme.json ]; then
|
||
echo "SSL certificates: PRESENT"
|
||
else
|
||
echo "SSL certificates: MISSING or EMPTY"
|
||
fi
|
||
register: ssl_status
|
||
changed_when: false
|
||
|
||
- name: Final status summary
|
||
ansible.builtin.debug:
|
||
msg: |
|
||
================================================================================
|
||
REDEPLOYMENT SUMMARY
|
||
================================================================================
|
||
|
||
Traefik:
|
||
- Status: {{ traefik_ready.rc | ternary('Up', 'Down') }}
|
||
- SSL Certificates: {{ ssl_status.stdout }}
|
||
|
||
Gitea:
|
||
- Status: {{ gitea_ready.rc | ternary('Up', 'Down') }}
|
||
- Health: {% if gitea_health.stdout == 'HEALTHY' %}✅ Healthy{% else %}❌ Not Healthy{% endif %}
|
||
- Configuration: {% if gitea_app_ini_restore.changed %}✅ Restored{% else %}ℹ️ Using default{% endif %}
|
||
|
||
Service Discovery:
|
||
- Gitea in network: {% if gitea_in_network.stdout == 'YES' %}✅{% else %}❌{% endif %}
|
||
- Direct connection: {% if 'CONNECTION_FAILED' not in traefik_gitea_direct.stdout %}✅{% else %}❌{% endif %}
|
||
|
||
Gitea Accessibility:
|
||
{% if gitea_https_test.status == 200 %}
|
||
✅ Gitea is reachable via HTTPS (Status: 200)
|
||
URL: {{ gitea_url }}
|
||
{% else %}
|
||
❌ Gitea is NOT reachable via HTTPS (Status: {{ gitea_https_test.status | default('TIMEOUT') }})
|
||
|
||
Possible causes:
|
||
1. SSL certificate is still being generated (wait 2-5 minutes)
|
||
2. Service discovery needs more time (wait 1-2 minutes)
|
||
3. Network configuration issue
|
||
|
||
Next steps:
|
||
- Wait 2-5 minutes and test again: curl -k {{ gitea_url }}/api/healthz
|
||
- Check Traefik logs: cd {{ traefik_stack_path }} && docker compose logs {{ traefik_container_name }} --tail=50
|
||
- Check Gitea logs: cd {{ gitea_stack_path }} && docker compose logs {{ gitea_container_name }} --tail=50
|
||
{% endif %}
|
||
|
||
{% if not skip_backup %}
|
||
Backup location: {{ backup_base_path }}/{{ actual_backup_name }}
|
||
To rollback: ansible-playbook -i inventory/production.yml playbooks/maintenance/rollback-redeploy.yml \
|
||
--vault-password-file secrets/.vault_pass \
|
||
-e "backup_name={{ actual_backup_name }}"
|
||
{% endif %}
|
||
|
||
================================================================================
|
||
|