From 846ab74f87e5a7e8c777f222ad9b1bc74ef8101f Mon Sep 17 00:00:00 2001 From: Joakim Date: Sat, 20 Dec 2025 19:51:26 +0100 Subject: [PATCH] Fix Nextcloud DNS resolution and implement systemd cron for background jobs - Enable IP forwarding in security playbook (net.ipv4.ip_forward = 1) - Add podman network firewall rules to fix container DNS/HTTPS access - Implement systemd timer for reliable Nextcloud background job execution - Add database optimization tasks (indices, bigint conversion, mimetypes) - Configure maintenance window (04:00 UTC) and phone region (NO) - Add security headers (X-Robots-Tag, X-Permitted-Cross-Domain-Policies) - Create Nextcloud removal playbook for clean uninstall - Fix nftables interface matching (podman0 vs podman+) Root cause: nftables FORWARD chain blocked container egress traffic Solution: Explicit firewall rules for podman0 bridge interface --- now-what.md | 3 + playbooks/remove-nextcloud.yml | 212 ++++++++++++++++++ playbooks/security.yml | 4 +- rick-infra.yml | 16 +- roles/nextcloud/defaults/main.yml | 14 ++ roles/nextcloud/tasks/cron.yml | 72 ++++++ roles/nextcloud/tasks/main.yml | 15 ++ roles/nextcloud/tasks/optimization.yml | 64 ++++++ roles/nextcloud/templates/nextcloud.caddy.j2 | 4 + .../templates/nextcloud.config.php.j2 | 32 +++ roles/podman/defaults/main.yml | 4 + roles/podman/handlers/main.yml | 7 +- roles/podman/tasks/main.yml | 16 ++ roles/podman/templates/podman.nft.j2 | 32 +++ 14 files changed, 484 insertions(+), 11 deletions(-) create mode 100644 playbooks/remove-nextcloud.yml create mode 100644 roles/nextcloud/tasks/cron.yml create mode 100644 roles/nextcloud/tasks/optimization.yml create mode 100644 roles/nextcloud/templates/nextcloud.config.php.j2 create mode 100644 roles/podman/templates/podman.nft.j2 diff --git a/now-what.md b/now-what.md index f48dc13..0a093ee 100644 --- a/now-what.md +++ b/now-what.md @@ -5,6 +5,7 @@ - [ ] What gets served on jnss.me? - [ ] Backups +- [x] Titan email provider support. For smtp access to hello@jnss.me - [ ] Vaultvarden @@ -13,10 +14,12 @@ - [ ] Settings - [ ] Contacts and calendars - [ ] Storage bucket integration? + - [x] SMTP setup for email sending - [x] Gitea - [x] SSH passthrough setup - [x] Figure out how to disable registration and local password + - [x] SMTP setup for email sending - [ ] Authentik Invitations for users? diff --git a/playbooks/remove-nextcloud.yml b/playbooks/remove-nextcloud.yml new file mode 100644 index 0000000..b5e457c --- /dev/null +++ b/playbooks/remove-nextcloud.yml @@ -0,0 +1,212 @@ +--- +# ================================================================= +# Nextcloud Removal Playbook +# ================================================================= +# Rick-Infra - Clean removal of Nextcloud installation +# +# This playbook removes all Nextcloud components: +# - Systemd services and timers +# - Container and images +# - Data directories +# - Database and user +# - Caddy configuration +# - System user and groups +# +# Usage: ansible-playbook playbooks/remove-nextcloud.yml -i inventory/hosts.yml + +- name: Remove Nextcloud Installation + hosts: arch-vps + become: yes + gather_facts: yes + + vars: + nextcloud_user: nextcloud + nextcloud_group: nextcloud + nextcloud_home: /opt/nextcloud + nextcloud_db_name: nextcloud + nextcloud_db_user: nextcloud + caddy_sites_enabled_dir: /etc/caddy/sites-enabled + + tasks: + # ============================================ + # Stop and Disable Services + # ============================================ + + - name: Stop and disable nextcloud-cron timer + systemd: + name: nextcloud-cron.timer + state: stopped + enabled: no + failed_when: false + + - name: Stop and disable nextcloud-cron service + systemd: + name: nextcloud-cron.service + state: stopped + enabled: no + failed_when: false + + - name: Stop and disable nextcloud service + systemd: + name: nextcloud.service + state: stopped + enabled: no + failed_when: false + + # ============================================ + # Remove Container and Images + # ============================================ + + - name: Remove nextcloud container (if running) + command: podman rm -f nextcloud + register: container_remove + changed_when: container_remove.rc == 0 + failed_when: false + + - name: Remove nextcloud images + command: podman rmi -f {{ item }} + loop: + - docker.io/library/nextcloud:stable-fpm + - docker.io/library/nextcloud + register: image_remove + changed_when: image_remove.rc == 0 + failed_when: false + + # ============================================ + # Remove Systemd Units + # ============================================ + + - name: Remove nextcloud-cron systemd units + file: + path: "{{ item }}" + state: absent + loop: + - /etc/systemd/system/nextcloud-cron.timer + - /etc/systemd/system/nextcloud-cron.service + + - name: Remove nextcloud quadlet file + file: + path: /etc/containers/systemd/nextcloud.container + state: absent + + - name: Reload systemd daemon + systemd: + daemon_reload: yes + + # ============================================ + # Remove Database + # ============================================ + + - name: Drop nextcloud database + become_user: postgres + postgresql_db: + name: "{{ nextcloud_db_name }}" + state: absent + failed_when: false + + - name: Drop nextcloud database user + become_user: postgres + postgresql_user: + name: "{{ nextcloud_db_user }}" + state: absent + failed_when: false + + # ============================================ + # Remove Caddy Configuration + # ============================================ + + - name: Remove nextcloud Caddy configuration + file: + path: "{{ caddy_sites_enabled_dir }}/nextcloud.caddy" + state: absent + notify: reload caddy + + # ============================================ + # Remove Data Directories + # ============================================ + + - name: Remove nextcloud home directory (including all data) + file: + path: "{{ nextcloud_home }}" + state: absent + + # ============================================ + # Remove User and Groups + # ============================================ + + - name: Remove nextcloud user + user: + name: "{{ nextcloud_user }}" + state: absent + remove: yes + force: yes + + - name: Remove nextcloud group + group: + name: "{{ nextcloud_group }}" + state: absent + + # ============================================ + # Clean Up Remaining Files + # ============================================ + + - name: Find nextcloud-related files in /tmp + find: + paths: /tmp + patterns: "nextcloud*,nc_*" + file_type: any + register: tmp_files + + - name: Remove nextcloud temp files + file: + path: "{{ item.path }}" + state: absent + loop: "{{ tmp_files.files }}" + when: tmp_files.files | length > 0 + failed_when: false + + - name: Remove caddy logs for nextcloud + file: + path: /var/log/caddy/nextcloud.log + state: absent + failed_when: false + + # ============================================ + # Verification + # ============================================ + + - name: Verify nextcloud service is removed + command: systemctl list-units --all nextcloud* + register: units_check + changed_when: false + + - name: Verify nextcloud container is removed + command: podman ps -a --filter name=nextcloud + register: container_check + changed_when: false + + - name: Display removal status + debug: + msg: | + ✅ Nextcloud removal complete! + + Removed components: + - âšī¸ Nextcloud service and cron timer + - đŸŗ Container: {{ 'Removed' if container_remove.rc == 0 else 'Not found' }} + - đŸ—„ī¸ Database: {{ nextcloud_db_name }} + - 📁 Data directory: {{ nextcloud_home }} + - 👤 System user: {{ nextcloud_user }} + - 🌐 Caddy configuration + + Remaining services: + {{ units_check.stdout }} + + Containers: + {{ container_check.stdout }} + + handlers: + - name: reload caddy + systemd: + name: caddy + state: reloaded + failed_when: false diff --git a/playbooks/security.yml b/playbooks/security.yml index b668a67..50c9adf 100644 --- a/playbooks/security.yml +++ b/playbooks/security.yml @@ -322,8 +322,8 @@ sysctl_file: /etc/sysctl.d/99-security.conf reload: yes loop: - # Disable IP forwarding - - { name: 'net.ipv4.ip_forward', value: '0' } + # Enable IP forwarding (required for container networking) + - { name: 'net.ipv4.ip_forward', value: '1' } - { name: 'net.ipv6.conf.all.forwarding', value: '0' } # Disable source routing diff --git a/rick-infra.yml b/rick-infra.yml index 6e7f399..afdb2d0 100644 --- a/rick-infra.yml +++ b/rick-infra.yml @@ -25,13 +25,13 @@ # name: authentik # tags: ['authentik', 'sso', 'auth'] - - name: Deploy Gitea - include_role: - name: gitea - tags: ['gitea', 'git', 'development'] - - # - name: Deploy Nextcloud + # - name: Deploy Gitea # include_role: - # name: nextcloud - # tags: ['nextcloud', 'cloud', 'storage'] + # name: gitea + # tags: ['gitea', 'git', 'development'] + + - name: Deploy Nextcloud + include_role: + name: nextcloud + tags: ['nextcloud', 'cloud', 'storage'] diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index ae89eef..58eaab1 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -67,6 +67,20 @@ nextcloud_overwriteprotocol: "https" nextcloud_php_memory_limit: "512M" nextcloud_php_upload_limit: "512M" +# ================================================================= +# Background Jobs Configuration +# ================================================================= + +nextcloud_background_jobs_mode: "cron" # Options: ajax, webcron, cron +nextcloud_cron_interval: "5min" # How often cron runs (systemd timer) + +# ================================================================= +# Maintenance Configuration +# ================================================================= + +nextcloud_maintenance_window_start: 4 # Start hour (UTC) for maintenance window +nextcloud_default_phone_region: "NO" # Default phone region code (ISO 3166-1 alpha-2) + # ================================================================= # Caddy Integration # ================================================================= diff --git a/roles/nextcloud/tasks/cron.yml b/roles/nextcloud/tasks/cron.yml new file mode 100644 index 0000000..71aea62 --- /dev/null +++ b/roles/nextcloud/tasks/cron.yml @@ -0,0 +1,72 @@ +--- +# ================================================================= +# Nextcloud Background Jobs Configuration +# ================================================================= +# Rick-Infra - Nextcloud Role +# +# Configures systemd timer for reliable background job execution +# instead of Ajax-based cron (which requires user activity) + +- name: Create nextcloud cron service + copy: + content: | + [Unit] + Description=Nextcloud Background Jobs (cron.php) + Documentation=https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/background_jobs_configuration.html + After=nextcloud.service + Requires=nextcloud.service + + [Service] + Type=oneshot + ExecStart=/usr/bin/podman exec --user www-data nextcloud php -f /var/www/html/cron.php + + # Logging + StandardOutput=journal + StandardError=journal + SyslogIdentifier=nextcloud-cron + dest: /etc/systemd/system/nextcloud-cron.service + mode: '0644' + backup: yes + notify: reload systemd + +- name: Create nextcloud cron timer + copy: + content: | + [Unit] + Description=Nextcloud Background Jobs Timer + Documentation=https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/background_jobs_configuration.html + + [Timer] + OnBootSec=5min + OnUnitActiveSec={{ nextcloud_cron_interval }} + Unit=nextcloud-cron.service + + [Install] + WantedBy=timers.target + dest: /etc/systemd/system/nextcloud-cron.timer + mode: '0644' + backup: yes + notify: reload systemd + +- name: Enable and start nextcloud cron timer + systemd: + name: nextcloud-cron.timer + enabled: yes + state: started + daemon_reload: yes + +- name: Configure Nextcloud to use cron for background jobs + command: > + podman exec --user www-data nextcloud + php occ background:cron + register: nextcloud_cron_mode + changed_when: "'background jobs mode changed' in nextcloud_cron_mode.stdout or 'Set mode for background jobs to' in nextcloud_cron_mode.stdout" + failed_when: + - nextcloud_cron_mode.rc != 0 + - "'mode for background jobs is currently' not in nextcloud_cron_mode.stdout" + +- name: Verify cron timer is active + command: systemctl is-active nextcloud-cron.timer + register: timer_status + changed_when: false + failed_when: timer_status.stdout != "active" diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index 626d0b2..5e8c904 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -138,6 +138,21 @@ notify: restart nextcloud tags: [config, redis] +- name: Truncate nextcloud.log to prevent bloat + shell: | + podman exec nextcloud truncate -s 0 /var/www/html/data/nextcloud.log || true + changed_when: false + failed_when: false + tags: [maintenance, cleanup] + +- name: Configure background jobs (cron) + include_tasks: cron.yml + tags: [cron, background-jobs] + +- name: Optimize database and apply configuration + include_tasks: optimization.yml + tags: [optimization, database] + - name: Display Nextcloud deployment status debug: msg: | diff --git a/roles/nextcloud/tasks/optimization.yml b/roles/nextcloud/tasks/optimization.yml new file mode 100644 index 0000000..46d52b5 --- /dev/null +++ b/roles/nextcloud/tasks/optimization.yml @@ -0,0 +1,64 @@ +--- +# ================================================================= +# Nextcloud Database Optimization +# ================================================================= +# Rick-Infra - Nextcloud Role +# +# Performs database maintenance tasks to optimize performance +# and resolve setup warnings about missing indices and migrations + +- name: Add missing database indices + command: > + podman exec --user www-data nextcloud + php occ db:add-missing-indices + register: nextcloud_indices + changed_when: "'indices added' in nextcloud_indices.stdout or 'Check indices' in nextcloud_indices.stdout" + failed_when: + - nextcloud_indices.rc != 0 + - "'already exists' not in nextcloud_indices.stderr" + +- name: Convert filecache bigint columns + command: > + podman exec --user www-data nextcloud + php occ db:convert-filecache-bigint --no-interaction + register: nextcloud_bigint + changed_when: "'converted' in nextcloud_bigint.stdout" + failed_when: + - nextcloud_bigint.rc != 0 + - "'already' not in nextcloud_bigint.stdout" + timeout: 300 # 5 minutes for large databases + +- name: Update mimetype database mappings + command: > + podman exec --user www-data nextcloud + php occ maintenance:repair --include-expensive + register: nextcloud_repair + changed_when: "'updated' in nextcloud_repair.stdout or 'repaired' in nextcloud_repair.stdout" + failed_when: nextcloud_repair.rc != 0 + timeout: 600 # 10 minutes for expensive repairs + +- name: Configure maintenance window + command: > + podman exec --user www-data nextcloud + php occ config:system:set maintenance_window_start --value={{ nextcloud_maintenance_window_start }} --type=integer + register: nextcloud_maintenance_window + changed_when: "'set' in nextcloud_maintenance_window.stdout" + failed_when: nextcloud_maintenance_window.rc != 0 + +- name: Configure default phone region + command: > + podman exec --user www-data nextcloud + php occ config:system:set default_phone_region --value={{ nextcloud_default_phone_region }} + register: nextcloud_phone_region + changed_when: "'set' in nextcloud_phone_region.stdout" + failed_when: nextcloud_phone_region.rc != 0 + +- name: Display optimization results + debug: + msg: | + Database optimization complete: + - Indices: {{ 'Added' if 'indices added' in nextcloud_indices.stdout else 'Already optimized' }} + - BigInt: {{ 'Converted' if 'converted' in nextcloud_bigint.stdout else 'Already converted' }} + - Mimetypes: {{ 'Updated' if 'updated' in nextcloud_repair.stdout else 'Up to date' }} + - Maintenance window: {{ nextcloud_maintenance_window_start }}:00 UTC + - Phone region: {{ nextcloud_default_phone_region }} diff --git a/roles/nextcloud/templates/nextcloud.caddy.j2 b/roles/nextcloud/templates/nextcloud.caddy.j2 index 5fa267f..b2eff45 100644 --- a/roles/nextcloud/templates/nextcloud.caddy.j2 +++ b/roles/nextcloud/templates/nextcloud.caddy.j2 @@ -58,6 +58,10 @@ Referrer-Policy "no-referrer" # Disable FLoC tracking Permissions-Policy "interest-cohort=()" + # Robot indexing policy + X-Robots-Tag "noindex, nofollow" + # Cross-domain policy + X-Permitted-Cross-Domain-Policies "none" # Remove server header -Server } diff --git a/roles/nextcloud/templates/nextcloud.config.php.j2 b/roles/nextcloud/templates/nextcloud.config.php.j2 new file mode 100644 index 0000000..fde5f88 --- /dev/null +++ b/roles/nextcloud/templates/nextcloud.config.php.j2 @@ -0,0 +1,32 @@ + {{ nextcloud_maintenance_window_start }}, + + /** + * Default Phone Region + * + * Sets the default country code for phone number validation. + * Used when users enter phone numbers without country prefix. + * + * Format: ISO 3166-1 alpha-2 country code + */ + 'default_phone_region' => '{{ nextcloud_default_phone_region }}', +); diff --git a/roles/podman/defaults/main.yml b/roles/podman/defaults/main.yml index 286daed..52ba0e6 100644 --- a/roles/podman/defaults/main.yml +++ b/roles/podman/defaults/main.yml @@ -46,6 +46,10 @@ podman_registry_blocked: false podman_default_network: "bridge" podman_network_security: true +# Trusted container subnets (allowed through firewall) +podman_trusted_subnets: + - "10.88.0.0/16" + # ================================================================= # Storage Configuration # ================================================================= diff --git a/roles/podman/handlers/main.yml b/roles/podman/handlers/main.yml index 1534643..263870d 100644 --- a/roles/podman/handlers/main.yml +++ b/roles/podman/handlers/main.yml @@ -10,4 +10,9 @@ systemd: name: podman state: restarted - when: podman_service_enabled | default(true) \ No newline at end of file + when: podman_service_enabled | default(true) + +- name: reload nftables + systemd: + name: nftables + state: reloaded \ No newline at end of file diff --git a/roles/podman/tasks/main.yml b/roles/podman/tasks/main.yml index 96c7922..6f10484 100644 --- a/roles/podman/tasks/main.yml +++ b/roles/podman/tasks/main.yml @@ -42,6 +42,22 @@ backup: yes notify: restart podman +- name: Create default podman network with DNS enabled + command: podman network create podman --subnet 10.88.0.0/16 + register: podman_network_create + changed_when: "'podman' in podman_network_create.stdout" + failed_when: + - podman_network_create.rc != 0 + - "'already exists' not in podman_network_create.stderr" + +- name: Deploy podman firewall rules + template: + src: podman.nft.j2 + dest: /etc/nftables.d/10-podman.nft + mode: '0644' + backup: yes + notify: reload nftables + - name: Enable podman system service (if enabled) systemd: name: podman diff --git a/roles/podman/templates/podman.nft.j2 b/roles/podman/templates/podman.nft.j2 new file mode 100644 index 0000000..f93566d --- /dev/null +++ b/roles/podman/templates/podman.nft.j2 @@ -0,0 +1,32 @@ +#!/usr/sbin/nft -f + +# ================================================================= +# Podman Container Network Firewall Rules +# ================================================================= +# Rick-Infra Infrastructure - Podman Role +# Priority: 10 (loaded after base rules, before drop rules) +# +# Purpose: +# - Allow container-to-host communication for services (PostgreSQL, Valkey) +# - Allow container outbound traffic for DNS, package updates, etc. +# - Enable NAT/masquerading for container networks +# +# Security Model: +# - Containers are trusted (they run our own services) +# - All container egress traffic is allowed (simplified management) +# - Container ingress is controlled by application-specific port publishing +# +# Architecture: +# - Containers access host services via Unix sockets or host.containers.internal +# - Caddy reverse proxy handles all external traffic +# - No direct container port exposure to internet + +# Add rules to INPUT chain - Allow trusted container subnets +{% for subnet in podman_trusted_subnets %} +add rule inet filter input ip saddr {{ subnet }} accept comment "Podman containers: {{ subnet }}" +{% endfor %} + +# Add rules to FORWARD chain - Enable container forwarding +add rule inet filter forward ct state established,related accept comment "Allow established connections" +add rule inet filter forward iifname "podman0" accept comment "Allow outbound from podman bridge" +add rule inet filter forward oifname "podman0" ct state established,related accept comment "Allow inbound to podman bridge (established)"