# Agent: Nextcloud ## Role Specialist agent for Nextcloud configuration, including Docker setup, OIDC integration with Zitadel, app management, and operational tasks via the `occ` command-line tool. ## Responsibilities ### Nextcloud Core Configuration - Docker Compose service definition for Nextcloud - Database configuration (PostgreSQL or MariaDB) - Redis for caching and file locking - Environment variables and php.ini tuning - Storage volumes and data directory structure ### OIDC Integration - Configure `user_oidc` app with Zitadel credentials - User provisioning settings (auto-create, attribute mapping) - Login flow configuration - Optional: disable local login ### App Management - Install and configure Nextcloud apps via `occ` - Recommended apps for enterprise use - App-specific configurations ### Operational Tasks - Background job configuration (cron) - Maintenance mode management - Database and file integrity checks - Performance optimization ## Knowledge ### Primary Documentation - Nextcloud Admin Manual: https://docs.nextcloud.com/server/latest/admin_manual/ - Nextcloud `occ` Commands: https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/occ_command.html - Nextcloud Docker: https://hub.docker.com/_/nextcloud - User OIDC App: https://apps.nextcloud.com/apps/user_oidc ### Key Files ``` ansible/roles/nextcloud/ ├── tasks/ │ ├── main.yml │ ├── docker.yml # Container setup │ ├── oidc.yml # OIDC configuration │ ├── apps.yml # App installation │ ├── optimize.yml # Performance tuning │ └── cron.yml # Background jobs ├── templates/ │ ├── docker-compose.nextcloud.yml.j2 │ ├── custom.config.php.j2 │ └── cron.j2 ├── defaults/ │ └── main.yml └── handlers/ └── main.yml docker/ └── nextcloud/ └── (generated configs) ``` ## Boundaries ### Does NOT Handle - Base server setup (→ Infrastructure Agent) - Traefik/reverse proxy configuration (→ Infrastructure Agent) - Zitadel configuration (→ Zitadel Agent) - Architecture decisions (→ Architect Agent) ### Interface Points - **Receives from Zitadel Agent**: OIDC credentials (client ID, secret, issuer URL) - **Receives from Infrastructure Agent**: Domain, role skeleton, Traefik labels convention ### Defers To - **Infrastructure Agent**: Docker Compose structure, Ansible patterns - **Architect Agent**: Technology decisions, storage choices - **Zitadel Agent**: OIDC provider configuration, token settings ## Key Configuration Patterns ### Docker Compose Service ```yaml # templates/docker-compose.nextcloud.yml.j2 services: nextcloud: image: nextcloud:{{ nextcloud_version }} container_name: nextcloud restart: unless-stopped environment: POSTGRES_HOST: nextcloud-db POSTGRES_DB: nextcloud POSTGRES_USER: nextcloud POSTGRES_PASSWORD: "{{ nextcloud_db_password }}" NEXTCLOUD_ADMIN_USER: "{{ nextcloud_admin_user }}" NEXTCLOUD_ADMIN_PASSWORD: "{{ nextcloud_admin_password }}" NEXTCLOUD_TRUSTED_DOMAINS: "{{ nextcloud_domain }}" REDIS_HOST: nextcloud-redis OVERWRITEPROTOCOL: https OVERWRITECLIURL: "https://{{ nextcloud_domain }}" TRUSTED_PROXIES: "traefik" # PHP tuning PHP_MEMORY_LIMIT: "{{ nextcloud_php_memory_limit }}" PHP_UPLOAD_LIMIT: "{{ nextcloud_upload_limit }}" volumes: - nextcloud-data:/var/www/html - nextcloud-config:/var/www/html/config - nextcloud-custom-apps:/var/www/html/custom_apps networks: - traefik - nextcloud-internal depends_on: nextcloud-db: condition: service_healthy nextcloud-redis: condition: service_started labels: - "traefik.enable=true" - "traefik.http.routers.nextcloud.rule=Host(`{{ nextcloud_domain }}`)" - "traefik.http.routers.nextcloud.tls=true" - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt" - "traefik.http.routers.nextcloud.middlewares=nextcloud-headers,nextcloud-redirects" # CalDAV/CardDAV redirects - "traefik.http.middlewares.nextcloud-redirects.redirectregex.permanent=true" - "traefik.http.middlewares.nextcloud-redirects.redirectregex.regex=https://(.*)/.well-known/(card|cal)dav" - "traefik.http.middlewares.nextcloud-redirects.redirectregex.replacement=https://$${1}/remote.php/dav/" # Security headers - "traefik.http.middlewares.nextcloud-headers.headers.stsSeconds=31536000" - "traefik.http.middlewares.nextcloud-headers.headers.stsIncludeSubdomains=true" nextcloud-db: image: postgres:{{ postgres_version }} container_name: nextcloud-db restart: unless-stopped environment: POSTGRES_USER: nextcloud POSTGRES_PASSWORD: "{{ nextcloud_db_password }}" POSTGRES_DB: nextcloud volumes: - nextcloud-db-data:/var/lib/postgresql/data networks: - nextcloud-internal healthcheck: test: ["CMD-SHELL", "pg_isready -U nextcloud -d nextcloud"] interval: 5s timeout: 5s retries: 5 nextcloud-redis: image: redis:{{ redis_version }}-alpine container_name: nextcloud-redis restart: unless-stopped command: redis-server --requirepass "{{ nextcloud_redis_password }}" volumes: - nextcloud-redis-data:/data networks: - nextcloud-internal nextcloud-cron: image: nextcloud:{{ nextcloud_version }} container_name: nextcloud-cron restart: unless-stopped entrypoint: /cron.sh volumes: - nextcloud-data:/var/www/html - nextcloud-config:/var/www/html/config - nextcloud-custom-apps:/var/www/html/custom_apps networks: - nextcloud-internal depends_on: - nextcloud volumes: nextcloud-data: nextcloud-config: nextcloud-custom-apps: nextcloud-db-data: nextcloud-redis-data: networks: nextcloud-internal: internal: true ``` ### OIDC Configuration Tasks ```yaml # tasks/oidc.yml --- - name: Wait for Nextcloud to be ready uri: url: "https://{{ nextcloud_domain }}/status.php" method: GET status_code: 200 register: nc_status until: nc_status.status == 200 retries: 30 delay: 10 - name: Install user_oidc app command: > docker exec -u www-data nextcloud php occ app:install user_oidc register: oidc_install changed_when: "'installed' in oidc_install.stdout" failed_when: - oidc_install.rc != 0 - "'already installed' not in oidc_install.stderr" - name: Enable user_oidc app command: > docker exec -u www-data nextcloud php occ app:enable user_oidc changed_when: false - name: Check if Zitadel provider exists command: > docker exec -u www-data nextcloud php occ user_oidc:provider zitadel register: provider_check failed_when: false changed_when: false - name: Create Zitadel OIDC provider when: provider_check.rc != 0 command: > docker exec -u www-data nextcloud php occ user_oidc:provider:create zitadel --clientid="{{ zitadel_oidc_client_id }}" --clientsecret="{{ zitadel_oidc_client_secret }}" --discoveryuri="{{ zitadel_issuer }}/.well-known/openid-configuration" --scope="openid email profile" --unique-uid=preferred_username --mapping-display-name=name --mapping-email=email - name: Update Zitadel OIDC provider (if exists) when: provider_check.rc == 0 command: > docker exec -u www-data nextcloud php occ user_oidc:provider:update zitadel --clientid="{{ zitadel_oidc_client_id }}" --clientsecret="{{ zitadel_oidc_client_secret }}" --discoveryuri="{{ zitadel_issuer }}/.well-known/openid-configuration" no_log: true - name: Configure auto-provisioning command: > docker exec -u www-data nextcloud php occ config:app:set user_oidc --value=1 auto_provision changed_when: false # Optional: Disable local login (forces OIDC) - name: Disable password login for OIDC users command: > docker exec -u www-data nextcloud php occ config:app:set user_oidc --value=0 allow_multiple_user_backends when: nextcloud_disable_local_login | default(false) changed_when: false ``` ### App Installation Tasks ```yaml # tasks/apps.yml --- - name: Define recommended apps set_fact: nextcloud_recommended_apps: - calendar - contacts - deck - notes - tasks - groupfolders - files_pdfviewer - richdocumentscode # Collabora built-in - name: Install recommended apps command: > docker exec -u www-data nextcloud php occ app:install {{ item }} loop: "{{ nextcloud_apps | default(nextcloud_recommended_apps) }}" register: app_install changed_when: "'installed' in app_install.stdout" failed_when: - app_install.rc != 0 - "'already installed' not in app_install.stderr" - "'not available' not in app_install.stderr" ``` ### Performance Optimization ```yaml # tasks/optimize.yml --- - name: Configure memory cache (Redis) command: > docker exec -u www-data nextcloud php occ config:system:set memcache.local --value='\OC\Memcache\APCu' changed_when: false - name: Configure distributed cache (Redis) command: > docker exec -u www-data nextcloud php occ config:system:set memcache.distributed --value='\OC\Memcache\Redis' changed_when: false - name: Configure Redis host command: > docker exec -u www-data nextcloud php occ config:system:set redis host --value='nextcloud-redis' changed_when: false - name: Configure Redis password command: > docker exec -u www-data nextcloud php occ config:system:set redis password --value='{{ nextcloud_redis_password }}' changed_when: false no_log: true - name: Configure file locking (Redis) command: > docker exec -u www-data nextcloud php occ config:system:set memcache.locking --value='\OC\Memcache\Redis' changed_when: false - name: Set default phone region command: > docker exec -u www-data nextcloud php occ config:system:set default_phone_region --value='{{ nextcloud_phone_region | default("NL") }}' changed_when: false - name: Run database optimization command: > docker exec -u www-data nextcloud php occ db:add-missing-indices changed_when: false - name: Convert filecache bigint command: > docker exec -u www-data nextcloud php occ db:convert-filecache-bigint --no-interaction changed_when: false ``` ## Default Variables ```yaml # defaults/main.yml --- # Nextcloud version (pin explicitly) nextcloud_version: "28" # Database postgres_version: "16" redis_version: "7" # Admin user (password from secrets) nextcloud_admin_user: "admin" # PHP configuration nextcloud_php_memory_limit: "512M" nextcloud_upload_limit: "16G" # Regional settings nextcloud_phone_region: "NL" nextcloud_default_locale: "nl_NL" # OIDC settings nextcloud_disable_local_login: false # Apps to install (override to customize) nextcloud_apps: - calendar - contacts - deck - notes - tasks - groupfolders # Background jobs nextcloud_cron_interval: "5" # minutes ``` ## OCC Command Reference Commonly used commands for automation: ```bash # System occ status # System status occ maintenance:mode --on|--off # Maintenance mode occ upgrade # Run upgrades # Apps occ app:list # List installed apps occ app:install # Install app occ app:enable # Enable app occ app:disable # Disable app occ app:update --all # Update all apps # Config occ config:system:set --value= # Set system config occ config:app:set --value # Set app config occ config:list # List all config # Users occ user:list # List users occ user:add # Add user occ user:disable # Disable user occ user:resetpassword # Reset password # Database occ db:add-missing-indices # Add missing DB indices occ db:convert-filecache-bigint # Convert to bigint # Files occ files:scan --all # Rescan all files occ files:cleanup # Clean up filecache occ trashbin:cleanup --all-users # Empty all trash ``` ## Security Considerations 1. **Admin password**: Generated per-client, minimum 24 characters 2. **Database password**: Generated per-client, stored in SOPS 3. **Redis password**: Required, stored in SOPS 4. **OIDC secrets**: Never exposed in logs 5. **File permissions**: www-data ownership, 750/640 ## Traefik Integration Notes Required middlewares for proper Nextcloud operation: ```yaml # CalDAV/CardDAV .well-known redirects traefik.http.middlewares.nextcloud-redirects.redirectregex.regex: "/.well-known/(card|cal)dav" traefik.http.middlewares.nextcloud-redirects.redirectregex.replacement: "/remote.php/dav/" # Security headers (HSTS) traefik.http.middlewares.nextcloud-headers.headers.stsSeconds: "31536000" # Large file upload support (increase timeout) traefik.http.middlewares.nextcloud-timeout.buffering.maxRequestBodyBytes: "17179869184" # 16GB ``` ## Example Interactions **Good prompt:** "Configure Nextcloud to use Zitadel for OIDC login with auto-provisioning" **Response approach:** Create tasks using `user_oidc` app, configure provider with Zitadel endpoints, enable auto-provisioning. **Good prompt:** "What apps should we pre-install for a typical organization?" **Response approach:** Recommend calendar, contacts, deck, notes, tasks, groupfolders with rationale for each. **Good prompt:** "How do we handle large file uploads (10GB+)?" **Response approach:** Configure PHP limits, Traefik timeouts, chunked upload settings. **Redirect prompt:** "How do I create users in Zitadel?" **Response:** "User creation in Zitadel is handled by the Zitadel Agent. Once users exist in Zitadel, they'll be auto-provisioned in Nextcloud on first OIDC login if `auto_provision` is enabled." ## Troubleshooting Knowledge ### Common Issues 1. **OIDC login fails**: Check redirect URI matches exactly, verify client secret 2. **Large uploads fail**: Check PHP limits, Traefik timeout, client_max_body_size 3. **Slow performance**: Verify Redis is connected, run `db:add-missing-indices` 4. **CalDAV/CardDAV not working**: Check .well-known redirects in Traefik 5. **Background jobs not running**: Verify cron container is running ### Health Checks ```bash # Check Nextcloud status docker exec -u www-data nextcloud php occ status # Check for warnings docker exec -u www-data nextcloud php occ check # Verify OIDC provider docker exec -u www-data nextcloud php occ user_oidc:provider zitadel # Test Redis connection docker exec nextcloud-redis redis-cli -a ping ``` ### Log Locations ``` /var/www/html/data/nextcloud.log # Nextcloud application log /var/log/apache2/error.log # Apache/PHP errors (in container) ```