added ansible script

This commit is contained in:
liph
2026-02-16 23:40:30 +01:00
parent e6e444fff7
commit 8925d9677e
86 changed files with 15476 additions and 1911 deletions

View File

@@ -0,0 +1,98 @@
---
# Playbook 01: Preflight Checks
# Validates environment before deployment
- name: Preflight Checks
hosts: all
gather_facts: yes
become: no
tasks:
- name: Check Ansible version
assert:
that:
- ansible_version.full is version('2.14', '>=')
fail_msg: "Ansible 2.14 or higher is required"
success_msg: "Ansible version OK ({{ ansible_version.full }})"
delegate_to: localhost
run_once: true
- name: Test SSH connectivity
ping:
- name: Check sudo access
command: sudo -n true
changed_when: false
- name: Check Python3 availability
command: python3 --version
register: python_version
changed_when: false
- name: Display Python version
debug:
msg: "Python version: {{ python_version.stdout }}"
- name: Check disk space
shell: df -h / | awk 'NR==2 {print $4}'
register: disk_space
changed_when: false
- name: Validate sufficient disk space
assert:
that:
- disk_space.stdout is regex('[0-9]+G')
fail_msg: "Insufficient disk space. At least 20GB recommended."
success_msg: "Disk space OK ({{ disk_space.stdout }} available)"
- name: Check if ports 80 and 443 are available
wait_for:
port: "{{ item }}"
state: stopped
timeout: 1
loop:
- 80
- 443
ignore_errors: yes
register: port_check
- name: Detect virtualization type
command: systemd-detect-virt
register: virt_type
changed_when: false
failed_when: false
- name: Warn if running in LXC
debug:
msg: |
⚠️ RUNNING IN LXC CONTAINER
Docker requires nested virtualization.
Ensure on LXC host: lxc config set {{ inventory_hostname }} security.nesting true
when: "'lxc' in virt_type.stdout"
- name: Validate DNS resolution for all subdomains
command: dig +short {{ item }}.{{ domain }} @8.8.8.8
register: dns_check
changed_when: false
failed_when: false
loop:
- "{{ subdomain_nextcloud }}"
- "{{ subdomain_office }}"
- "{{ subdomain_draw }}"
- "{{ subdomain_notes }}"
- "{{ subdomain_homarr }}"
- "{{ subdomain_dockhand }}"
- "{{ subdomain_uptime }}"
- name: Display DNS check results
debug:
msg: "{{ item.item }}.{{ domain }} → {{ item.stdout if item.stdout else 'NOT CONFIGURED' }}"
loop: "{{ dns_check.results }}"
loop_control:
label: "{{ item.item }}.{{ domain }}"
- name: Preflight checks complete
debug:
msg: |
✓ All preflight checks passed
✓ Ready to proceed with deployment

View File

@@ -0,0 +1,119 @@
---
# Playbook 02: System Setup
# Install packages, configure firewall and security
- name: System Setup
hosts: all
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Upgrade all packages
apt:
upgrade: dist
autoremove: yes
autoclean: yes
- name: Install essential packages
apt:
name:
- curl
- wget
- git
- vim
- htop
- net-tools
- dnsutils
- ufw
- fail2ban
- unattended-upgrades
- apt-transport-https
- ca-certificates
- gnupg
- lsb-release
- software-properties-common
- python3-pip
- python3-docker
- rsync
- "{% if install_rclone %}rclone{% endif %}"
state: present
- name: Configure UFW - Allow SSH
ufw:
rule: allow
port: '22'
proto: tcp
- name: Configure UFW - Allow HTTP
ufw:
rule: allow
port: '80'
proto: tcp
- name: Configure UFW - Allow HTTPS TCP
ufw:
rule: allow
port: '443'
proto: tcp
- name: Configure UFW - Allow HTTPS UDP (HTTP/3)
ufw:
rule: allow
port: '443'
proto: udp
- name: Configure UFW - Allow Tailscale
ufw:
rule: allow
port: '41641'
proto: udp
- name: Enable UFW
ufw:
state: enabled
policy: deny
- name: Configure fail2ban for SSH
copy:
dest: /etc/fail2ban/jail.local
content: |
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 600
notify: restart fail2ban
- name: Enable unattended security updates
copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
- name: Set timezone
timezone:
name: "{{ timezone }}"
- name: Set kernel parameters for Docker
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
reload: yes
loop:
- { key: 'net.ipv4.ip_forward', value: '1' }
- { key: 'fs.inotify.max_user_watches', value: '524288' }
handlers:
- name: restart fail2ban
service:
name: fail2ban
state: restarted

View File

@@ -0,0 +1,115 @@
---
# Playbook 03: Docker Setup
# Install Docker CE and Docker Compose v2
- name: Docker Installation
hosts: all
become: yes
tasks:
- name: Remove old Docker installations
apt:
name:
- docker
- docker-engine
- docker.io
- containerd
- runc
- docker-compose
state: absent
purge: yes
- name: Remove old Docker data directories
file:
path: "{{ item }}"
state: absent
loop:
- /var/lib/docker
- /var/lib/containerd
- /etc/docker
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add Docker repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: Install Docker CE
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: yes
- name: Create Docker daemon configuration
copy:
dest: /etc/docker/daemon.json
content: |
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"userland-proxy": false,
"live-restore": true
}
notify: restart docker
- name: Create docker group
group:
name: docker
state: present
- name: Add user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
- name: Create deployment directory
file:
path: "{{ deployment_dir }}"
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
- name: Start and enable Docker
service:
name: docker
state: started
enabled: yes
- name: Verify Docker installation
command: docker --version
register: docker_version
changed_when: false
- name: Display Docker version
debug:
msg: "Docker installed: {{ docker_version.stdout }}"
- name: Verify Docker Compose installation
command: docker compose version
register: compose_version
changed_when: false
- name: Display Docker Compose version
debug:
msg: "Docker Compose installed: {{ compose_version.stdout }}"
handlers:
- name: restart docker
service:
name: docker
state: restarted

View File

@@ -0,0 +1,60 @@
---
# Playbook 04: Tailscale Setup
# Install and optionally activate Tailscale VPN
- name: Tailscale Installation
hosts: all
become: yes
tasks:
- name: Add Tailscale GPG key
apt_key:
url: https://pkgs.tailscale.com/stable/ubuntu/{{ ansible_distribution_release }}.noarmor.gpg
state: present
- name: Add Tailscale repository
apt_repository:
repo: "deb https://pkgs.tailscale.com/stable/ubuntu {{ ansible_distribution_release }} main"
state: present
- name: Install Tailscale
apt:
name: tailscale
state: present
update_cache: yes
- name: Check if Tailscale auth key is provided
set_fact:
tailscale_auto_enable: "{{ tailscale_auth_key is defined and tailscale_auth_key != '' }}"
- name: Activate Tailscale (if auth key provided)
command: tailscale up --authkey={{ tailscale_auth_key }} --advertise-tags=tag:nextcloud
when: tailscale_auto_enable
register: tailscale_activation
- name: Get Tailscale IP (if activated)
command: tailscale ip -4
register: tailscale_ip
when: tailscale_auto_enable
changed_when: false
- name: Display Tailscale status (activated)
debug:
msg: |
✓ Tailscale activated
IP: {{ tailscale_ip.stdout }}
when: tailscale_auto_enable
- name: Display manual activation instructions (not activated)
debug:
msg: |
Tailscale installed but not activated.
To enable, run on the server:
sudo tailscale up
when: not tailscale_auto_enable
- name: Enable Tailscale service
service:
name: tailscaled
state: started
enabled: yes

View File

@@ -0,0 +1,109 @@
---
# Playbook 05: Deploy Stack
# Deploy Docker Compose stack with all services
- name: Deploy Nextcloud Stack
hosts: all
become: yes
tasks:
- name: Create directory structure
file:
path: "{{ item }}"
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
loop:
- "{{ deployment_dir }}/configs"
- "{{ deployment_dir }}/configs/caddy"
- "{{ deployment_dir }}/configs/nextcloud"
- "{{ deployment_dir }}/data"
- "{{ deployment_dir }}/data/homarr"
- "{{ deployment_dir }}/data/obsidian/config"
- "{{ deployment_dir }}/data/obsidian/vault"
- "{{ deployment_dir }}/backups"
- "{{ deployment_dir }}/backups/database"
- "{{ deployment_dir }}/backups/volumes"
- name: Template docker-compose.yml
template:
src: ../roles/nextcloud_stack/templates/docker-compose.yml.j2
dest: "{{ deployment_dir }}/docker-compose.yml"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
- name: Template .env file
template:
src: ../roles/nextcloud_stack/templates/env.j2
dest: "{{ deployment_dir }}/.env"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0600'
no_log: true
- name: Template Caddyfile
template:
src: ../roles/caddy/templates/Caddyfile.j2
dest: "{{ deployment_dir }}/configs/caddy/Caddyfile"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
- name: Pull Docker images
command: docker compose pull
args:
chdir: "{{ deployment_dir }}"
become_user: "{{ ansible_user }}"
- name: Start Docker Compose stack
command: docker compose up -d
args:
chdir: "{{ deployment_dir }}"
become_user: "{{ ansible_user }}"
- name: Wait for PostgreSQL to be healthy
command: docker exec next-db pg_isready -U {{ db_user }}
register: pg_ready
until: pg_ready.rc == 0
retries: 30
delay: 10
changed_when: false
- name: Wait for Nextcloud to be accessible
uri:
url: http://localhost:8808/status.php
status_code: 200
register: nc_status
until: nc_status.status == 200
retries: 30
delay: 10
- name: Configure Nextcloud Redis cache
command: |
docker exec -u www-data next php occ config:system:set redis host --value=next-redis
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Configure Nextcloud Redis port
command: |
docker exec -u www-data next php occ config:system:set redis port --value=6379
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Configure Nextcloud memcache
command: |
docker exec -u www-data next php occ config:system:set memcache.locking --value='\\OC\\Memcache\\Redis'
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Display deployment success
debug:
msg: |
✓ Docker Compose stack deployed
✓ All containers started
✓ Nextcloud is accessible

View File

@@ -0,0 +1,48 @@
---
# Playbook 06: Configure Caddy
# Setup reverse proxy and obtain SSL certificates
- name: Configure Caddy Reverse Proxy
hosts: all
become: yes
tasks:
- name: Validate Caddyfile syntax
command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
args:
chdir: "{{ deployment_dir }}"
register: caddy_validate
failed_when: caddy_validate.rc != 0
changed_when: false
- name: Reload Caddy configuration
command: docker exec caddy caddy reload --config /etc/caddy/Caddyfile
args:
chdir: "{{ deployment_dir }}"
- name: Wait for SSL certificates (may take 1-2 minutes)
pause:
seconds: 30
prompt: "Waiting for Let's Encrypt to issue certificates..."
- name: Test HTTPS endpoint for Nextcloud
uri:
url: "https://{{ subdomain_nextcloud }}.{{ domain }}/status.php"
validate_certs: yes
status_code: 200
register: https_test
until: https_test.status == 200
retries: 10
delay: 10
ignore_errors: yes
- name: Display Caddy status
debug:
msg: |
✓ Caddyfile validated
✓ Caddy reloaded
{% if https_test.status == 200 %}
✓ HTTPS working: https://{{ subdomain_nextcloud }}.{{ domain }}
{% else %}
⚠ HTTPS check failed - verify DNS and firewall
{% endif %}

View File

@@ -0,0 +1,87 @@
---
# Playbook 07: Setup Backups
# Configure automated backup system
- name: Setup Backup System
hosts: all
become: yes
tasks:
- name: Create backup script
copy:
dest: "{{ deployment_dir }}/backup.sh"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
content: |
#!/bin/bash
# Nextcloud Stack Backup Script
set -euo pipefail
BACKUP_DIR="{{ deployment_dir }}/backups"
DB_BACKUP_DIR="$BACKUP_DIR/database"
VOLUME_BACKUP_DIR="$BACKUP_DIR/volumes"
RETENTION_DAYS={{ backup_retention_days }}
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="$BACKUP_DIR/backup.log"
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Starting backup process..."
# Database backup
log "Backing up PostgreSQL database..."
docker exec next-db pg_dump -U {{ db_user }} {{ db_name }} | \
gzip > "$DB_BACKUP_DIR/nextcloud_db_$DATE.sql.gz"
if [ $? -eq 0 ]; then
log "Database backup completed: nextcloud_db_$DATE.sql.gz"
else
log "ERROR: Database backup failed!"
exit 1
fi
# Weekly volume backup (Sundays only)
if [ $(date +%u) -eq 7 ]; then
log "Weekly volume backup (Sunday)..."
docker exec -u www-data next php occ maintenance:mode --on
tar -czf "$VOLUME_BACKUP_DIR/nextcloud_data_$DATE.tar.gz" \
-C /var/lib/docker/volumes/nextcloud_nextcloud_data/_data . 2>/dev/null || true
tar -czf "$VOLUME_BACKUP_DIR/configs_$DATE.tar.gz" \
-C {{ deployment_dir }}/configs . 2>/dev/null || true
docker exec -u www-data next php occ maintenance:mode --off
log "Volume backup completed"
fi
# Cleanup old backups
log "Cleaning up backups older than $RETENTION_DAYS days..."
find "$DB_BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
find "$VOLUME_BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
log "Backup process completed successfully!"
- name: Create cron job for daily backups
cron:
name: "Nextcloud Stack Backup"
minute: "0"
hour: "3"
job: "{{ deployment_dir }}/backup.sh >> {{ deployment_dir }}/backups/backup.log 2>&1"
user: root
- name: Run initial test backup
command: "{{ deployment_dir }}/backup.sh"
register: backup_test
ignore_errors: yes
- name: Display backup status
debug:
msg: |
✓ Backup script created
✓ Cron job configured (daily 3:00 AM)
✓ Test backup completed
Location: {{ deployment_dir }}/backups/

View File

@@ -0,0 +1,154 @@
---
# Playbook 08: Post-Deployment
# Final verification and configuration
- name: Post-Deployment Tasks
hosts: all
become: yes
tasks:
- name: Verify all containers are running
command: docker compose ps --format json
args:
chdir: "{{ deployment_dir }}"
register: container_status
changed_when: false
- name: Check Nextcloud status
command: docker exec -u www-data next php occ status
args:
chdir: "{{ deployment_dir }}"
register: nc_status
changed_when: false
ignore_errors: yes
- name: Install recommended Nextcloud apps
command: docker exec -u www-data next php occ app:install {{ item }}
args:
chdir: "{{ deployment_dir }}"
loop:
- calendar
- contacts
- tasks
- notes
ignore_errors: yes
- name: Configure Nextcloud background jobs
command: docker exec -u www-data next php occ background:cron
args:
chdir: "{{ deployment_dir }}"
ignore_errors: yes
- name: Setup Nextcloud cron job
cron:
name: "Nextcloud Background Jobs"
minute: "*/5"
job: "docker exec -u www-data next php /var/www/html/cron.php"
user: root
- name: Get Tailscale IP (if activated)
command: tailscale ip -4
register: tailscale_ip_result
changed_when: false
failed_when: false
- name: Create deployment report
copy:
dest: "{{ deployment_dir }}/DEPLOYMENT.txt"
content: |
════════════════════════════════════════════════════════════
NEXTCLOUD STACK DEPLOYMENT REPORT
════════════════════════════════════════════════════════════
Server: {{ inventory_hostname }}
IP Address: {{ ansible_host }}
Domain: {{ domain }}
Deployment Date: {{ ansible_date_time.iso8601 }}
────────────────────────────────────────────────────────────
PUBLIC SERVICES (HTTPS)
────────────────────────────────────────────────────────────
• Nextcloud: https://{{ subdomain_nextcloud }}.{{ domain }}
• OnlyOffice: https://{{ subdomain_office }}.{{ domain }}
• Excalidraw: https://{{ subdomain_draw }}.{{ domain }}
• Obsidian: https://{{ subdomain_notes }}.{{ domain }}
────────────────────────────────────────────────────────────
MANAGEMENT SERVICES (Tailscale only)
────────────────────────────────────────────────────────────
• Homarr: https://{{ subdomain_homarr }}.{{ domain }}
• Dockhand: https://{{ subdomain_dockhand }}.{{ domain }}
• Uptime Kuma: https://{{ subdomain_uptime }}.{{ domain }}
────────────────────────────────────────────────────────────
CREDENTIALS
────────────────────────────────────────────────────────────
Nextcloud Admin User: {{ admin_user }}
Nextcloud Admin Password: [stored in Ansible vault]
────────────────────────────────────────────────────────────
TAILSCALE
────────────────────────────────────────────────────────────
{% if tailscale_ip_result.rc == 0 %}
Status: Connected
Tailscale IP: {{ tailscale_ip_result.stdout }}
{% else %}
Status: Not activated
Activate with: sudo tailscale up
{% endif %}
────────────────────────────────────────────────────────────
BACKUPS
────────────────────────────────────────────────────────────
Schedule: Daily at 3:00 AM
Location: {{ deployment_dir }}/backups/
Retention: {{ backup_retention_days }} days
────────────────────────────────────────────────────────────
USEFUL COMMANDS
────────────────────────────────────────────────────────────
View containers: cd {{ deployment_dir }} && docker compose ps
View logs: cd {{ deployment_dir }} && docker compose logs -f
Restart service: cd {{ deployment_dir }} && docker compose restart [service]
Manual backup: {{ deployment_dir }}/backup.sh
Nextcloud CLI: docker exec -u www-data next php occ [command]
════════════════════════════════════════════════════════════
- name: Display deployment summary
debug:
msg: |
╔════════════════════════════════════════════════════════════╗
║ DEPLOYMENT COMPLETED SUCCESSFULLY! ║
╚════════════════════════════════════════════════════════════╝
Server: {{ inventory_hostname }} ({{ ansible_host }})
Domain: {{ domain }}
📦 Public Services:
• Nextcloud: https://{{ subdomain_nextcloud }}.{{ domain }}
• OnlyOffice: https://{{ subdomain_office }}.{{ domain }}
• Excalidraw: https://{{ subdomain_draw }}.{{ domain }}
• Obsidian: https://{{ subdomain_notes }}.{{ domain }}
🔒 Management (Tailscale only):
• Homarr: https://{{ subdomain_homarr }}.{{ domain }}
• Dockhand: https://{{ subdomain_dockhand }}.{{ domain }}
• Uptime Kuma: https://{{ subdomain_uptime }}.{{ domain }}
👤 Nextcloud Admin:
Username: {{ admin_user }}
Password: [check vault]
💾 Backups:
Daily at 3:00 AM
Location: {{ deployment_dir }}/backups/
📝 Next Steps:
1. Login to Nextcloud and complete setup
2. Setup Uptime Kuma monitoring (via Tailscale)
3. Configure Homarr dashboard (via Tailscale)
4. Review deployment report: {{ deployment_dir }}/DEPLOYMENT.txt
Deployment report saved to:
{{ deployment_dir }}/DEPLOYMENT.txt

View File

@@ -0,0 +1,41 @@
---
# Playbook 99: Emergency Rollback
# Stop and remove the deployment (keeps backups)
- name: Emergency Rollback
hosts: all
become: yes
tasks:
- name: Confirm rollback
pause:
prompt: |
⚠️ WARNING: This will stop and remove all containers!
Backups in {{ deployment_dir }}/backups/ will be preserved.
Press ENTER to continue or Ctrl+C to cancel
- name: Stop Docker Compose stack
command: docker compose down
args:
chdir: "{{ deployment_dir }}"
become_user: "{{ ansible_user }}"
ignore_errors: yes
- name: Remove deployment directory (except backups)
shell: |
cd {{ deployment_dir }}
rm -rf configs data docker-compose.yml .env
ls -la
args:
warn: false
- name: Display rollback status
debug:
msg: |
✓ Containers stopped and removed
✓ Deployment files removed
✓ Backups preserved in {{ deployment_dir }}/backups/
✓ Docker volumes preserved (use 'docker volume ls' to see)
To completely remove volumes (DELETES ALL DATA):
cd {{ deployment_dir }} && docker compose down -v

View File

@@ -0,0 +1,26 @@
---
# Main Playbook - Nextcloud Stack Deployment
# This orchestrates the complete deployment process
- name: Nextcloud Stack - Complete Deployment
hosts: all
gather_facts: yes
pre_tasks:
- name: Display deployment banner
debug:
msg: |
╔════════════════════════════════════════════════════════════╗
║ Nextcloud Stack Deployment Starting ║
║ Target: {{ inventory_hostname }} ({{ ansible_host }}) ║
║ Domain: {{ domain }} ║
╚════════════════════════════════════════════════════════════╝
- import_playbook: 01-preflight-checks.yml
- import_playbook: 02-system-setup.yml
- import_playbook: 03-docker-setup.yml
- import_playbook: 04-tailscale-setup.yml
- import_playbook: 05-deploy-stack.yml
- import_playbook: 06-configure-caddy.yml
- import_playbook: 07-setup-backups.yml
- import_playbook: 08-post-deployment.yml