Files
michaelschiemer/deployment/ansible/playbooks/initial-server-setup.yml
Michael Schiemer 36ef2a1e2c
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
fix: Gitea Traefik routing and connection pool optimization
- Remove middleware reference from Gitea Traefik labels (caused routing issues)
- Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s)
- Add explicit service reference in Traefik labels
- Fix intermittent 504 timeouts by improving PostgreSQL connection handling

Fixes Gitea unreachability via git.michaelschiemer.de
2025-11-09 14:46:15 +01:00

351 lines
12 KiB
YAML

---
- name: Initial Server Setup - Debian 13 (Trixie)
hosts: production
become: yes
gather_facts: yes
vars:
# User configuration
deploy_user: "{{ ansible_user | default('deploy') }}"
deploy_user_groups: ['sudo'] # docker group added after Docker installation
# SSH configuration
ssh_key_only_auth: false # Set to true AFTER SSH keys are properly configured
ssh_disable_root_login: false # Set to true after deploy user is configured
# Firewall configuration
firewall_enable: false # Set to true after initial setup is complete
firewall_ports:
- { port: 22, proto: 'tcp', comment: 'SSH' }
- { port: 80, proto: 'tcp', comment: 'HTTP' }
- { port: 443, proto: 'tcp', comment: 'HTTPS' }
- { port: 51820, proto: 'udp', comment: 'WireGuard' }
# System packages
system_base_packages:
- curl
- wget
- git
- vim
- sudo
- ufw
- fail2ban
- rsync
tasks:
- name: Display system information
ansible.builtin.debug:
msg:
- "Distribution: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Hostname: {{ ansible_hostname }}"
- "Deploy User: {{ deploy_user }}"
# ========================================
# 1. System Updates
# ========================================
- name: Check and wait for apt locks to be released
ansible.builtin.shell:
cmd: |
for lock in /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock; do
if [ -f "$lock" ]; then
echo "Waiting for lock: $lock"
count=0
while [ -f "$lock" ] && [ $count -lt 60 ]; do
sleep 1
count=$((count + 1))
done
if [ -f "$lock" ]; then
echo "Warning: Lock still exists after 60s: $lock"
else
echo "Lock released: $lock"
fi
fi
done
changed_when: false
failed_when: false
timeout: 70
- name: Update apt cache
ansible.builtin.shell:
cmd: timeout 300 apt-get update -qq
environment:
DEBIAN_FRONTEND: noninteractive
APT_LISTCHANGES_FRONTEND: none
register: apt_update_result
changed_when: apt_update_result.rc == 0
failed_when: apt_update_result.rc != 0
timeout: 300
- name: Display apt update result
ansible.builtin.debug:
msg: "apt update completed successfully"
when: apt_update_result.rc == 0
- name: Show packages to be upgraded
ansible.builtin.command:
cmd: apt list --upgradable 2>/dev/null | tail -n +2 | wc -l
register: packages_to_upgrade
changed_when: false
failed_when: false
- name: Display upgrade information
ansible.builtin.debug:
msg: "Packages to upgrade: {{ packages_to_upgrade.stdout | default('0') | trim }}"
- name: Upgrade system packages
ansible.builtin.shell:
cmd: timeout 600 apt-get upgrade -y -qq && apt-get autoremove -y -qq
environment:
DEBIAN_FRONTEND: noninteractive
APT_LISTCHANGES_FRONTEND: none
register: apt_upgrade_result
changed_when: apt_upgrade_result.rc == 0
failed_when: apt_upgrade_result.rc != 0
timeout: 600
- name: Display apt upgrade result
ansible.builtin.debug:
msg: "apt upgrade completed: {{ 'Packages upgraded' if apt_upgrade_result.rc == 0 else 'Failed' }}"
when: apt_upgrade_result.rc is defined
# ========================================
# 2. Install Base Packages
# ========================================
- name: Install base packages
ansible.builtin.shell:
cmd: timeout 300 apt-get install -y -qq {{ system_base_packages | join(' ') }}
environment:
DEBIAN_FRONTEND: noninteractive
APT_LISTCHANGES_FRONTEND: none
register: apt_install_result
changed_when: apt_install_result.rc == 0
failed_when: apt_install_result.rc != 0
timeout: 300
- name: Display apt install result
ansible.builtin.debug:
msg: "apt install completed: {{ 'Packages installed/updated' if apt_install_result.rc == 0 else 'Failed' }}"
when: apt_install_result.rc is defined
# ========================================
# 3. Create Deploy User
# ========================================
- name: Check if deploy user exists
ansible.builtin.shell:
cmd: timeout 5 getent passwd {{ deploy_user }} >/dev/null 2>&1 && echo "exists" || echo "not_found"
register: deploy_user_check
changed_when: false
failed_when: false
timeout: 10
- name: Create deploy user
ansible.builtin.user:
name: "{{ deploy_user }}"
groups: "{{ deploy_user_groups }}"
append: yes
shell: /bin/bash
create_home: yes
when:
- "'not_found' in deploy_user_check.stdout"
- deploy_user != 'root'
- name: Ensure deploy user has sudo access
ansible.builtin.lineinfile:
path: /etc/sudoers.d/deploy
line: "{{ deploy_user }} ALL=(ALL) NOPASSWD: ALL"
create: yes
validate: 'visudo -cf %s'
mode: '0440'
when: deploy_user != 'root'
# ========================================
# 4. SSH Configuration
# ========================================
- name: Get deploy user home directory
ansible.builtin.getent:
database: passwd
key: "{{ deploy_user }}"
register: deploy_user_info
when: deploy_user != 'root'
ignore_errors: yes
- name: Set deploy user home directory (root)
ansible.builtin.set_fact:
deploy_user_home: "/root"
when: deploy_user == 'root'
- name: Set deploy user home directory (from getent)
ansible.builtin.set_fact:
deploy_user_home: "{{ deploy_user_info.ansible_facts.getent_passwd[deploy_user][4] }}"
when:
- deploy_user != 'root'
- deploy_user_info.ansible_facts.getent_passwd[deploy_user] is defined
- name: Set deploy user home directory (fallback)
ansible.builtin.set_fact:
deploy_user_home: "/home/{{ deploy_user }}"
when: deploy_user_home is not defined
- name: Ensure .ssh directory exists
ansible.builtin.file:
path: "{{ deploy_user_home }}/.ssh"
state: directory
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
mode: '0700'
- name: Add SSH public key from control node
ansible.builtin.authorized_key:
user: "{{ deploy_user }}"
state: present
key: "{{ lookup('file', ansible_ssh_private_key_file | default('~/.ssh/production') + '.pub') }}"
when: ansible_ssh_private_key_file is defined
- name: Verify SSH key is configured before disabling password auth
ansible.builtin.stat:
path: "{{ deploy_user_home }}/.ssh/authorized_keys"
register: ssh_key_file
when: ssh_key_only_auth | bool
- name: Configure SSH key-only authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
backup: yes
loop:
- { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^#?PubkeyAuthentication', line: 'PubkeyAuthentication yes' }
- { regexp: '^#?AuthorizedKeysFile', line: 'AuthorizedKeysFile .ssh/authorized_keys' }
when:
- ssh_key_only_auth | bool
- ssh_key_file.stat.exists | default(false)
notify: restart sshd
- name: Disable root login (optional)
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
backup: yes
when: ssh_disable_root_login | bool
notify: restart sshd
# ========================================
# 5. Firewall Configuration
# ========================================
# WICHTIG: Firewall wird erst am Ende konfiguriert, um SSH-Verbindung nicht zu unterbrechen
- name: Check current UFW status
ansible.builtin.command:
cmd: ufw status | head -1
register: ufw_current_status
changed_when: false
failed_when: false
when: firewall_enable | bool
- name: Display current firewall status
ansible.builtin.debug:
msg: "Current firewall status: {{ ufw_current_status.stdout | default('Unknown') }}"
when: firewall_enable | bool and ufw_current_status is defined
- name: Ensure SSH port is allowed before configuring firewall
ansible.builtin.command:
cmd: ufw allow 22/tcp comment 'SSH - Allow before enabling firewall'
when:
- firewall_enable | bool
- "'inactive' in (ufw_current_status.stdout | default(''))"
ignore_errors: yes
- name: Reset UFW to defaults (only if inactive)
ansible.builtin.command:
cmd: ufw --force reset
when:
- firewall_enable | bool
- "'inactive' in (ufw_current_status.stdout | default(''))"
changed_when: false
- name: Set UFW default policies
ansible.builtin.command:
cmd: "ufw default {{ item.policy }} {{ item.direction }}"
loop:
- { policy: 'deny', direction: 'incoming' }
- { policy: 'allow', direction: 'outgoing' }
when:
- firewall_enable | bool
- "'inactive' in (ufw_current_status.stdout | default(''))"
- name: Allow firewall ports (ensure SSH is first)
ansible.builtin.command:
cmd: "ufw allow {{ item.port }}/{{ item.proto }} comment '{{ item.comment }}'"
loop: "{{ firewall_ports }}"
when:
- firewall_enable | bool
- "'inactive' in (ufw_current_status.stdout | default(''))"
register: ufw_rules
changed_when: ufw_rules.rc == 0
- name: Enable UFW (only if inactive)
ansible.builtin.command:
cmd: ufw --force enable
when:
- firewall_enable | bool
- "'inactive' in (ufw_current_status.stdout | default(''))"
- name: Display UFW status
ansible.builtin.command:
cmd: ufw status verbose
register: ufw_status
changed_when: false
- name: Show UFW status
ansible.builtin.debug:
msg: "{{ ufw_status.stdout_lines }}"
# ========================================
# 6. Fail2ban Configuration
# ========================================
- name: Ensure fail2ban is enabled and started
ansible.builtin.systemd:
name: fail2ban
enabled: yes
state: started
when: "'fail2ban' in system_base_packages"
# ========================================
# 7. System Configuration
# ========================================
- name: Configure timezone
ansible.builtin.timezone:
name: Europe/Berlin
- name: Display setup summary
ansible.builtin.debug:
msg:
- "=========================================="
- "Initial Server Setup Complete"
- "=========================================="
- "Deploy User: {{ deploy_user }}"
- "SSH Key-only Auth: {{ ssh_key_only_auth }}"
- "Firewall: {{ 'Enabled' if firewall_enable else 'Disabled' }}"
- "Fail2ban: {{ 'Enabled' if 'fail2ban' in system_base_packages else 'Disabled' }}"
- "=========================================="
- "Next Steps:"
- "1. Test SSH connection: ssh {{ deploy_user }}@{{ ansible_host }}"
- "2. Install Docker: ansible-playbook playbooks/install-docker.yml"
- "3. Deploy Infrastructure: ansible-playbook playbooks/setup-infrastructure.yml"
- "=========================================="
handlers:
- name: restart sshd
ansible.builtin.systemd:
name: sshd
state: restarted