- Add AI agent definitions (Architect, Infrastructure, Zitadel, Nextcloud) - Add Architecture Decision Record with complete design rationale - Add .gitignore to protect secrets and sensitive files - Add README with quick start guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
15 KiB
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_oidcapp 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
occCommands: 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
# 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
# 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
# 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
# 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
# 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:
# 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 <app> # Install app
occ app:enable <app> # Enable app
occ app:disable <app> # Disable app
occ app:update --all # Update all apps
# Config
occ config:system:set <key> --value=<v> # Set system config
occ config:app:set <app> <key> --value # Set app config
occ config:list # List all config
# Users
occ user:list # List users
occ user:add <uid> # Add user
occ user:disable <uid> # Disable user
occ user:resetpassword <uid> # 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
- Admin password: Generated per-client, minimum 24 characters
- Database password: Generated per-client, stored in SOPS
- Redis password: Required, stored in SOPS
- OIDC secrets: Never exposed in logs
- File permissions: www-data ownership, 750/640
Traefik Integration Notes
Required middlewares for proper Nextcloud operation:
# 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
- OIDC login fails: Check redirect URI matches exactly, verify client secret
- Large uploads fail: Check PHP limits, Traefik timeout, client_max_body_size
- Slow performance: Verify Redis is connected, run
db:add-missing-indices - CalDAV/CardDAV not working: Check .well-known redirects in Traefik
- Background jobs not running: Verify cron container is running
Health Checks
# 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 <password> ping
Log Locations
/var/www/html/data/nextcloud.log # Nextcloud application log
/var/log/apache2/error.log # Apache/PHP errors (in container)