From 89b43180fc36a7537372372d36670b7d0327f733 Mon Sep 17 00:00:00 2001 From: Joakim Date: Sun, 21 Dec 2025 14:54:44 +0100 Subject: [PATCH] Refactor Nextcloud configuration to use OCC script approach and add email/OIDC support Major architectural changes: - Replace config file templating with unified OCC command script - Remove custom_apps mount overlay that caused Caddy serving issues - Implement script-based configuration for idempotency and clarity Configuration improvements: - Add email/SMTP support with master switch (nextcloud_email_enabled) - Add OIDC/SSO integration with Authentik support - Add apps installation (user_oidc, calendar, contacts) - Enable group provisioning and quota management from OIDC - Set nextcloud_oidc_unique_uid to false per Authentik docs Files removed: - nextcloud.config.php.j2 (replaced by OCC commands) - redis.config.php.j2 (replaced by OCC commands) - optimization.yml (merged into configure.yml) Files added: - configure-nextcloud.sh.j2 (single source of truth for config) - configure.yml (deploys and runs configuration script) Documentation: - Add comprehensive OIDC setup guide with Authentik integration - Document custom scope mapping and group provisioning - Add email configuration examples for common providers - Update vault variables documentation - Explain two-phase deployment approach Host configuration: - Change admin user from 'admin' to 'joakim' - Add admin email configuration --- host_vars/arch-vps/main.yml | 3 +- playbooks/remove-nextcloud.yml | 9 - roles/nextcloud/README.md | 458 +++++++++++++++++- roles/nextcloud/VAULT_VARIABLES.md | 174 +++++-- roles/nextcloud/defaults/main.yml | 71 ++- roles/nextcloud/tasks/configure.yml | 36 ++ roles/nextcloud/tasks/main.yml | 27 +- roles/nextcloud/tasks/optimization.yml | 64 --- .../templates/configure-nextcloud.sh.j2 | 189 ++++++++ .../templates/nextcloud.config.php.j2 | 32 -- roles/nextcloud/templates/nextcloud.container | 3 - roles/nextcloud/templates/redis.config.php.j2 | 34 -- 12 files changed, 887 insertions(+), 213 deletions(-) create mode 100644 roles/nextcloud/tasks/configure.yml delete mode 100644 roles/nextcloud/tasks/optimization.yml create mode 100644 roles/nextcloud/templates/configure-nextcloud.sh.j2 delete mode 100644 roles/nextcloud/templates/nextcloud.config.php.j2 delete mode 100644 roles/nextcloud/templates/redis.config.php.j2 diff --git a/host_vars/arch-vps/main.yml b/host_vars/arch-vps/main.yml index 49a0c51..3174d01 100644 --- a/host_vars/arch-vps/main.yml +++ b/host_vars/arch-vps/main.yml @@ -65,7 +65,8 @@ nextcloud_db_password: "{{ vault_nextcloud_db_password }}" nextcloud_valkey_db: 2 # Authentik uses 1 # Admin configuration -nextcloud_admin_user: "admin" +nextcloud_admin_user: "joakim" +nextcloud_admin_email: "joakim@jnss.me" nextcloud_admin_password: "{{ vault_nextcloud_admin_password }}" # Service configuration diff --git a/playbooks/remove-nextcloud.yml b/playbooks/remove-nextcloud.yml index b5e457c..fbc0d5f 100644 --- a/playbooks/remove-nextcloud.yml +++ b/playbooks/remove-nextcloud.yml @@ -63,15 +63,6 @@ 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 # ============================================ diff --git a/roles/nextcloud/README.md b/roles/nextcloud/README.md index b111423..7f2a918 100644 --- a/roles/nextcloud/README.md +++ b/roles/nextcloud/README.md @@ -52,9 +52,17 @@ See `defaults/main.yml` for all configurable variables. Define these in your `host_vars/` with `ansible-vault`: ```yaml +# Core credentials (required) vault_nextcloud_db_password: "secure-database-password" vault_nextcloud_admin_password: "secure-admin-password" vault_valkey_password: "secure-valkey-password" + +# Email credentials (optional - only if email enabled) +vault_nextcloud_smtp_password: "secure-smtp-password" + +# OIDC credentials (optional - only if OIDC enabled) +vault_nextcloud_oidc_client_id: "nextcloud-client-id-from-authentik" +vault_nextcloud_oidc_client_secret: "nextcloud-client-secret-from-authentik" ``` ### Key Variables @@ -91,27 +99,449 @@ This role uses a **two-phase deployment** approach to work correctly with the Ne 6. Container runs `occ maintenance:install` with PostgreSQL 7. Installation creates `config.php` with database credentials -### Phase 2: Custom Configuration (automatic) +### Phase 2: Configuration via OCC Script (automatic) 8. Ansible waits for `occ status` to report `installed: true` -9. Ansible deploys custom `redis.config.php` (overwrites default) -10. Container restart applies custom configuration +9. Ansible deploys and runs configuration script inside container +10. Script configures system settings via OCC commands: + - Redis caching (without sessions) + - Maintenance window and phone region + - Database optimizations (indices, bigint, mimetypes) **Why this order?** -The Nextcloud container's entrypoint uses `version.php` as a marker to determine if installation is needed. If you deploy any files into `/opt/nextcloud/config/` before the container starts, the initialization process fails: +The Nextcloud container's entrypoint uses `version.php` as a marker to determine if installation is needed. We must wait for the container's auto-installation to complete before running configuration commands: -- Container copies files including `version.php` -- Entrypoint sees `version.php` exists → assumes already installed -- Skips running `occ maintenance:install` -- Result: Empty `config.php`, 503 errors +- Container must complete first-time setup (copy files, run `occ maintenance:install`) +- OCC commands require a fully initialized Nextcloud installation +- Running configuration after installation avoids conflicts with the entrypoint script -By deploying custom configs **after** installation completes, we: -- ✅ Allow the container's auto-installation to run properly -- ✅ Override specific configs (like Redis) after the fact -- ✅ Maintain idempotency (subsequent runs just update configs) +**Configuration Method:** + +This role uses **OCC commands via a script** rather than config files because: +- ✅ **Explicit and verifiable** - Run `occ config:list system` to see exact state +- ✅ **No file conflicts** - Avoids issues with Docker image's built-in config files +- ✅ **Fully idempotent** - Safe to re-run during updates +- ✅ **Single source of truth** - All configuration in one script template See the official [Nextcloud Docker documentation](https://github.com/nextcloud/docker#auto-configuration-via-environment-variables) for more details on the auto-configuration process. +## Installed Apps + +This role automatically installs and enables the following apps: + +- **user_oidc** - OpenID Connect authentication backend for SSO integration +- **calendar** - Calendar and scheduling application (CalDAV) +- **contacts** - Contact management application (CardDAV) + +To customize the app list, override these variables in your `host_vars`: + +```yaml +nextcloud_apps_install: + - user_oidc + - calendar + - contacts + - tasks # Add more apps as needed + - deck + - mail + +``` + +## OIDC/SSO Integration + +### Prerequisites + +Before enabling OIDC, you must create an OIDC application/provider in your identity provider (e.g., Authentik): + +**For Authentik:** + +1. Navigate to **Applications → Providers** +2. Click **Create** → **OAuth2/OpenID Provider** +3. Configure: + - **Name**: `Nextcloud` + - **Authorization flow**: `default-authentication-flow` (or your preferred flow) + - **Client type**: `Confidential` + - **Client ID**: Generate or specify (save this) + - **Client Secret**: Generate or specify (save this) + - **Redirect URIs**: `https://cloud.jnss.me/apps/user_oidc/code` + - **Signing Key**: Select your signing certificate + - **Scopes**: Add `openid`, `profile`, `email` + +4. Create **Application**: + - Navigate to **Applications → Applications** + - Click **Create** + - **Name**: `Nextcloud` + - **Slug**: `nextcloud` + - **Provider**: Select the provider created above + - **Launch URL**: `https://cloud.jnss.me` + +5. Note the **Discovery URL**: `https://auth.jnss.me/application/o/nextcloud/.well-known/openid-configuration` + +### Configuration + +Enable OIDC in your `host_vars/arch-vps/main.yml`: + +```yaml +# OIDC Configuration +nextcloud_oidc_enabled: true +nextcloud_oidc_provider_id: "authentik" # Provider identifier (slug) +nextcloud_oidc_provider_name: "Authentik SSO" # Display name on login button +nextcloud_oidc_discovery_url: "https://auth.jnss.me/application/o/nextcloud/.well-known/openid-configuration" + +# Security settings (recommended defaults) +nextcloud_oidc_unique_uid: true # Prevents account takeover between providers +nextcloud_oidc_check_bearer: false +nextcloud_oidc_send_id_token_hint: true + +# Attribute mappings (defaults work for most providers) +nextcloud_oidc_mapping_display_name: "name" +nextcloud_oidc_mapping_email: "email" +nextcloud_oidc_mapping_uid: "preferred_username" # Or "sub" for UUID + +# Optional: Enable single login (auto-redirect to SSO) +nextcloud_oidc_single_login: false # Set to true to force SSO login +``` + +Add credentials to your vault file `host_vars/arch-vps/vault.yml`: + +```yaml +vault_nextcloud_oidc_client_id: "nextcloud-client-id-from-authentik" +vault_nextcloud_oidc_client_secret: "nextcloud-client-secret-from-authentik" +``` + +### OIDC Scopes + +The following scopes are requested from your OIDC provider by default: + +```yaml +nextcloud_oidc_scope: "email profile nextcloud openid" +``` + +**Standard scopes:** +- `openid` - Required for OpenID Connect (contains no claims itself) +- `email` - User's email address (`email` and `email_verified` claims) +- `profile` - User's profile information (`name`, `given_name`, `preferred_username`, `picture`, etc.) + +**Custom scope for Authentik:** +- `nextcloud` - Custom scope mapping you create in Authentik (contains `groups`, `quota`, `user_id`) + +#### Creating the Nextcloud Scope Mapping in Authentik + +The `nextcloud` scope must be created as a custom property mapping in Authentik: + +1. Log in to Authentik as administrator +2. Navigate to **Customization** → **Property mappings** → **Create** +3. Select type: **Scope mapping** +4. Configure: + - **Name**: `Nextcloud Profile` + - **Scope name**: `nextcloud` + - **Expression**: + +```python +# Extract all groups the user is a member of +groups = [group.name for group in user.ak_groups.all()] + +# In Nextcloud, administrators must be members of a fixed group called "admin" +# If a user is an admin in authentik, ensure that "admin" is appended to their group list +if user.is_superuser and "admin" not in groups: + groups.append("admin") + +return { + "name": request.user.name, + "groups": groups, + # Set a quota by using the "nextcloud_quota" property in the user's attributes + "quota": user.group_attributes().get("nextcloud_quota", None), + # To connect an existing Nextcloud user, set "nextcloud_user_id" to the Nextcloud username + "user_id": user.attributes.get("nextcloud_user_id", str(user.uuid)), +} +``` + +5. Click **Finish** +6. Navigate to your Nextcloud provider → **Advanced protocol settings** +7. Add `Nextcloud Profile` to **Scopes** (in addition to the default scopes) + +### Group Provisioning and Synchronization + +Automatically sync user group membership from Authentik to Nextcloud. + +**Default configuration:** + +```yaml +nextcloud_oidc_group_provisioning: true # Auto-create groups from Authentik +nextcloud_oidc_mapping_groups: "groups" # Claim containing group list +``` + +**How it works:** + +1. User logs in via OIDC +2. Authentik sends group membership in the `groups` claim (from the custom scope) +3. Nextcloud automatically: + - Creates groups that don't exist in Nextcloud + - Adds user to those groups + - Removes user from groups they're no longer member of in Authentik + +**Example: Making a user an admin** + +Nextcloud requires admins to be in a group literally named `admin`. The custom scope mapping (above) automatically adds `"admin"` to the groups list for Authentik superusers. + +Alternatively, manually create a group in Authentik called `admin` and add users to it. + +**Quota management:** + +Set storage quotas by adding the `nextcloud_quota` attribute to Authentik groups or users: + +1. In Authentik, navigate to **Directory** → **Groups** → select your group +2. Under **Attributes**, add: + ```json + { + "nextcloud_quota": "15 GB" + } + ``` +3. Users in this group will have a 15 GB quota in Nextcloud +4. If not set, quota is unlimited + +### Complete Authentik Setup Guide + +Follow these steps to set up OIDC authentication with Authentik: + +**Step 1: Create the Custom Scope Mapping** + +See [Creating the Nextcloud Scope Mapping in Authentik](#creating-the-nextcloud-scope-mapping-in-authentik) above. + +**Step 2: Create the OAuth2/OpenID Provider** + +1. In Authentik, navigate to **Applications** → **Providers** +2. Click **Create** → **OAuth2/OpenID Provider** +3. Configure: + - **Name**: `Nextcloud` + - **Authorization flow**: `default-authentication-flow` (or your preferred flow) + - **Client type**: `Confidential` + - **Client ID**: Generate or specify (save this for later) + - **Client Secret**: Generate or specify (save this for later) + - **Redirect URIs**: `https://cloud.jnss.me/apps/user_oidc/code` + - **Signing Key**: Select your signing certificate + - Under **Advanced protocol settings**: + - **Scopes**: Add `openid`, `email`, `profile`, and `Nextcloud Profile` (the custom scope created in Step 1) + - **Subject mode**: `Based on the User's UUID` (or `Based on the User's username` if you prefer usernames) + +**Step 3: Create the Application** + +1. Navigate to **Applications** → **Applications** +2. Click **Create** +3. Configure: + - **Name**: `Nextcloud` + - **Slug**: `nextcloud` + - **Provider**: Select the provider created in Step 2 + - **Launch URL**: `https://cloud.jnss.me` (optional) + +**Step 4: Note the Discovery URL** + +The discovery URL follows this pattern: +``` +https://auth.jnss.me/application/o//.well-known/openid-configuration +``` + +For the application slug `nextcloud`, it will be: +``` +https://auth.jnss.me/application/o/nextcloud/.well-known/openid-configuration +``` + +**Step 5: Configure Nextcloud Role Variables** + +In your `host_vars/arch-vps/main.yml`: + +```yaml +nextcloud_oidc_enabled: true +nextcloud_oidc_provider_id: "authentik" +nextcloud_oidc_provider_name: "Authentik" +nextcloud_oidc_discovery_url: "https://auth.jnss.me/application/o/nextcloud/.well-known/openid-configuration" +nextcloud_oidc_scope: "email profile nextcloud openid" +nextcloud_oidc_mapping_uid: "preferred_username" # Or "sub" for UUID-based IDs +nextcloud_oidc_mapping_display_name: "name" +nextcloud_oidc_mapping_email: "email" +nextcloud_oidc_mapping_groups: "groups" +nextcloud_oidc_mapping_quota: "quota" +nextcloud_oidc_group_provisioning: true +``` + +In your `host_vars/arch-vps/vault.yml`: + +```yaml +vault_nextcloud_oidc_client_id: "nextcloud" # Client ID from Authentik +vault_nextcloud_oidc_client_secret: "very-long-secret-from-authentik" # Client Secret from Authentik +``` + +**Step 6: Deploy and Test** + +Run the Nextcloud playbook: +```bash +ansible-playbook -i inventory/hosts.yml site.yml --tags nextcloud --ask-vault-pass +``` + +### Supported OIDC Providers + +The `user_oidc` app supports any **OpenID Connect 1.0** compliant provider: + +- **Authentik** (recommended for self-hosted) +- **Keycloak** +- **Auth0** +- **Okta** +- **Azure AD / Microsoft Entra ID** +- **Google Identity Platform** +- **GitHub** (via OIDC) +- **GitLab** +- **Authelia** +- **Kanidm** +- Any other OIDC 1.0 compliant provider + +The `nextcloud_oidc_provider_id` is just an identifier slug - you can use any value like `authentik`, `keycloak`, `auth0`, `mycompany-sso`, etc. + +### Verification + +After deployment: + +1. **Check provider configuration:** + ```bash + podman exec --user www-data nextcloud php occ user_oidc:provider + podman exec --user www-data nextcloud php occ user_oidc:provider authentik + ``` + +2. **Test login:** + - Visit `https://cloud.jnss.me` + - You should see a "Log in with Authentik SSO" button + - Click it to test SSO flow + - User account should be auto-created on first login + +3. **Check user mapping:** + ```bash + podman exec --user www-data nextcloud php occ user:list + ``` + +### Troubleshooting OIDC + +**Login button doesn't appear:** +```bash +# Check if user_oidc app is enabled +podman exec --user www-data nextcloud php occ app:list | grep user_oidc + +# Enable if needed +podman exec --user www-data nextcloud php occ app:enable user_oidc +``` + +**Discovery URL errors:** +```bash +# Test discovery URL is accessible from container +podman exec nextcloud curl -k https://auth.jnss.me/application/o/nextcloud/.well-known/openid-configuration +``` + +**JWKS cache issues:** +```bash +# Clear JWKS cache +podman exec --user www-data nextcloud php occ user_oidc:provider authentik \ + --clientid='your-client-id' +``` + +## Email Configuration + +Configure Nextcloud to send emails for password resets, notifications, and sharing. + +### Configuration + +Enable email in your `host_vars/arch-vps/main.yml`: + +```yaml +# Email Configuration +nextcloud_email_enabled: true +nextcloud_smtp_host: "smtp.fastmail.com" +nextcloud_smtp_port: 587 +nextcloud_smtp_secure: "tls" # tls, ssl, or empty +nextcloud_smtp_username: "nextcloud@jnss.me" +nextcloud_mail_from_address: "nextcloud" +nextcloud_mail_domain: "jnss.me" + +# Set admin user's email address +nextcloud_admin_email: "admin@jnss.me" +``` + +Add SMTP password to vault `host_vars/arch-vps/vault.yml`: + +```yaml +vault_nextcloud_smtp_password: "your-smtp-app-password" +``` + +### Common SMTP Providers + +**Fastmail:** +```yaml +nextcloud_smtp_host: "smtp.fastmail.com" +nextcloud_smtp_port: 587 +nextcloud_smtp_secure: "tls" +``` + +**Gmail (App Password required):** +```yaml +nextcloud_smtp_host: "smtp.gmail.com" +nextcloud_smtp_port: 587 +nextcloud_smtp_secure: "tls" +``` + +**Office 365:** +```yaml +nextcloud_smtp_host: "smtp.office365.com" +nextcloud_smtp_port: 587 +nextcloud_smtp_secure: "tls" +``` + +**SMTP2GO:** +```yaml +nextcloud_smtp_host: "mail.smtp2go.com" +nextcloud_smtp_port: 587 +nextcloud_smtp_secure: "tls" +``` + +### Verification + +After deployment: + +1. **Check SMTP configuration:** + ```bash + podman exec --user www-data nextcloud php occ config:list system | grep mail + ``` + +2. **Check admin email:** + ```bash + podman exec --user www-data nextcloud php occ user:setting admin settings email + ``` + +3. **Send test email via Web UI:** + - Log in as admin + - Settings → Administration → Basic settings + - Scroll to "Email server" + - Click "Send email" button + - Check recipient inbox + +### Troubleshooting Email + +**Test SMTP connection from container:** +```bash +# Install swaks if needed (for testing) +podman exec nextcloud apk add --no-cache swaks + +# Test SMTP connection +podman exec nextcloud swaks \ + --to recipient@example.com \ + --from nextcloud@jnss.me \ + --server smtp.fastmail.com:587 \ + --auth LOGIN \ + --auth-user nextcloud@jnss.me \ + --auth-password 'your-password' \ + --tls +``` + +**Check Nextcloud logs:** +```bash +podman exec --user www-data nextcloud php occ log:watch +``` + ## Usage ### Include in Playbook @@ -250,7 +680,7 @@ This role uses a **split caching strategy** for optimal performance and stabilit - `memcache.local`: APCu (in-memory opcode cache) - `memcache.distributed`: Redis (shared cache, file locking) - `memcache.locking`: Redis (transactional file locking) -- Configuration: Via custom `redis.config.php` template +- Configuration: Via OCC commands in configuration script **Why not Redis sessions?** @@ -262,7 +692,7 @@ The official Nextcloud Docker image enables Redis session handling when `REDIS_H 4. **Worker exhaustion**: Limited FPM workers (default 5) all become blocked 5. **Cascading failure**: New requests queue, timeouts accumulate, locks orphan -This role disables Redis sessions by **not setting** `REDIS_HOST` in the environment, while still providing Redis caching via a custom `redis.config.php` that is deployed independently. +This role disables Redis sessions by **not setting** `REDIS_HOST` in the environment, while still providing Redis caching via OCC configuration commands. **If you need Redis sessions** (e.g., multi-server setup with session sharing), you must: 1. Enable `REDIS_HOST` in `nextcloud.env.j2` diff --git a/roles/nextcloud/VAULT_VARIABLES.md b/roles/nextcloud/VAULT_VARIABLES.md index d4ca080..47a87af 100644 --- a/roles/nextcloud/VAULT_VARIABLES.md +++ b/roles/nextcloud/VAULT_VARIABLES.md @@ -1,22 +1,104 @@ -# Nextcloud Role - Required Vault Variables +# Nextcloud Role - Vault Variables -This role requires the following encrypted variables to be defined in your vault file (typically `host_vars//vault.yml`). +This document describes all vault-encrypted variables used by the Nextcloud role. ## Required Variables -Add these to your encrypted vault file: +These variables **must** be defined in your vault file for the role to function: ```yaml -# Nextcloud database password +# ================================================================= +# Core Credentials (REQUIRED) +# ================================================================= + +# PostgreSQL database password for Nextcloud user vault_nextcloud_db_password: "CHANGE_ME_secure_database_password" -# Nextcloud admin account password +# Nextcloud admin user password vault_nextcloud_admin_password: "CHANGE_ME_secure_admin_password" -# Valkey/Redis password (shared infrastructure) +# Valkey (Redis) password for caching (shared infrastructure) vault_valkey_password: "CHANGE_ME_secure_valkey_password" ``` +## Optional Variables + +These variables are only required if you enable the corresponding features: + +### Email/SMTP Configuration + +Only required if `nextcloud_email_enabled: true`: + +```yaml +# ================================================================= +# Email/SMTP Credentials (OPTIONAL) +# ================================================================= + +# SMTP server password for sending emails +# Used with nextcloud_smtp_username for authentication +vault_nextcloud_smtp_password: "your-smtp-password-or-app-password" +``` + +**Example for Gmail:** +- Use an [App Password](https://support.google.com/accounts/answer/185833) +- Do NOT use your main Google account password + +**Example for Fastmail:** +- Use an [App Password](https://www.fastmail.help/hc/en-us/articles/360058752854) + +### OIDC/SSO Configuration + +Only required if `nextcloud_oidc_enabled: true`: + +```yaml +# ================================================================= +# OIDC/SSO Credentials (OPTIONAL) +# ================================================================= + +# OAuth2/OIDC Client ID from your identity provider +vault_nextcloud_oidc_client_id: "nextcloud" + +# OAuth2/OIDC Client Secret from your identity provider +# IMPORTANT: Keep this secret! Anyone with this can impersonate your app +vault_nextcloud_oidc_client_secret: "very-long-random-secret-from-authentik" +``` + +## Complete Vault File Example + +Here's a complete example of a vault file with all possible variables: + +```yaml +--- +# ================================================================= +# Example Vault File +# ================================================================= +# File: host_vars/arch-vps/vault.yml +# Encrypted with: ansible-vault encrypt host_vars/arch-vps/vault.yml + +# Caddy TLS +vault_caddy_tls_email: "admin@jnss.me" +vault_cloudflare_api_token: "your-cloudflare-token" + +# Authentik +vault_authentik_db_password: "authentik-db-password" +vault_authentik_secret_key: "authentik-secret-key" +vault_authentik_admin_password: "authentik-admin-password" + +# Valkey (shared infrastructure) +vault_valkey_password: "V4lk3y!P@ssw0rd#R3d1s" + +# Nextcloud - Core (always required) +vault_nextcloud_db_password: "XkN8vQ2mP9wR5tY7uI0oP3sA6dF8gH1j" +vault_nextcloud_admin_password: "AdminP@ssw0rd!SecureAndL0ng" + +# Nextcloud - Email (optional) +vault_nextcloud_smtp_password: "fastmail-app-password-xyz123" + +# Nextcloud - OIDC (optional) +vault_nextcloud_oidc_client_id: "nextcloud" +vault_nextcloud_oidc_client_secret: "aksk_authentik_secret_very_long_random_string" +``` + ## Creating/Editing Vault File ### First Time Setup @@ -37,6 +119,13 @@ ansible-vault edit host_vars/arch-vps/vault.yml # Add the Nextcloud variables, then save and exit ``` +### View Vault Contents + +```bash +# View vault file contents +ansible-vault view host_vars/arch-vps/vault.yml +``` + ### Password Generation Generate secure passwords: @@ -49,39 +138,26 @@ openssl rand -base64 32 pwgen -s 32 1 ``` -## Example Vault File +## Running Playbooks with Vault -Your `host_vars/arch-vps/vault.yml` should include: - -```yaml ---- -# Caddy TLS -vault_caddy_tls_email: "admin@jnss.me" -vault_cloudflare_api_token: "your-cloudflare-token" - -# Authentik -vault_authentik_db_password: "authentik-db-password" -vault_authentik_secret_key: "authentik-secret-key" -vault_authentik_admin_password: "authentik-admin-password" - -# Nextcloud (ADD THESE) -vault_nextcloud_db_password: "generated-password-1" -vault_nextcloud_admin_password: "generated-password-2" - -# Valkey (shared infrastructure) -vault_valkey_password: "valkey-password" -``` - -## Deployment - -When deploying, you'll need to provide the vault password: +### Interactive Password Prompt ```bash -# Deploy with vault password prompt -ansible-playbook -i inventory/hosts.yml site.yml --tags nextcloud --ask-vault-pass +ansible-playbook -i inventory/hosts.yml site.yml --ask-vault-pass +``` -# Or use a password file -ansible-playbook -i inventory/hosts.yml site.yml --tags nextcloud --vault-password-file ~/.vault_pass +### Using a Password File + +```bash +# Create password file (DO NOT COMMIT THIS!) +echo 'your-vault-password' > .vault_pass +chmod 600 .vault_pass + +# Add to .gitignore +echo '.vault_pass' >> .gitignore + +# Run playbook +ansible-playbook -i inventory/hosts.yml site.yml --vault-password-file .vault_pass ``` ## Security Notes @@ -92,6 +168,29 @@ ansible-playbook -i inventory/hosts.yml site.yml --tags nextcloud --vault-passwo - Store vault password securely (password manager, encrypted file, etc.) - Consider using `ansible-vault rekey` to change vault password periodically +## Troubleshooting + +### "Vault password incorrect" + +**Problem:** Wrong vault password entered + +**Solution:** Verify you're using the correct vault password + +### "vault_nextcloud_db_password is undefined" + +**Problem:** Variable not defined in vault file or vault file not loaded + +**Solution:** +1. Verify variable exists in vault file: + ```bash + ansible-vault view host_vars/arch-vps/vault.yml | grep vault_nextcloud + ``` + +2. Ensure you're using `--ask-vault-pass`: + ```bash + ansible-playbook -i inventory/hosts.yml site.yml --ask-vault-pass + ``` + ## Verification Check that variables are properly encrypted: @@ -103,3 +202,8 @@ cat host_vars/arch-vps/vault.yml # Decrypt and view (requires password) ansible-vault view host_vars/arch-vps/vault.yml ``` + +## Reference + +- [Ansible Vault Documentation](https://docs.ansible.com/ansible/latest/user_guide/vault.html) +- [Best Practices for Variables and Vaults](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#variables-and-vaults) diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index 58eaab1..4c5efb1 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -15,7 +15,6 @@ nextcloud_home: /opt/nextcloud nextcloud_html_dir: "{{ nextcloud_home }}/html" nextcloud_data_dir: "{{ nextcloud_home }}/data" nextcloud_config_dir: "{{ nextcloud_home }}/config" -nextcloud_custom_apps_dir: "{{ nextcloud_home }}/custom_apps" # Container configuration (FPM variant) nextcloud_version: "stable-fpm" @@ -52,6 +51,7 @@ nextcloud_domain: "cloud.jnss.me" # Admin user (auto-configured on first run) nextcloud_admin_user: "admin" +nextcloud_admin_email: "admin@jnss.me" nextcloud_admin_password: "{{ vault_nextcloud_admin_password }}" # Trusted domains (space-separated) @@ -75,12 +75,79 @@ nextcloud_background_jobs_mode: "cron" # Options: ajax, webcron, cron nextcloud_cron_interval: "5min" # How often cron runs (systemd timer) # ================================================================= -# Maintenance Configuration +# Nextcloud System 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) +# ================================================================= +# Apps Configuration +# ================================================================= + +# Apps to install and enable +nextcloud_apps_install: + - user_oidc + - calendar + - contacts + +# ================================================================= +# Email/SMTP Configuration (Optional) +# ================================================================= + +nextcloud_email_enabled: true # Master switch - set to true to enable SMTP + +# SMTP Server Configuration +nextcloud_smtp_mode: "smtp" # smtp, sendmail, qmail +nextcloud_smtp_host: "smtp.titan.email" # e.g., smtp.gmail.com, smtp.fastmail.com +nextcloud_smtp_port: 587 # 587 for TLS, 465 for SSL, 25 for plain +nextcloud_smtp_secure: "tls" # tls, ssl, or empty string for no encryption +nextcloud_smtp_auth: true # Enable SMTP authentication +nextcloud_smtp_authtype: "PLAIN" # LOGIN or PLAIN +nextcloud_smtp_username: "hello@jnss.me" # SMTP username +nextcloud_smtp_password: "{{ vault_nextcloud_smtp_password | default('') }}" + +# Email Addressing +nextcloud_mail_from_address: "hello" # Local part only (before @) +nextcloud_mail_domain: "jnss.me" # Domain part (after @) + +# Admin User Email (set at line 55 in Core Configuration section) + +# ================================================================= +# OIDC/SSO Configuration (Optional) +# ================================================================= + +nextcloud_oidc_enabled: true # Master switch - set to true to enable OIDC + +# Provider Configuration +nextcloud_oidc_provider_id: "authentik" # Provider identifier (slug) +nextcloud_oidc_provider_name: "Authentik" # Display name (shown on login button) +nextcloud_oidc_client_id: "{{ vault_nextcloud_oidc_client_id | default('') }}" +nextcloud_oidc_client_secret: "{{ vault_nextcloud_oidc_client_secret | default('') }}" +nextcloud_oidc_discovery_url: "https://auth.jnss.me/application/o/nextcloud/.well-known/openid-configuration" # Full discovery URL, e.g., https://auth.example.com/application/o/nextcloud/.well-known/openid-configuration + +# Scopes (based on Authentik integration guide) +# The 'nextcloud' scope is a custom scope you must create in Authentik +nextcloud_oidc_scope: "email profile nextcloud openid" + +# Provider Options +nextcloud_oidc_unique_uid: false # Hash provider+user ID to prevent account takeover (recommended: true) +nextcloud_oidc_check_bearer: false # Check bearer tokens for API/WebDAV calls +nextcloud_oidc_send_id_token_hint: true # Send ID token hint during logout + +# Attribute Mappings (based on Authentik integration guide) +nextcloud_oidc_mapping_display_name: "name" # Claim for display name +nextcloud_oidc_mapping_email: "email" # Claim for email +nextcloud_oidc_mapping_quota: "quota" # Claim for quota (from Authentik property mapping) +nextcloud_oidc_mapping_uid: "preferred_username" # Claim for user ID +nextcloud_oidc_mapping_groups: "groups" # Claim for groups (from Authentik property mapping) + +# Group Provisioning (based on Authentik integration guide) +nextcloud_oidc_group_provisioning: true # Auto-create groups from OIDC provider + +# Single Login Option +nextcloud_oidc_single_login: true # If true and only one provider, auto-redirect to SSO + # ================================================================= # Caddy Integration # ================================================================= diff --git a/roles/nextcloud/tasks/configure.yml b/roles/nextcloud/tasks/configure.yml new file mode 100644 index 0000000..eed54a4 --- /dev/null +++ b/roles/nextcloud/tasks/configure.yml @@ -0,0 +1,36 @@ +--- +# ================================================================= +# Nextcloud Configuration via Script +# ================================================================= +# Rick-Infra - Nextcloud Role +# +# Deploys and runs a configuration script inside the Nextcloud +# container to set system configuration via OCC commands. + +- name: Deploy Nextcloud configuration script + template: + src: configure-nextcloud.sh.j2 + dest: "{{ nextcloud_config_dir }}/configure.sh" + mode: '0755' + tags: [config, nextcloud-config] + +- name: Run Nextcloud configuration script + command: podman exec --user www-data nextcloud bash /var/www/html/config/configure.sh + register: nc_config_result + changed_when: false # Script output doesn't indicate changes reliably + failed_when: nc_config_result.rc != 0 + tags: [config, nextcloud-config] + +- name: Display configuration script output + debug: + msg: "{{ nc_config_result.stdout_lines }}" + when: nc_config_result.stdout | length > 0 + tags: [config, nextcloud-config] + +- name: Display configuration script errors + debug: + msg: "{{ nc_config_result.stderr_lines }}" + when: + - nc_config_result.stderr | length > 0 + - nc_config_result.rc != 0 + tags: [config, nextcloud-config] diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index 5e8c904..6014183 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -40,7 +40,6 @@ - "{{ nextcloud_html_dir }}" - "{{ nextcloud_data_dir }}" - "{{ nextcloud_config_dir }}" - - "{{ nextcloud_custom_apps_dir }}" tags: [setup, directories] - name: Deploy environment configuration @@ -52,12 +51,9 @@ notify: restart nextcloud tags: [config] -# NOTE: Custom Redis config is deployed AFTER installation completes (see below) -# to avoid interfering with the container's first-time initialization process - -# NOTE: redis-session-override.ini is NOT deployed because we use file-based sessions -# (not Redis sessions). If you enable REDIS_HOST in the future, you'll need to add -# proper session lock configuration. +# NOTE: Nextcloud is configured via OCC commands in a script after installation +# completes. This avoids interfering with the container's initialization process +# and provides a clean, explicit configuration approach. - name: Create Quadlet systemd directory (system scope) file: @@ -130,13 +126,9 @@ changed_when: false tags: [verification] -- name: Deploy custom Redis caching configuration (post-installation) - template: - src: redis.config.php.j2 - dest: "{{ nextcloud_config_dir }}/redis.config.php" - mode: '0644' - notify: restart nextcloud - tags: [config, redis] +- name: Configure Nextcloud via OCC script + include_tasks: configure.yml + tags: [config, configure] - name: Truncate nextcloud.log to prevent bloat shell: | @@ -149,10 +141,6 @@ 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: | @@ -167,7 +155,8 @@ ⚙️ Configuration: - Redis caching enabled (application-level cache & file locking) - PHP sessions use file-based storage (not Redis) - - Custom redis.config.php deployed post-installation + - Database optimizations applied + - Configuration via OCC commands 🚀 Ready for file storage and collaboration! diff --git a/roles/nextcloud/tasks/optimization.yml b/roles/nextcloud/tasks/optimization.yml deleted file mode 100644 index 46d52b5..0000000 --- a/roles/nextcloud/tasks/optimization.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -# ================================================================= -# 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/configure-nextcloud.sh.j2 b/roles/nextcloud/templates/configure-nextcloud.sh.j2 new file mode 100644 index 0000000..afdd42f --- /dev/null +++ b/roles/nextcloud/templates/configure-nextcloud.sh.j2 @@ -0,0 +1,189 @@ +#!/bin/bash +# ================================================================= +# Nextcloud Configuration Script +# ================================================================= +# Rick-Infra - Nextcloud Role +# +# This script configures Nextcloud via OCC commands after initial +# installation. It is generated from Ansible variables and runs +# inside the Nextcloud container. +# +# Generated by: roles/nextcloud/templates/configure-nextcloud.sh.j2 +# Managed by: Ansible + +set +e # Continue on errors, report at end +ERRORS=0 + +# Helper function for OCC +occ() { + php /var/www/html/occ "$@" 2>&1 +} + +# Track errors +check_error() { + if [ $? -ne 0 ]; then + ERRORS=$((ERRORS + 1)) + echo "ERROR: $1" >&2 + fi +} + +# ================================================================= +# Redis Caching Configuration +# ================================================================= +# Configure Redis for application-level caching and file locking +# WITHOUT enabling Redis sessions (which can cause performance issues) + +occ config:system:set memcache.distributed --value='\OC\Memcache\Redis' --quiet +check_error "Failed to set memcache.distributed" + +occ config:system:set memcache.locking --value='\OC\Memcache\Redis' --quiet +check_error "Failed to set memcache.locking" + +occ config:system:set redis host --value='{{ valkey_unix_socket_path }}' --quiet +check_error "Failed to set redis.host" + +occ config:system:set redis password --value='{{ valkey_password }}' --quiet +check_error "Failed to set redis.password" + +occ config:system:set redis dbindex --value={{ nextcloud_valkey_db }} --type=integer --quiet +check_error "Failed to set redis.dbindex" + +# ================================================================= +# Maintenance Configuration +# ================================================================= + +occ config:system:set maintenance_window_start --value={{ nextcloud_maintenance_window_start }} --type=integer --quiet +check_error "Failed to set maintenance_window_start" + +occ config:system:set default_phone_region --value='{{ nextcloud_default_phone_region }}' --quiet +check_error "Failed to set default_phone_region" + +# ================================================================= +# Database Optimization +# ================================================================= + +# Add missing database indices +occ db:add-missing-indices --quiet +check_error "Failed to add missing database indices" + +# Convert filecache to bigint +occ db:convert-filecache-bigint --no-interaction --quiet +check_error "Failed to convert filecache to bigint" + +# Update mimetype database mappings +occ maintenance:repair --include-expensive --quiet +check_error "Failed to run maintenance:repair" + +# ================================================================= +# App Installation and Enablement +# ================================================================= +# Install apps first, then enable them. This must happen before +# app-specific configuration (e.g., OIDC provider setup) + +{% if nextcloud_apps_install is defined and nextcloud_apps_install | length > 0 %} +# Install apps +{% for app in nextcloud_apps_install %} +occ app:install {{ app }} --quiet 2>&1 | grep -v "already installed" || true +check_error "Failed to install app: {{ app }}" +{% endfor %} +{% endif %} + +# ================================================================= +# Email/SMTP Configuration +# ================================================================= +{% if nextcloud_email_enabled | default(false) %} +# Configure SMTP mode +occ config:system:set mail_smtpmode --value={{ nextcloud_smtp_mode }} --quiet +check_error "Failed to set mail_smtpmode" + +# Configure SMTP server +occ config:system:set mail_smtphost --value='{{ nextcloud_smtp_host }}' --quiet +check_error "Failed to set mail_smtphost" + +occ config:system:set mail_smtpport --value={{ nextcloud_smtp_port }} --type=integer --quiet +check_error "Failed to set mail_smtpport" + +{% if nextcloud_smtp_secure %} +occ config:system:set mail_smtpsecure --value={{ nextcloud_smtp_secure }} --quiet +check_error "Failed to set mail_smtpsecure" +{% endif %} + +{% if nextcloud_smtp_auth %} +# Configure SMTP authentication +occ config:system:set mail_smtpauth --value=1 --type=integer --quiet +check_error "Failed to set mail_smtpauth" + +occ config:system:set mail_smtpauthtype --value={{ nextcloud_smtp_authtype }} --quiet +check_error "Failed to set mail_smtpauthtype" + +occ config:system:set mail_smtpname --value='{{ nextcloud_smtp_username }}' --quiet +check_error "Failed to set mail_smtpname" + +occ config:system:set mail_smtppassword --value='{{ nextcloud_smtp_password }}' --quiet +check_error "Failed to set mail_smtppassword" +{% endif %} + +# Configure email addressing +occ config:system:set mail_from_address --value='{{ nextcloud_mail_from_address }}' --quiet +check_error "Failed to set mail_from_address" + +occ config:system:set mail_domain --value='{{ nextcloud_mail_domain }}' --quiet +check_error "Failed to set mail_domain" +{% endif %} + +# Set admin user email address +{% if nextcloud_admin_email %} +occ user:setting {{ nextcloud_admin_user }} settings email '{{ nextcloud_admin_email }}' --quiet +check_error "Failed to set admin user email" +{% endif %} + +# ================================================================= +# OIDC/SSO Provider Configuration +# ================================================================= +{% if nextcloud_oidc_enabled | default(false) %} +# Configure OIDC provider (creates if doesn't exist, updates if exists) +occ user_oidc:provider {{ nextcloud_oidc_provider_id }} \ + --clientid='{{ nextcloud_oidc_client_id }}' \ + --clientsecret='{{ nextcloud_oidc_client_secret }}' \ + --discoveryuri='{{ nextcloud_oidc_discovery_url }}' \ + --scope='{{ nextcloud_oidc_scope }}' \ + --unique-uid={{ '1' if nextcloud_oidc_unique_uid else '0' }} \ + --check-bearer={{ '1' if nextcloud_oidc_check_bearer else '0' }} \ + --send-id-token-hint={{ '1' if nextcloud_oidc_send_id_token_hint else '0' }} \ +{% if nextcloud_oidc_mapping_display_name %} + --mapping-display-name='{{ nextcloud_oidc_mapping_display_name }}' \ +{% endif %} +{% if nextcloud_oidc_mapping_email %} + --mapping-email='{{ nextcloud_oidc_mapping_email }}' \ +{% endif %} +{% if nextcloud_oidc_mapping_quota %} + --mapping-quota='{{ nextcloud_oidc_mapping_quota }}' \ +{% endif %} +{% if nextcloud_oidc_mapping_uid %} + --mapping-uid='{{ nextcloud_oidc_mapping_uid }}' \ +{% endif %} +{% if nextcloud_oidc_mapping_groups %} + --mapping-groups='{{ nextcloud_oidc_mapping_groups }}' \ +{% endif %} + --group-provisioning={{ '1' if nextcloud_oidc_group_provisioning else '0' }} \ + --quiet 2>&1 | grep -v "already exists" || true +check_error "Failed to configure OIDC provider: {{ nextcloud_oidc_provider_id }}" + +{% if nextcloud_oidc_single_login %} +# Enable single login (auto-redirect to SSO if only one provider) +occ config:app:set user_oidc allow_multiple_user_backends --value=0 --quiet +check_error "Failed to enable single login mode" +{% endif %} +{% endif %} + +# ================================================================= +# Exit Status +# ================================================================= + +if [ $ERRORS -gt 0 ]; then + echo "Configuration completed with $ERRORS error(s)" >&2 + exit 1 +else + echo "Nextcloud configuration completed successfully" + exit 0 +fi diff --git a/roles/nextcloud/templates/nextcloud.config.php.j2 b/roles/nextcloud/templates/nextcloud.config.php.j2 deleted file mode 100644 index fde5f88..0000000 --- a/roles/nextcloud/templates/nextcloud.config.php.j2 +++ /dev/null @@ -1,32 +0,0 @@ - {{ 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/nextcloud/templates/nextcloud.container b/roles/nextcloud/templates/nextcloud.container index 4f3d112..9523370 100644 --- a/roles/nextcloud/templates/nextcloud.container +++ b/roles/nextcloud/templates/nextcloud.container @@ -23,9 +23,6 @@ Volume={{ nextcloud_data_dir }}:/var/www/html/data:Z # Configuration (private - contains secrets) Volume={{ nextcloud_config_dir }}:/var/www/html/config:Z -# Custom apps (world-readable) -Volume={{ nextcloud_custom_apps_dir }}:/var/www/html/custom_apps:Z - # Infrastructure sockets (mounted with world-readable permissions on host) Volume={{ postgresql_unix_socket_directories }}:{{ postgresql_unix_socket_directories }}:Z Volume={{ valkey_unix_socket_path | dirname }}:{{ valkey_unix_socket_path | dirname }}:Z diff --git a/roles/nextcloud/templates/redis.config.php.j2 b/roles/nextcloud/templates/redis.config.php.j2 deleted file mode 100644 index 7d6ff7f..0000000 --- a/roles/nextcloud/templates/redis.config.php.j2 +++ /dev/null @@ -1,34 +0,0 @@ - '\OC\Memcache\Redis', - 'memcache.locking' => '\OC\Memcache\Redis', - 'redis' => array( - 'host' => '{{ valkey_unix_socket_path }}', - 'password' => '{{ valkey_password }}', - ), -);