Compare commits

...

2 Commits

Author SHA1 Message Date
bfd6f22f0e Add Vaultwarden password manager role with PostgreSQL and SSO support
- Implement complete Vaultwarden deployment using Podman Quadlet
- PostgreSQL backend via Unix socket with 777 permissions
- Caddy reverse proxy with WebSocket support for live sync
- Control-node admin token hashing using argon2 (OWASP preset)
- Idempotent token hashing with deterministic salt generation
- Full Authentik SSO integration following official guide
- SMTP email configuration support (optional)
- Invitation-only user registration by default
- Comprehensive documentation with setup and troubleshooting guides

Technical Details:
- Container: vaultwarden/server:latest from Docker Hub
- Database: PostgreSQL via /var/run/postgresql socket
- Port: 8080 (localhost only, proxied by Caddy)
- Domain: vault.jnss.me
- Admin token: Hashed on control node with argon2id
- SSO: OpenID Connect with offline_access scope support

Role includes automatic argon2 installation on control node if needed.
2025-12-22 21:33:27 +01:00
89b43180fc 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
2025-12-21 14:54:44 +01:00
26 changed files with 2233 additions and 216 deletions

View File

@@ -0,0 +1,307 @@
# Vaultwarden SSO Feature Status and Configuration
**Document Date:** December 21, 2025
**Last Updated:** December 21, 2025
**Status:** SSO Configured, Waiting for Stable Release
---
## Executive Summary
Vaultwarden has been successfully deployed with **SSO integration pre-configured** for Authentik. However, SSO functionality is currently **only available in testing images** and not yet in the stable release. This document explains the current state, our decision to wait for stable, and how to activate SSO when it becomes available.
## Current Deployment Status
### What's Working
- ✅ Vaultwarden deployed successfully at `https://vault.jnss.me`
- ✅ PostgreSQL backend via Unix socket
- ✅ Admin panel accessible and working
- ✅ Email/password authentication working
- ✅ SMTP notifications configured
- ✅ All SSO environment variables correctly configured
- ✅ Authentik OAuth2 provider created and ready
### What's Not Working (By Design)
- ❌ SSO login option not appearing on login page
- ❌ "Use single sign-on" button missing
**Reason:** Using stable image (`vaultwarden/server:latest` v1.34.3) which does not include SSO code.
## Investigation Summary (Dec 21, 2025)
### Problem Reported
User deployed Vaultwarden with `vaultwarden_sso_enabled: true` and configured Authentik integration following official guides, but no SSO option appeared on the login page.
### Root Cause Identified
After investigation including:
- Service status check (healthy, running normally)
- Environment variable verification (all SSO vars present and correct)
- Configuration review (matches Authentik integration guide perfectly)
- API endpoint inspection (`/api/config` returns `"sso":""`)
- Official documentation review
**Finding:** SSO feature is only compiled into `vaultwarden/server:testing` images, not stable releases.
### Evidence
From [Vaultwarden Official Wiki](https://github.com/dani-garcia/vaultwarden/wiki):
> **Testing features**
>
> Features available in the `testing` docker image:
> - Single Sign-On (SSO), see [Documentation](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect)
From [SSO Documentation Page](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect):
> ⚠️ **Important**
>
> ‼️ ‼️ ‼️
> SSO is currently only available in the `:testing` tagged images!
> The current stable `v1.34.3` **does not** contain the SSO feature.
> ‼️ ‼️ ‼️
### API Response Analysis
```bash
# API config endpoint shows SSO not available
curl -s http://127.0.0.1:8080/api/config | grep -o '"sso":"[^"]*"'
# Returns: "sso":"" ← Empty = feature not compiled in
# Environment variables are set correctly
podman exec vaultwarden env | grep -i sso
SSO_ENABLED=true
SSO_ONLY=false
SSO_CLIENT_ID=DDOQXdwFn6pi4FtSvo7PK5b63pRzyD552xapTZGr
SSO_CLIENT_SECRET=02D308Sle2w2NPsi7UaXb3bvKKK4punFDT2LiVqKpzvEFqgPpyLysA8Z5yS4g8t4LYmsI9txLE02l5MtWP5R2RBavLhYHjNFHcwEmvYB94bOJw45YmgiGePaW4NHKcfY
SSO_AUTHORITY=https://auth.jnss.me/application/o/vaultwarden/
SSO_SCOPES="openid email profile offline_access"
# ... etc (all correct)
```
**Conclusion:** Environment configured correctly, feature simply not available in stable release.
## Decision: Wait for Stable Release
### Rationale
**Why NOT switch to testing image:**
1. **Production stability** - This is a password manager handling sensitive credentials
2. **Testing images are volatile** - Frequent updates, potential bugs
3. **No ETA for stable** - SSO marked as "testing feature" indefinitely
4. **Current auth works fine** - Email/password login is secure and functional
5. **Configuration is ready** - When SSO reaches stable, it will work immediately
**Why keep SSO configured:**
1. **Future-ready** - No additional work needed when SSO stabilizes
2. **No harm** - Environment variables are ignored by stable image
3. **Documentation** - Clear record of SSO setup for future reference
4. **Authentik provider ready** - Already created and configured
### Alternatives Considered
| Option | Pros | Cons | Decision |
|--------|------|------|----------|
| Use `testing` image | SSO available now | Unstable, potential data loss, frequent breaking changes | ❌ Rejected |
| Wait for stable | Stable, reliable, secure | No SSO until unknown future date | ✅ **Selected** |
| Remove SSO config | Cleaner config | Requires reconfiguration later | ❌ Rejected |
| Dual deployment | Test SSO separately | Resource waste, complexity | ❌ Rejected |
## Current Configuration
### Ansible Role Variables
Location: `roles/vaultwarden/defaults/main.yml`
```yaml
# Container version (stable, no SSO)
vaultwarden_version: "latest"
# SSO enabled (ready for when feature reaches stable)
vaultwarden_sso_enabled: true
vaultwarden_sso_only: false
vaultwarden_sso_client_id: "{{ vault_vaultwarden_sso_client_id }}"
vaultwarden_sso_client_secret: "{{ vault_vaultwarden_sso_client_secret }}"
vaultwarden_sso_authority: "https://auth.jnss.me/application/o/vaultwarden/"
vaultwarden_sso_scopes: "openid email profile offline_access"
vaultwarden_sso_signups_match_email: true
vaultwarden_sso_allow_unknown_email_verification: false
vaultwarden_sso_client_cache_expiration: 0
```
### Vault Variables (Encrypted)
Location: `group_vars/homelab/vault.yml`
```yaml
# SSO credentials from Authentik
vault_vaultwarden_sso_client_id: "DDOQXdwFn6pi4FtSvo7PK5b63pRzyD552xapTZGr"
vault_vaultwarden_sso_client_secret: "02D308Sle2w2NPsi7UaXb3bvKKK4punFDT2LiVqKpzvEFqgPpyLysA8Z5yS4g8t4LYmsI9txLE02l5MtWP5R2RBavLhYHjNFHcwEmvYB94bOJw45YmgiGePaW4NHKcfY"
```
### Authentik Provider Configuration
**Provider Details:**
- **Name:** Vaultwarden
- **Type:** OAuth2/OpenID Connect Provider
- **Client Type:** Confidential
- **Client ID:** `DDOQXdwFn6pi4FtSvo7PK5b63pRzyD552xapTZGr`
- **Application Slug:** `vaultwarden`
- **Redirect URI:** `https://vault.jnss.me/identity/connect/oidc-signin`
**Scopes Configured:**
-`authentik default OAuth Mapping: OpenID 'openid'`
-`authentik default OAuth Mapping: OpenID 'email'`
-`authentik default OAuth Mapping: OpenID 'profile'`
-`authentik default OAuth Mapping: OpenID 'offline_access'`
**Token Settings:**
- Access token validity: > 5 minutes
- Refresh token enabled via `offline_access` scope
**Authority URL:** `https://auth.jnss.me/application/o/vaultwarden/`
### Deployed Environment (VPS)
```bash
# Deployed environment file
Location: /opt/vaultwarden/.env
# All SSO variables present and correct
SSO_ENABLED=true
SSO_AUTHORITY=https://auth.jnss.me/application/o/vaultwarden/
SSO_SCOPES="openid email profile offline_access"
# ... etc
```
## How to Activate SSO (Future)
When Vaultwarden SSO reaches stable release:
### Automatic Activation (Recommended)
1. **Monitor Vaultwarden releases** for SSO in stable:
- Watch: https://github.com/dani-garcia/vaultwarden/releases
- Look for: SSO feature in stable image changelog
2. **Update container** (standard maintenance):
```bash
ansible-playbook rick-infra.yml --tags vaultwarden --ask-vault-pass
```
3. **Verify SSO is available**:
```bash
ssh root@69.62.119.31 "curl -s http://127.0.0.1:8080/api/config | grep sso"
# Should return: "sso":"https://vault.jnss.me" or similar
```
4. **Test SSO**:
- Navigate to: https://vault.jnss.me
- Log out if logged in
- Enter verified email address
- Click "Use single sign-on" button
- Should redirect to Authentik login
**No configuration changes needed** - Everything is already set up correctly.
### Manual Testing (Use Testing Image)
If you want to test SSO before stable release:
1. **Backup current deployment**:
```bash
ansible-playbook playbooks/backup-vaultwarden.yml # Create if needed
```
2. **Change to testing image** in `roles/vaultwarden/defaults/main.yml`:
```yaml
vaultwarden_version: "testing"
```
3. **Deploy**:
```bash
ansible-playbook rick-infra.yml --tags vaultwarden --ask-vault-pass
```
4. **Test SSO** (same as above)
5. **Revert to stable** when testing complete:
```yaml
vaultwarden_version: "latest"
```
**Warning:** Testing images may contain bugs, data corruption risks, or breaking changes. Not recommended for production password manager.
## Verification Commands
### Check Current Image Version
```bash
ssh root@69.62.119.31 "podman inspect vaultwarden --format '{{.ImageName}}'"
# Expected: docker.io/vaultwarden/server:latest
```
### Check SSO API Status
```bash
ssh root@69.62.119.31 "curl -s http://127.0.0.1:8080/api/config | grep -o '\"sso\":\"[^\"]*\"'"
# Current: "sso":"" (empty = not available)
# Future: "sso":"https://vault.jnss.me" (URL = available)
```
### Check SSO Environment Variables
```bash
ssh root@69.62.119.31 "podman exec vaultwarden env | grep -i sso | sort"
# Should show all SSO_* variables configured correctly
```
### Check Vaultwarden Version
```bash
ssh root@69.62.119.31 "podman exec vaultwarden /vaultwarden --version"
# Current: Vaultwarden 1.34.3
```
## Documentation Updates
The following files have been updated to document this finding:
1. **`roles/vaultwarden/README.md`**
- Added warning banner in SSO configuration section
- Updated troubleshooting section with SSO status checks
- Documented stable vs testing image behavior
2. **`roles/vaultwarden/VAULT_VARIABLES.md`**
- Added SSO feature status warning
- Documented that credentials are ready but inactive
3. **`roles/vaultwarden/defaults/main.yml`**
- Added comments explaining SSO availability
4. **`docs/vaultwarden-sso-status.md`** (this document)
- Complete investigation findings
- Configuration reference
- Activation procedures
## Timeline
- **2025-07-30:** Vaultwarden v1.34.3 released (current stable)
- **2025-12-21:** Vaultwarden deployed with SSO pre-configured
- **2025-12-21:** Investigation completed, SSO status documented
- **TBD:** SSO feature reaches stable release (no ETA)
- **Future:** Automatic SSO activation on next deployment
## Related Documentation
- [Vaultwarden Official Wiki](https://github.com/dani-garcia/vaultwarden/wiki)
- [SSO Documentation](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect)
- [Authentik Integration Guide](https://integrations.goauthentik.io/security/vaultwarden/)
- [Vaultwarden Testing Features](https://github.com/dani-garcia/vaultwarden/wiki#testing-features)
## Contact
For questions about this deployment:
- Infrastructure repo: `/home/fitz/rick-infra`
- Role location: `roles/vaultwarden/`
- Service: `vault.jnss.me`
- Host: `arch-vps` (69.62.119.31)
---
**Status:** SSO configured and ready, waiting for upstream stable release. No action required.

View File

@@ -65,7 +65,8 @@ nextcloud_db_password: "{{ vault_nextcloud_db_password }}"
nextcloud_valkey_db: 2 # Authentik uses 1 nextcloud_valkey_db: 2 # Authentik uses 1
# Admin configuration # Admin configuration
nextcloud_admin_user: "admin" nextcloud_admin_user: "joakim"
nextcloud_admin_email: "joakim@jnss.me"
nextcloud_admin_password: "{{ vault_nextcloud_admin_password }}" nextcloud_admin_password: "{{ vault_nextcloud_admin_password }}"
# Service configuration # Service configuration

View File

@@ -63,15 +63,6 @@
changed_when: container_remove.rc == 0 changed_when: container_remove.rc == 0
failed_when: false 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 # Remove Systemd Units
# ============================================ # ============================================

View File

@@ -9,6 +9,7 @@
# - Nextcloud cloud storage # - Nextcloud cloud storage
# - Authentik SSO/authentication # - Authentik SSO/authentication
# - Gitea git hosting # - Gitea git hosting
# - Vaultwarden password manager
# #
# Usage: # Usage:
# ansible-playbook playbooks/homelab.yml # ansible-playbook playbooks/homelab.yml
@@ -30,8 +31,13 @@
# name: gitea # name: gitea
# tags: ['gitea', 'git', 'development'] # tags: ['gitea', 'git', 'development']
- name: Deploy Nextcloud # - name: Deploy Nextcloud
# include_role:
# name: nextcloud
# tags: ['nextcloud', 'cloud', 'storage']
- name: Deploy Vaultwarden
include_role: include_role:
name: nextcloud name: vaultwarden
tags: ['nextcloud', 'cloud', 'storage'] tags: ['vaultwarden', 'vault', 'password-manager', 'security']

View File

@@ -52,9 +52,17 @@ See `defaults/main.yml` for all configurable variables.
Define these in your `host_vars/` with `ansible-vault`: Define these in your `host_vars/` with `ansible-vault`:
```yaml ```yaml
# Core credentials (required)
vault_nextcloud_db_password: "secure-database-password" vault_nextcloud_db_password: "secure-database-password"
vault_nextcloud_admin_password: "secure-admin-password" vault_nextcloud_admin_password: "secure-admin-password"
vault_valkey_password: "secure-valkey-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 ### 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 6. Container runs `occ maintenance:install` with PostgreSQL
7. Installation creates `config.php` with database credentials 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` 8. Ansible waits for `occ status` to report `installed: true`
9. Ansible deploys custom `redis.config.php` (overwrites default) 9. Ansible deploys and runs configuration script inside container
10. Container restart applies custom configuration 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?** **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` - Container must complete first-time setup (copy files, run `occ maintenance:install`)
- Entrypoint sees `version.php` exists → assumes already installed - OCC commands require a fully initialized Nextcloud installation
- Skips running `occ maintenance:install` - Running configuration after installation avoids conflicts with the entrypoint script
- Result: Empty `config.php`, 503 errors
By deploying custom configs **after** installation completes, we: **Configuration Method:**
- ✅ Allow the container's auto-installation to run properly
- ✅ Override specific configs (like Redis) after the fact This role uses **OCC commands via a script** rather than config files because:
-Maintain idempotency (subsequent runs just update configs) -**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. 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/<slug>/.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 ## Usage
### Include in Playbook ### 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.local`: APCu (in-memory opcode cache)
- `memcache.distributed`: Redis (shared cache, file locking) - `memcache.distributed`: Redis (shared cache, file locking)
- `memcache.locking`: Redis (transactional 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?** **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 4. **Worker exhaustion**: Limited FPM workers (default 5) all become blocked
5. **Cascading failure**: New requests queue, timeouts accumulate, locks orphan 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: **If you need Redis sessions** (e.g., multi-server setup with session sharing), you must:
1. Enable `REDIS_HOST` in `nextcloud.env.j2` 1. Enable `REDIS_HOST` in `nextcloud.env.j2`

View File

@@ -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/<hostname>/vault.yml`). This document describes all vault-encrypted variables used by the Nextcloud role.
## Required Variables ## Required Variables
Add these to your encrypted vault file: These variables **must** be defined in your vault file for the role to function:
```yaml ```yaml
# Nextcloud database password # =================================================================
# Core Credentials (REQUIRED)
# =================================================================
# PostgreSQL database password for Nextcloud user
vault_nextcloud_db_password: "CHANGE_ME_secure_database_password" 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" 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" 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 ## Creating/Editing Vault File
### First Time Setup ### First Time Setup
@@ -37,6 +119,13 @@ ansible-vault edit host_vars/arch-vps/vault.yml
# Add the Nextcloud variables, then save and exit # 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 ### Password Generation
Generate secure passwords: Generate secure passwords:
@@ -49,39 +138,26 @@ openssl rand -base64 32
pwgen -s 32 1 pwgen -s 32 1
``` ```
## Example Vault File ## Running Playbooks with Vault
Your `host_vars/arch-vps/vault.yml` should include: ### Interactive Password Prompt
```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:
```bash ```bash
# Deploy with vault password prompt ansible-playbook -i inventory/hosts.yml site.yml --ask-vault-pass
ansible-playbook -i inventory/hosts.yml site.yml --tags nextcloud --ask-vault-pass ```
# Or use a password file ### Using a Password File
ansible-playbook -i inventory/hosts.yml site.yml --tags nextcloud --vault-password-file ~/.vault_pass
```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 ## 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.) - Store vault password securely (password manager, encrypted file, etc.)
- Consider using `ansible-vault rekey` to change vault password periodically - 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 ## Verification
Check that variables are properly encrypted: Check that variables are properly encrypted:
@@ -103,3 +202,8 @@ cat host_vars/arch-vps/vault.yml
# Decrypt and view (requires password) # Decrypt and view (requires password)
ansible-vault view host_vars/arch-vps/vault.yml 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)

View File

@@ -15,7 +15,6 @@ nextcloud_home: /opt/nextcloud
nextcloud_html_dir: "{{ nextcloud_home }}/html" nextcloud_html_dir: "{{ nextcloud_home }}/html"
nextcloud_data_dir: "{{ nextcloud_home }}/data" nextcloud_data_dir: "{{ nextcloud_home }}/data"
nextcloud_config_dir: "{{ nextcloud_home }}/config" nextcloud_config_dir: "{{ nextcloud_home }}/config"
nextcloud_custom_apps_dir: "{{ nextcloud_home }}/custom_apps"
# Container configuration (FPM variant) # Container configuration (FPM variant)
nextcloud_version: "stable-fpm" nextcloud_version: "stable-fpm"
@@ -52,6 +51,7 @@ nextcloud_domain: "cloud.jnss.me"
# Admin user (auto-configured on first run) # Admin user (auto-configured on first run)
nextcloud_admin_user: "admin" nextcloud_admin_user: "admin"
nextcloud_admin_email: "admin@jnss.me"
nextcloud_admin_password: "{{ vault_nextcloud_admin_password }}" nextcloud_admin_password: "{{ vault_nextcloud_admin_password }}"
# Trusted domains (space-separated) # 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) 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_maintenance_window_start: 4 # Start hour (UTC) for maintenance window
nextcloud_default_phone_region: "NO" # Default phone region code (ISO 3166-1 alpha-2) 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 # Caddy Integration
# ================================================================= # =================================================================

View File

@@ -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]

View File

@@ -40,7 +40,6 @@
- "{{ nextcloud_html_dir }}" - "{{ nextcloud_html_dir }}"
- "{{ nextcloud_data_dir }}" - "{{ nextcloud_data_dir }}"
- "{{ nextcloud_config_dir }}" - "{{ nextcloud_config_dir }}"
- "{{ nextcloud_custom_apps_dir }}"
tags: [setup, directories] tags: [setup, directories]
- name: Deploy environment configuration - name: Deploy environment configuration
@@ -52,12 +51,9 @@
notify: restart nextcloud notify: restart nextcloud
tags: [config] tags: [config]
# NOTE: Custom Redis config is deployed AFTER installation completes (see below) # NOTE: Nextcloud is configured via OCC commands in a script after installation
# to avoid interfering with the container's first-time initialization process # completes. This avoids interfering with the container's initialization process
# and provides a clean, explicit configuration approach.
# 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.
- name: Create Quadlet systemd directory (system scope) - name: Create Quadlet systemd directory (system scope)
file: file:
@@ -130,13 +126,9 @@
changed_when: false changed_when: false
tags: [verification] tags: [verification]
- name: Deploy custom Redis caching configuration (post-installation) - name: Configure Nextcloud via OCC script
template: include_tasks: configure.yml
src: redis.config.php.j2 tags: [config, configure]
dest: "{{ nextcloud_config_dir }}/redis.config.php"
mode: '0644'
notify: restart nextcloud
tags: [config, redis]
- name: Truncate nextcloud.log to prevent bloat - name: Truncate nextcloud.log to prevent bloat
shell: | shell: |
@@ -149,10 +141,6 @@
include_tasks: cron.yml include_tasks: cron.yml
tags: [cron, background-jobs] tags: [cron, background-jobs]
- name: Optimize database and apply configuration
include_tasks: optimization.yml
tags: [optimization, database]
- name: Display Nextcloud deployment status - name: Display Nextcloud deployment status
debug: debug:
msg: | msg: |
@@ -167,7 +155,8 @@
⚙️ Configuration: ⚙️ Configuration:
- Redis caching enabled (application-level cache & file locking) - Redis caching enabled (application-level cache & file locking)
- PHP sessions use file-based storage (not Redis) - 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! 🚀 Ready for file storage and collaboration!

View File

@@ -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 }}

View File

@@ -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

View File

@@ -1,32 +0,0 @@
<?php
/**
* Nextcloud Additional Configuration
* Rick-Infra - Nextcloud Role
*
* This file provides additional configuration for Nextcloud
* that complements the main config.php file.
*
* Applied via: php occ config:system:set
*/
$CONFIG = array(
/**
* Maintenance Window
*
* Defines a maintenance window during which resource-intensive
* operations (like database updates) can be performed.
*
* Format: Hour (0-23, UTC)
*/
'maintenance_window_start' => {{ 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 }}',
);

View File

@@ -23,9 +23,6 @@ Volume={{ nextcloud_data_dir }}:/var/www/html/data:Z
# Configuration (private - contains secrets) # Configuration (private - contains secrets)
Volume={{ nextcloud_config_dir }}:/var/www/html/config:Z 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) # Infrastructure sockets (mounted with world-readable permissions on host)
Volume={{ postgresql_unix_socket_directories }}:{{ postgresql_unix_socket_directories }}:Z Volume={{ postgresql_unix_socket_directories }}:{{ postgresql_unix_socket_directories }}:Z
Volume={{ valkey_unix_socket_path | dirname }}:{{ valkey_unix_socket_path | dirname }}:Z Volume={{ valkey_unix_socket_path | dirname }}:{{ valkey_unix_socket_path | dirname }}:Z

View File

@@ -1,34 +0,0 @@
<?php
/**
* Redis/Valkey Caching Configuration for Nextcloud
*
* This file provides Redis caching for Nextcloud application-level operations
* (distributed cache, file locking) WITHOUT enabling Redis for PHP sessions.
*
* IMPORTANT: This overrides the default /usr/src/nextcloud/config/redis.config.php
* which checks for REDIS_HOST environment variable. We deploy this custom version
* to enable Redis caching while keeping PHP sessions file-based for stability.
*
* Why not use REDIS_HOST env var?
* - Setting REDIS_HOST enables BOTH Redis sessions AND Redis caching
* - Redis session handling can cause severe performance issues:
* * Session lock contention under high concurrency
* * Infinite lock retries blocking FPM workers
* * Timeout orphaning leaving locks unreleased
* * Worker pool exhaustion causing cascading failures
*
* This configuration provides the benefits of Redis caching (fast distributed
* cache, reliable file locking) while avoiding the pitfalls of Redis sessions.
*
* Managed by: Ansible Nextcloud role
* Template: roles/nextcloud/templates/redis.config.php.j2
*/
$CONFIG = array(
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => array(
'host' => '{{ valkey_unix_socket_path }}',
'password' => '{{ valkey_password }}',
),
);

331
roles/vaultwarden/README.md Normal file
View File

@@ -0,0 +1,331 @@
# Vaultwarden Password Manager Role
Self-contained Vaultwarden (Bitwarden-compatible) password manager deployment using Podman and PostgreSQL.
## Overview
This role deploys Vaultwarden as a Podman Quadlet container with:
- **PostgreSQL backend** via Unix socket (777 permissions)
- **Caddy reverse proxy** with HTTPS and WebSocket support
- **SSO integration** ready (Authentik OpenID Connect)
- **SMTP support** for email notifications (optional)
- **Admin panel** for management
## Architecture
```
Internet → Caddy (HTTPS) → Vaultwarden Container → PostgreSQL (Unix socket)
/data volume
```
### Components
- **Container Image**: `vaultwarden/server:latest` (Docker Hub)
- **User**: System user `vaultwarden` (non-root)
- **Port**: 8080 (localhost only)
- **Domain**: `vault.jnss.me`
- **Database**: PostgreSQL via Unix socket at `/var/run/postgresql`
- **Data**: `/opt/vaultwarden/data`
## Dependencies
**Managed Hosts:**
- `postgresql` role (provides database and Unix socket)
- `caddy` role (provides reverse proxy)
**Control Node:**
- `argon2` command-line tool (automatically installed if not present)
- Used to hash the admin token securely on the control node
- Available in most package managers: `pacman -S argon2`, `apt install argon2`, etc.
## Configuration
### Required Variables
Must be defined in vault (e.g., `group_vars/homelab/vault.yml`):
```yaml
# Database password
vault_vaultwarden_db_password: "secure-database-password"
# Admin token (plain text - will be hashed automatically during deployment)
vault_vaultwarden_admin_token: "your-secure-admin-token"
# SMTP password (if using email)
vault_vaultwarden_smtp_password: "smtp-password" # optional
# SSO credentials (if using Authentik integration)
vault_vaultwarden_sso_client_id: "vaultwarden" # optional
vault_vaultwarden_sso_client_secret: "sso-secret" # optional
```
### Optional Variables
Override in `group_vars` or `host_vars`:
```yaml
# Domain
vaultwarden_domain: "vault.jnss.me"
# Container version
vaultwarden_version: "latest"
# Registration controls
vaultwarden_signups_allowed: false # Disable open registration
vaultwarden_invitations_allowed: true # Allow existing users to invite
# SMTP Configuration
vaultwarden_smtp_enabled: true
vaultwarden_smtp_host: "smtp.example.com"
vaultwarden_smtp_port: 587
vaultwarden_smtp_from: "vault@jnss.me"
vaultwarden_smtp_username: "smtp-user"
# SSO Configuration (Authentik)
vaultwarden_sso_enabled: true
vaultwarden_sso_authority: "https://auth.jnss.me"
```
## Usage
### Deploy Vaultwarden
```bash
# Full deployment
ansible-playbook rick-infra.yml --tags vaultwarden
# Or via site.yml
ansible-playbook site.yml --tags vaultwarden -l homelab
```
### Access Admin Panel
1. Set admin token in vault file (plain text):
```yaml
# Generate a secure token
vault_vaultwarden_admin_token: "$(openssl rand -base64 32)"
```
2. The role automatically hashes the token during deployment:
- Hashing occurs on the **control node** using `argon2` CLI
- Uses OWASP recommended settings (19MiB memory, 2 iterations, 1 thread)
- Idempotent: same token always produces the same hash
- The `argon2` package is automatically installed if not present
3. Access: `https://vault.jnss.me/admin` (use the plain text token from step 1)
### Configure SSO (Authentik Integration)
> ⚠️ **IMPORTANT: SSO Feature Status (as of December 2025)**
>
> SSO is currently **only available in `vaultwarden/server:testing` images**.
> The stable release (v1.34.3) does **NOT** include SSO functionality.
>
> **Current Deployment Status:**
> - This role is configured with SSO settings ready for when SSO reaches stable release
> - Using `vaultwarden_version: "latest"` (stable) - SSO will not appear
> - To test SSO now: Set `vaultwarden_version: "testing"` (not recommended for production)
> - To wait for stable: Keep current configuration, SSO will activate automatically when available
>
> **References:**
> - [Vaultwarden Wiki - SSO Documentation](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect)
> - [Vaultwarden Testing Features](https://github.com/dani-garcia/vaultwarden/wiki#testing-features)
>
> **Decision:** This deployment keeps SSO configured but uses stable image until SSO feature is production-ready.
Following the [Authentik integration guide](https://integrations.goauthentik.io/security/vaultwarden/):
1. **In Authentik**: Create OAuth2/OpenID Provider
- **Name**: `Vaultwarden`
- **Client Type**: `Confidential`
- **Redirect URIs**: `https://vault.jnss.me/identity/connect/oidc-signin` (must be strict/exact match)
- **Scopes**: Under "Advanced protocol settings", ensure these scope mappings are selected:
- `authentik default OAuth Mapping: OpenID 'openid'`
- `authentik default OAuth Mapping: OpenID 'email'`
- `authentik default OAuth Mapping: OpenID 'profile'`
- `authentik default OAuth Mapping: OpenID 'offline_access'` ⚠️ **Required**
- **Access token validity**: Set to more than 5 minutes
- **Note the Client ID, Client Secret, and application slug** (from URL or provider settings)
2. **Update Vault Variables**:
```yaml
vault_vaultwarden_sso_client_id: "<client-id-from-authentik>"
vault_vaultwarden_sso_client_secret: "<client-secret-from-authentik>"
```
3. **Enable SSO and set authority** in `group_vars/homelab/main.yml`:
```yaml
vaultwarden_sso_enabled: true
# Replace 'vaultwarden' with your actual application slug
vaultwarden_sso_authority: "https://auth.jnss.me/application/o/vaultwarden/"
```
4. **Optional: SSO-Only Mode** (disable password login):
```yaml
vaultwarden_sso_only: true # Requires SSO, disables email+password
```
5. **Redeploy**:
```bash
ansible-playbook rick-infra.yml --tags vaultwarden
```
6. **Test**: Log out, enter a verified email on login page, click "Use single sign-on"
- **Note**: With `vaultwarden_version: "latest"`, SSO button will not appear (feature not in stable yet)
## Security Considerations
### Database Access
- Uses PostgreSQL Unix socket with **777 permissions**
- Security maintained via password authentication (scram-sha-256)
- See: `docs/socket-permissions-architecture.md`
### Admin Token
- **Never commit plain admin token to git**
- Use Ansible Vault for `vault_vaultwarden_admin_token`
- Rotate periodically via admin panel
### User Registration
- Default: **Disabled** (`vaultwarden_signups_allowed: false`)
- Users must be invited by existing users or created via admin panel
- Prevents unauthorized account creation
## Maintenance
### Backup
Backup the following:
```bash
# Database backup (via PostgreSQL role)
sudo -u postgres pg_dump vaultwarden > vaultwarden-backup.sql
# Data directory (attachments, icons, etc.)
tar -czf vaultwarden-data-backup.tar.gz /opt/vaultwarden/data
```
### Update Container
```bash
# Pull new image and restart
ansible-playbook rick-infra.yml --tags vaultwarden
```
### View Logs
```bash
# Service logs
journalctl -u vaultwarden -f
# Container logs
podman logs vaultwarden -f
```
### Restart Service
```bash
systemctl restart vaultwarden
```
## Troubleshooting
### Container won't start
```bash
# Check container status
systemctl status vaultwarden
# Check container directly
podman ps -a
podman logs vaultwarden
# Verify database connectivity
sudo -u vaultwarden psql -h /var/run/postgresql -U vaultwarden -d vaultwarden -c "SELECT 1;"
```
### Database connection errors
1. Verify PostgreSQL is running: `systemctl status postgresql`
2. Check socket exists: `ls -la /var/run/postgresql/.s.PGSQL.5432`
3. Verify socket permissions: Should be `srwxrwxrwx` (777)
4. Test connection as vaultwarden user (see above)
### Can't access admin panel
1. Verify admin token is set in vault file (plain text)
2. Check that the token was hashed successfully during deployment
3. Ensure you're using the plain text token to log in
4. Redeploy to regenerate hash if needed
### SSO not appearing / not working
**Most Common Issue: Using Stable Image**
SSO is only available in testing images. Check your deployment:
```bash
# Check current image version
podman inspect vaultwarden --format '{{.ImageName}}'
# Check API config for SSO support
curl -s http://127.0.0.1:8080/api/config | grep -o '"sso":"[^"]*"'
# Empty string "" = SSO not available in this image
# URL present = SSO is available
```
**If using `vaultwarden_version: "latest"`**: SSO will not appear (feature not in stable yet)
- **To test SSO**: Set `vaultwarden_version: "testing"` in role defaults or group/host vars
- **For production**: Wait for SSO to reach stable release (recommended)
**If using `vaultwarden_version: "testing"` and SSO still not working**:
1. Verify Authentik provider configuration:
- Check that `offline_access` scope mapping is added
- Verify redirect URI matches exactly: `https://vault.jnss.me/identity/connect/oidc-signin`
- Ensure access token validity is > 5 minutes
2. Verify SSO authority URL includes full path with slug:
- Should be: `https://auth.jnss.me/application/o/<your-slug>/`
- Not just: `https://auth.jnss.me`
3. Check client ID and secret in vault match Authentik
4. Verify all required scopes: `openid email profile offline_access`
5. Check Vaultwarden logs for SSO-related errors:
```bash
podman logs vaultwarden 2>&1 | grep -i sso
```
6. Test SSO flow: Log out, enter verified email, click "Use single sign-on"
## File Structure
```
roles/vaultwarden/
├── defaults/
│ └── main.yml # Default variables
├── handlers/
│ └── main.yml # Service restart handlers
├── meta/
│ └── main.yml # Role dependencies
├── tasks/
│ ├── main.yml # Main orchestration
│ ├── user.yml # User and directory setup
│ └── database.yml # PostgreSQL setup
├── templates/
│ ├── vaultwarden.container # Quadlet container definition
│ ├── vaultwarden.env.j2 # Environment configuration
│ └── vaultwarden.caddy.j2 # Caddy reverse proxy config
└── README.md # This file
```
## References
- [Vaultwarden Documentation](https://github.com/dani-garcia/vaultwarden/wiki)
- [PostgreSQL Backend Guide](https://github.com/dani-garcia/vaultwarden/wiki/Using-the-PostgreSQL-Backend)
- [SSO Configuration](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect)
- [Socket Permissions Architecture](../../docs/socket-permissions-architecture.md)
## License
MIT

View File

@@ -0,0 +1,171 @@
# Vaultwarden Role - Required Vault Variables
This document lists all vault-encrypted variables required by the Vaultwarden role.
## Required Variables
These variables **must** be defined in your vault file (e.g., `group_vars/homelab/vault.yml`):
### Database Credentials
```yaml
# PostgreSQL database password for vaultwarden user
vault_vaultwarden_db_password: "your-secure-database-password-here"
```
**Generation**:
```bash
openssl rand -base64 32
```
### Admin Panel Access
```yaml
# Plain text admin token (will be hashed during deployment)
vault_vaultwarden_admin_token: "your-secret-admin-token"
```
**Generation**:
```bash
# Generate a secure random token
openssl rand -base64 32
```
**Important**:
- Store as **plain text** in vault (the role will hash it automatically)
- Use the same token to access `/admin` panel
- The token is automatically hashed on the **control node** using argon2id
- Hashing uses OWASP recommended settings (m=19456, t=2, p=1)
- Hashing is **idempotent**: same token always produces same hash
- The `argon2` package is automatically installed if not present on control node
- Never commit the vault file unencrypted to git
## Optional Variables
These variables are only needed if you enable specific features:
### SMTP Configuration
Required if `vaultwarden_smtp_enabled: true`:
```yaml
# SMTP password for sending emails
vault_vaultwarden_smtp_password: "smtp-password-here"
```
### SSO Integration (Authentik)
> ⚠️ **SSO Feature Status (December 2025)**
>
> SSO is only available in `vaultwarden/server:testing` images (not in stable yet).
> This role is configured with SSO ready for when it reaches stable release.
>
> Current deployment uses `vaultwarden_version: "latest"` (stable) - SSO credentials
> below are configured but SSO will not appear until feature reaches stable.
Required if `vaultwarden_sso_enabled: true`:
```yaml
# OAuth2 Client ID from Authentik
vault_vaultwarden_sso_client_id: "your-client-id-here"
# OAuth2 Client Secret from Authentik
vault_vaultwarden_sso_client_secret: "your-client-secret-here"
```
**Setup** (following [Authentik integration guide](https://integrations.goauthentik.io/security/vaultwarden/)):
1. Create OAuth2/OpenID Provider in Authentik:
- Redirect URI: `https://vault.jnss.me/identity/connect/oidc-signin` (exact match)
- Add scope mappings: `openid`, `email`, `profile`, `offline_access` (required)
- Access token validity: > 5 minutes
- Note the **application slug** from the provider URL
2. Copy Client ID and Secret from Authentik
3. Add credentials to vault file
4. Set the SSO authority URL in role configuration:
```yaml
vaultwarden_sso_enabled: true
vaultwarden_sso_authority: "https://auth.jnss.me/application/o/<your-slug>/"
```
5. Deploy the role
6. **Wait for SSO to reach stable**, or use `vaultwarden_version: "testing"` to test now
**Important**:
- The SSO authority must include the full path with application slug
- The `offline_access` scope mapping is **required** for Vaultwarden SSO
- Access token must be valid for more than 5 minutes
- SSO is configured and ready but will activate when stable release includes it
## Example Vault File
```yaml
---
# group_vars/homelab/vault.yml (encrypted with ansible-vault)
# Vaultwarden - Database
vault_vaultwarden_db_password: "xK9mP2nR5tY8wQ3vZ7cB6sA4dF1gH0jL"
# Vaultwarden - Admin Panel (plain text, will be hashed automatically)
vault_vaultwarden_admin_token: "MySecureAdminToken123!"
# Vaultwarden - SMTP (optional)
vault_vaultwarden_smtp_password: "smtp-app-password-here"
# Vaultwarden - SSO (optional)
vault_vaultwarden_sso_client_id: "vaultwarden"
vault_vaultwarden_sso_client_secret: "sso-secret-from-authentik"
```
## Vault File Management
### Encrypt Vault File
```bash
ansible-vault encrypt group_vars/homelab/vault.yml
```
### Edit Vault File
```bash
ansible-vault edit group_vars/homelab/vault.yml
```
### Decrypt Vault File (temporary)
```bash
ansible-vault decrypt group_vars/homelab/vault.yml
# Make changes
ansible-vault encrypt group_vars/homelab/vault.yml
```
## Security Best Practices
1. **Never commit unencrypted vault files**
2. **Use strong passwords** (32+ characters for database, admin token)
3. **Rotate credentials periodically** (especially admin token)
4. **Limit vault password access** (use password manager)
5. **Use separate passwords** for different services
6. **Back up vault password** (secure location, not in git)
## Verifying Variables
Test if variables are properly loaded:
```bash
ansible -m debug -a "var=vault_vaultwarden_db_password" homelab --ask-vault-pass
```
## Troubleshooting
### Variable not found error
- Ensure vault file is in correct location: `group_vars/homelab/vault.yml`
- Verify file is encrypted: `file group_vars/homelab/vault.yml`
- Check variable name matches exactly (case-sensitive)
- Provide vault password with `--ask-vault-pass`
### Admin token not working
- Verify the plain text token in vault matches what you're entering
- Check for extra whitespace in vault file
- Ensure the token was hashed successfully during deployment (check ansible output)
- Try redeploying the role to regenerate the hash

View File

@@ -0,0 +1,109 @@
---
# =================================================================
# Vaultwarden Password Manager Role - Default Variables
# =================================================================
# Self-contained Vaultwarden deployment with Podman and PostgreSQL
# =================================================================
# Service Configuration
# =================================================================
# Service user and directories
vaultwarden_user: vaultwarden
vaultwarden_group: vaultwarden
vaultwarden_home: /opt/vaultwarden
vaultwarden_data_dir: "{{ vaultwarden_home }}/data"
# Container configuration
# NOTE: SSO feature is only available in "testing" tag (as of Dec 2025)
# Using "latest" (stable) means SSO will not appear even if configured
# SSO settings below are configured and ready for when feature reaches stable
vaultwarden_version: "latest"
vaultwarden_image: "vaultwarden/server"
# Service management
vaultwarden_service_enabled: true
vaultwarden_service_state: "started"
# =================================================================
# Database Configuration (Self-managed)
# =================================================================
vaultwarden_db_name: "vaultwarden"
vaultwarden_db_user: "vaultwarden"
vaultwarden_db_password: "{{ vault_vaultwarden_db_password }}"
# =================================================================
# Network Configuration
# =================================================================
vaultwarden_domain: "vault.jnss.me"
vaultwarden_http_port: 8080
# =================================================================
# Vaultwarden Core Configuration
# =================================================================
# Admin panel access token (plain text, will be hashed during deployment)
vaultwarden_admin_token_plain: "{{ vault_vaultwarden_admin_token }}"
# Registration and invitation controls
vaultwarden_signups_allowed: false # Disable open registration
vaultwarden_invitations_allowed: true # Allow existing users to invite
vaultwarden_show_password_hint: false # Don't show password hints
# WebSocket support for live sync
vaultwarden_websocket_enabled: true
# =================================================================
# Email Configuration (Optional)
# =================================================================
vaultwarden_smtp_enabled: true
vaultwarden_smtp_host: "smtp.titan.email"
vaultwarden_smtp_port: 587
vaultwarden_smtp_from: "hello@jnss.me"
vaultwarden_smtp_username: "hello@jnss.me"
vaultwarden_smtp_password: "{{ vault_vaultwarden_smtp_password | default('') }}"
vaultwarden_smtp_security: "starttls" # Options: starttls, force_tls, off
# =================================================================
# SSO Configuration (Optional - Authentik Integration)
# =================================================================
vaultwarden_sso_enabled: false
# SSO Provider Configuration (Authentik)
vaultwarden_sso_client_id: "{{ vault_vaultwarden_sso_client_id | default('') }}"
vaultwarden_sso_client_secret: "{{ vault_vaultwarden_sso_client_secret | default('') }}"
# Authority must include full path with application slug
vaultwarden_sso_authority: "https://{{ authentik_domain }}/application/o/vaultwarden/"
vaultwarden_sso_scopes: "openid email profile offline_access"
# Additional SSO settings (per Authentik integration guide)
vaultwarden_sso_only: false # Set to true to disable email+password login and require SSO
vaultwarden_sso_signups_match_email: true # Match first SSO login to existing account by email
vaultwarden_sso_allow_unknown_email_verification: false
vaultwarden_sso_client_cache_expiration: 0
# Domain whitelist for SSO signups (comma-separated domains, empty = all)
vaultwarden_sso_signups_domains_whitelist: ""
# =================================================================
# Caddy Integration
# =================================================================
# Caddy configuration (assumes caddy role provides these variables)
caddy_sites_enabled_dir: "/etc/caddy/sites-enabled"
caddy_log_dir: "/var/log/caddy"
caddy_user: "caddy"
# =================================================================
# Infrastructure Dependencies (Read-only)
# =================================================================
# PostgreSQL socket configuration (managed by postgresql role)
postgresql_unix_socket_directories: "/var/run/postgresql"
postgresql_client_group: "postgres-clients"
postgresql_port: 5432
postgresql_unix_socket_enabled: true

View File

@@ -0,0 +1,16 @@
---
# Vaultwarden Password Manager - Service Handlers
- name: reload systemd
systemd:
daemon_reload: true
- name: restart vaultwarden
systemd:
name: vaultwarden
state: restarted
- name: reload caddy
systemd:
name: caddy
state: reloaded

View File

@@ -0,0 +1,25 @@
---
# Vaultwarden Password Manager - Role Metadata
dependencies:
- role: postgresql
- role: caddy
galaxy_info:
author: Rick Infrastructure Team
description: Vaultwarden password manager deployment with PostgreSQL and Caddy
license: MIT
min_ansible_version: "2.14"
platforms:
- name: ArchLinux
versions:
- all
galaxy_tags:
- vaultwarden
- bitwarden
- password-manager
- security
- postgresql
- podman

View File

@@ -0,0 +1,51 @@
---
# Database setup for Vaultwarden - PostgreSQL via Unix Socket
- name: Test PostgreSQL socket connectivity
postgresql_ping:
login_unix_socket: "{{ postgresql_unix_socket_directories }}"
login_user: "{{ vaultwarden_user }}"
become: true
become_user: "{{ vaultwarden_user }}"
- name: Create Vaultwarden database user via socket
postgresql_user:
name: "{{ vaultwarden_db_user }}"
password: "{{ vaultwarden_db_password }}"
login_unix_socket: "{{ postgresql_unix_socket_directories }}"
login_user: postgres
become: true
become_user: postgres
- name: Create Vaultwarden database via socket
postgresql_db:
name: "{{ vaultwarden_db_name }}"
owner: "{{ vaultwarden_db_user }}"
encoding: UTF8
template: template0
login_unix_socket: "{{ postgresql_unix_socket_directories }}"
login_user: postgres
become: true
become_user: postgres
- name: Grant Vaultwarden database privileges
postgresql_privs:
db: "{{ vaultwarden_db_name }}"
privs: ALL
type: database
role: "{{ vaultwarden_db_user }}"
login_unix_socket: "{{ postgresql_unix_socket_directories }}"
login_user: postgres
become: true
become_user: postgres
- name: Display database setup status
debug:
msg: |
Vaultwarden database setup complete!
Database: {{ vaultwarden_db_name }}
User: {{ vaultwarden_db_user }}
Connection: Unix socket ({{ postgresql_unix_socket_directories }})
Ready for Vaultwarden container deployment

View File

@@ -0,0 +1,57 @@
---
# Hash admin token on Ansible control node using argon2
- name: Check if argon2 is available on control node
command: which argon2
register: argon2_check
delegate_to: localhost
become: false
changed_when: false
failed_when: false
- name: Install argon2 on control node if not present
package:
name: argon2
state: present
delegate_to: localhost
become: false
when: argon2_check.rc != 0
run_once: true
- name: Generate deterministic salt from domain
set_fact:
vaultwarden_salt_source: "{{ vaultwarden_domain }}-{{ vaultwarden_sso_authority }}"
no_log: true
- name: Create base64-encoded salt for argon2
shell: echo -n "{{ vaultwarden_salt_source }}" | sha256sum | cut -d' ' -f1 | head -c 22
register: admin_token_salt
delegate_to: localhost
become: false
changed_when: false
no_log: true
- name: Hash admin token using argon2 (OWASP preset)
shell: echo -n "{{ vaultwarden_admin_token_plain }}" | argon2 "{{ admin_token_salt.stdout }}" -id -t 2 -k 19456 -p 1 -e
register: admin_token_hash_result
delegate_to: localhost
become: false
changed_when: false
no_log: true
- name: Extract hashed admin token
set_fact:
vaultwarden_admin_token_hashed: "{{ admin_token_hash_result.stdout | trim }}"
no_log: true
- name: Display token hash status
debug:
msg: |
Admin token hashed successfully on control node
Hash algorithm: argon2id
Preset: OWASP (m=19456, t=2, p=1)
Format: PHC string (Vaultwarden compatible)
Idempotent: Same token always produces same hash
The hashed token will be used in the environment configuration

View File

@@ -0,0 +1,108 @@
---
# Vaultwarden Password Manager Role - Main Tasks
# Self-contained deployment with Podman and Unix sockets
- name: Setup vaultwarden user and directories
include_tasks: user.yml
tags: [user, setup]
- name: Setup database access and permissions
include_tasks: database.yml
tags: [database, setup]
- name: Pull vaultwarden container image
containers.podman.podman_image:
name: "{{ vaultwarden_image }}:{{ vaultwarden_version }}"
state: present
tags: [containers, image-pull]
- name: Hash admin token on host
include_tasks: hash_admin_token.yml
tags: [config, admin-token]
- name: Deploy environment configuration
template:
src: vaultwarden.env.j2
dest: "{{ vaultwarden_home }}/.env"
owner: "{{ vaultwarden_user }}"
group: "{{ vaultwarden_group }}"
mode: '0600'
backup: true
notify:
- restart vaultwarden
tags: [config]
- name: Create Quadlet systemd directory
file:
path: /etc/containers/systemd
state: directory
mode: '0755'
- name: Deploy Quadlet container file
template:
src: vaultwarden.container
dest: /etc/containers/systemd/vaultwarden.container
mode: '0644'
notify:
- reload systemd
- restart vaultwarden
tags: [containers, deployment]
- name: Deploy Caddy configuration
template:
src: vaultwarden.caddy.j2
dest: "{{ caddy_sites_enabled_dir }}/vaultwarden.caddy"
owner: root
group: "{{ caddy_user }}"
mode: '0644'
backup: true
notify: reload caddy
tags: [caddy, reverse-proxy]
- name: Ensure PostgreSQL is running
systemd:
name: postgresql
state: started
- name: Wait for PostgreSQL socket to be ready
wait_for:
path: "{{ postgresql_unix_socket_directories }}/.s.PGSQL.{{ postgresql_port }}"
timeout: 30
when: postgresql_unix_socket_enabled
- name: Enable and start Vaultwarden service (system scope)
systemd:
name: vaultwarden
enabled: "{{ vaultwarden_service_enabled }}"
state: "{{ vaultwarden_service_state }}"
daemon_reload: true
tags: [containers, service]
- name: Wait for Vaultwarden to be ready
uri:
url: "http://127.0.0.1:{{ vaultwarden_http_port }}/"
method: GET
status_code: [200, 302]
timeout: 30
retries: 10
delay: 15
register: vaultwarden_health_check
tags: [verification, health-check]
- name: Display Vaultwarden deployment status
debug:
msg: |
Vaultwarden Password Manager deployed successfully!
Domain: {{ vaultwarden_domain }}
Database: {{ vaultwarden_db_name }} (Unix socket)
Container: {{ vaultwarden_image }}:{{ vaultwarden_version }}
Admin Panel: https://{{ vaultwarden_domain }}/admin
Ready for user registration and password management!
Next Steps:
- Access https://{{ vaultwarden_domain }}/admin with your admin token
- Configure additional settings (SMTP, SSO, etc.)
- Invite users or create accounts
tags: [verification]

View File

@@ -0,0 +1,28 @@
---
# Vaultwarden User Management - Service-Specific User Setup
- name: Create vaultwarden group
group:
name: "{{ vaultwarden_group }}"
system: true
- name: Create vaultwarden user
user:
name: "{{ vaultwarden_user }}"
group: "{{ vaultwarden_group }}"
system: true
shell: /bin/bash
home: "{{ vaultwarden_home }}"
create_home: true
comment: "Vaultwarden password manager service"
- name: Create vaultwarden directories
file:
path: "{{ item }}"
state: directory
owner: "{{ vaultwarden_user }}"
group: "{{ vaultwarden_group }}"
mode: '0755'
loop:
- "{{ vaultwarden_home }}"
- "{{ vaultwarden_data_dir }}"

View File

@@ -0,0 +1,35 @@
# Vaultwarden Password Manager
{{ vaultwarden_domain }} {
# Notifications endpoint (WebSocket for live sync)
@websocket {
path /notifications/hub
}
reverse_proxy @websocket http://127.0.0.1:{{ vaultwarden_http_port }} {
header_up Upgrade {http.request.header.Upgrade}
header_up Connection {http.request.header.Connection}
}
# Regular HTTP traffic
reverse_proxy http://127.0.0.1:{{ vaultwarden_http_port }} {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto https
header_up X-Forwarded-For {remote_host}
}
# Security headers
header {
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
Referrer-Policy strict-origin-when-cross-origin
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
}
# Logging
log {
output file {{ caddy_log_dir }}/vaultwarden.log
level INFO
format json
}
}

View File

@@ -0,0 +1,26 @@
[Unit]
Description=Vaultwarden Password Manager Container
After=network-online.target postgresql.service
Wants=network-online.target
[Container]
ContainerName=vaultwarden
Image={{ vaultwarden_image }}:{{ vaultwarden_version }}
EnvironmentFile={{ vaultwarden_home }}/.env
# Volume mounts
# Application data (includes database, attachments, sends, icons, etc.)
Volume={{ vaultwarden_data_dir }}:/data:Z
# Infrastructure socket (PostgreSQL access with 777 permissions on host)
Volume={{ postgresql_unix_socket_directories }}:{{ postgresql_unix_socket_directories }}:Z
# Expose HTTP port to localhost only (Caddy will reverse proxy)
PublishPort=127.0.0.1:{{ vaultwarden_http_port }}:80
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,73 @@
# Vaultwarden Environment Configuration
# Generated by Ansible - DO NOT EDIT MANUALLY
# =================================================================
# Database Configuration (PostgreSQL via Unix Socket)
# =================================================================
DATABASE_URL=postgresql://{{ vaultwarden_db_user }}:{{ vaultwarden_db_password }}@/{{ vaultwarden_db_name }}?host={{ postgresql_unix_socket_directories }}
# =================================================================
# Domain Configuration
# =================================================================
DOMAIN=https://{{ vaultwarden_domain }}
# =================================================================
# Admin Configuration
# =================================================================
ADMIN_TOKEN={{ vaultwarden_admin_token_hashed }}
# =================================================================
# Registration and Invitation Controls
# =================================================================
SIGNUPS_ALLOWED={{ vaultwarden_signups_allowed | lower }}
INVITATIONS_ALLOWED={{ vaultwarden_invitations_allowed | lower }}
SHOW_PASSWORD_HINT={{ vaultwarden_show_password_hint | lower }}
# =================================================================
# WebSocket Configuration (for live sync)
# =================================================================
WEBSOCKET_ENABLED={{ vaultwarden_websocket_enabled | lower }}
# =================================================================
# SMTP Configuration (Optional)
# =================================================================
{% if vaultwarden_smtp_enabled %}
SMTP_HOST={{ vaultwarden_smtp_host }}
SMTP_PORT={{ vaultwarden_smtp_port }}
SMTP_FROM={{ vaultwarden_smtp_from }}
SMTP_SECURITY={{ vaultwarden_smtp_security }}
{% if vaultwarden_smtp_username %}
SMTP_USERNAME={{ vaultwarden_smtp_username }}
SMTP_PASSWORD={{ vaultwarden_smtp_password }}
{% endif %}
{% endif %}
# =================================================================
# SSO Configuration (Optional - Authentik Integration)
# =================================================================
{% if vaultwarden_sso_enabled %}
SSO_ENABLED=true
SSO_ONLY={{ vaultwarden_sso_only | lower }}
SSO_CLIENT_ID={{ vaultwarden_sso_client_id }}
SSO_CLIENT_SECRET={{ vaultwarden_sso_client_secret }}
SSO_AUTHORITY={{ vaultwarden_sso_authority }}
SSO_SCOPES="{{ vaultwarden_sso_scopes }}"
SSO_SIGNUPS_MATCH_EMAIL={{ vaultwarden_sso_signups_match_email | lower }}
SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION={{ vaultwarden_sso_allow_unknown_email_verification | lower }}
SSO_CLIENT_CACHE_EXPIRATION={{ vaultwarden_sso_client_cache_expiration }}
{% if vaultwarden_sso_signups_domains_whitelist %}
SSO_SIGNUPS_DOMAINS_WHITELIST={{ vaultwarden_sso_signups_domains_whitelist }}
{% endif %}
{% endif %}
# =================================================================
# Security and Performance
# =================================================================
# Disable user registration via email (use admin panel or invitations)
SIGNUPS_VERIFY=false
# Log level (trace, debug, info, warn, error, off)
LOG_LEVEL=info
# Rocket configuration
ROCKET_WORKERS=10