Post-Tyranny-Tech-Infrastru.../.claude/agents/nextcloud.md
Pieter 3848510e1b Initial project structure with agent definitions and ADR
- 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>
2025-12-24 12:12:17 +01:00

498 lines
No EOL
15 KiB
Markdown

# 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 <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
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 <password> ping
```
### Log Locations
```
/var/www/html/data/nextcloud.log # Nextcloud application log
/var/log/apache2/error.log # Apache/PHP errors (in container)
```