feat: add Vaultwarden password manager stack

Added self-hosted password manager to The Falcon infrastructure:

**Vault Stack** (vault.pivoine.art):
- Vaultwarden (Bitwarden-compatible server)
- SQLite database for password storage
- WebSocket support for real-time sync
- TOTP and WebAuthn/U2F 2FA support
- Browser extensions and mobile apps compatible

**Configuration:**
- Domain: https://vault.pivoine.art
- Signups: Disabled (invite-only for security)
- Invitations: Enabled
- Password hints: Disabled (security best practice)
- First user becomes admin

**Backup Integration:**
- Added vaultwarden-backup plan to Restic
- Schedule: 8 AM daily (same as letsencrypt)
- Retention: 7 daily, 4 weekly, 12 monthly, 3 yearly
- Backup volume: vault_data mounted read-only

**Infrastructure Updates:**
- Created vault/compose.yaml following stack pattern
- Added VAULT_* environment variables to arty.yml
- Updated compose.yaml to include vault stack
- Added backup_vaultwarden_data volume to restic
- Updated restic/config.json with 12th backup plan

**Documentation:**
- Added Vault to CORE SYSTEMS in README
- Added to ship architecture diagram
- Documented in CLAUDE.md with configuration details
- Updated volume management sections
- Backup count increased from 11 to 12 plans

Critical data backed up with long retention (3 years yearly).
Compatible with official Bitwarden clients on all platforms.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-06 11:15:12 +01:00
parent fcfe508698
commit 9b433e66ad
7 changed files with 101 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ Root `compose.yaml` uses Docker Compose's `include` directive to orchestrate mul
- **n8n**: Workflow automation platform (PostgreSQL)
- **stash**: Filestash web-based file manager
- **links**: Linkwarden bookmark manager (PostgreSQL + Meilisearch)
- **vault**: Vaultwarden password manager (SQLite)
- **restic**: Backrest backup system with restic backend
- **sablier**: Dynamic scaling plugin for Traefik
- **vpn**: WireGuard VPN (wg-easy)
@@ -197,6 +198,29 @@ Linkwarden bookmark manager with full-text search:
- `LINKS_NEXTAUTH_SECRET`: NextAuth.js secret for session encryption
- `LINKS_MEILI_MASTER_KEY`: Meilisearch master key for API authentication
### Vault (vault/compose.yaml)
Vaultwarden password manager (Bitwarden-compatible server):
- **vaultwarden**: Vaultwarden app exposed at `vault.pivoine.art:80`
- Self-hosted password manager compatible with Bitwarden clients
- Supports TOTP, WebAuthn/U2F two-factor authentication
- Secure password generation and sharing
- Browser extensions and mobile apps available
- Emergency access and organization support
- Data persisted in `vaultwarden_data` volume (SQLite database)
**Configuration**:
- **DOMAIN**: `https://vault.pivoine.art` (required for proper HTTPS operation)
- **WEBSOCKET_ENABLED**: `true` (enables real-time sync)
- **SIGNUPS_ALLOWED**: `false` (disable open registrations for security)
- **INVITATIONS_ALLOWED**: `true` (allow inviting users)
- **SHOW_PASSWORD_HINT**: `false` (security best practice)
**Important**:
- First user to register becomes the admin
- Use strong master password - it cannot be recovered
- Enable 2FA for all accounts
- Access admin panel at `/admin` (requires `ADMIN_TOKEN` in `.env`)
### Restic (restic/compose.yaml)
Backrest backup system with restic backend:
- **backrest**: Backrest web UI exposed at `restic.pivoine.art:9898`
@@ -262,6 +286,10 @@ Backrest backup system with restic backend:
- Path: `/volumes/letsencrypt_data`
- Retention: 7 daily, 4 weekly, 12 monthly, 3 yearly
12. **vaultwarden-backup** (8 AM daily)
- Path: `/volumes/vaultwarden_data`
- Retention: 7 daily, 4 weekly, 12 monthly, 3 yearly
**Volume Mounting**:
All Docker volumes are mounted read-only to `/volumes/` with prefixed names (e.g., `backup_core_postgres_data`) to avoid naming conflicts with other compose stacks.
@@ -296,6 +324,7 @@ Each service uses named volumes prefixed with project name:
- `n8n_n8n_data`: n8n workflow data
- `stash_filestash_data`: Filestash configuration and state
- `links_data`, `links_meili_data`: Linkwarden bookmarks and Meilisearch index
- `vault_data`: Vaultwarden password vault (SQLite database)
- `restic_data`, `restic_config`, `restic_cache`, `restic_tmp`: Backrest backup system
- `proxy_letsencrypt_data`: SSL certificates

View File

@@ -52,6 +52,7 @@ The **Falcon** is a state-of-the-art containerized starship, powered by Docker's
| **N8N** | *Automated workflow command center* | [n8n.pivoine.art](https://n8n.pivoine.art) |
| **STASH** | *Universal file management portal* | [stash.pivoine.art](https://stash.pivoine.art) |
| **LINKS** | *Interstellar bookmark archive* | [links.pivoine.art](https://links.pivoine.art) |
| **VAULT** | *Encrypted password vault* | [vault.pivoine.art](https://vault.pivoine.art) |
| **RESTIC** | *Automated backup vault system* | [restic.pivoine.art](https://restic.pivoine.art) |
| **PROXY** | *Shield control dashboard* | [proxy.pivoine.art](https://proxy.pivoine.art) |
| **VPN** | *Cloaking device network* | [vpn.pivoine.art](https://vpn.pivoine.art) |
@@ -204,6 +205,7 @@ THE FALCON (falcon_network)
│ ├─ n8n Workflows [n8n.pivoine.art]
│ ├─ Filestash Files [stash.pivoine.art]
│ ├─ Linkwarden Marks [links.pivoine.art]
│ ├─ Vaultwarden Vault [vault.pivoine.art]
│ ├─ Backrest Backups [restic.pivoine.art]
│ └─ WireGuard VPN [vpn.pivoine.art]
@@ -218,6 +220,7 @@ THE FALCON (falcon_network)
├─ filestash_data → File manager state
├─ linkwarden_data → Bookmark archives
├─ meili_data → Search index database
├─ vaultwarden_data → Encrypted password vault
├─ backrest_data → Backup system state
├─ backrest_config → Backup configurations
└─ letsencrypt_data → Shield certificates

View File

@@ -94,6 +94,15 @@ envs:
RESTIC_TRAEFIK_HOST: restic.pivoine.art
RESTIC_HOSTNAME: falcon
RESTIC_BACKUP_PATH: /mnt/hidrive/users/valknar/Backup
# Vault
VAULT_TRAEFIK_ENABLED: true
VAULT_COMPOSE_PROJECT_NAME: vault
VAULT_IMAGE: vaultwarden/server:latest
VAULT_TRAEFIK_HOST: vault.pivoine.art
VAULT_WEBSOCKET_ENABLED: true
VAULT_SIGNUPS_ALLOWED: false
VAULT_INVITATIONS_ALLOWED: true
VAULT_SHOW_PASSWORD_HINT: false
# Proxy
PROXY_COMPOSE_PROJECT_NAME: proxy
PROXY_DOCKER_IMAGE: traefik:latest

View File

@@ -8,6 +8,7 @@ include:
- n8n/compose.yaml
- stash/compose.yaml
- links/compose.yaml
- vault/compose.yaml
- restic/compose.yaml
- umami/compose.yaml
- sablier/compose.yaml

View File

@@ -28,6 +28,7 @@ services:
- backup_linkwarden_data:/volumes/linkwarden_data:ro
- backup_linkwarden_meili_data:/volumes/linkwarden_meili_data:ro
- backup_letsencrypt_data:/volumes/letsencrypt_data:ro
- backup_vaultwarden_data:/volumes/vaultwarden_data:ro
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
@@ -104,6 +105,9 @@ volumes:
backup_letsencrypt_data:
name: proxy_letsencrypt_data
external: true
backup_vaultwarden_data:
name: vault_data
external: true
networks:
compose_network:

View File

@@ -194,6 +194,22 @@
"yearly": 3
}
}
},
{
"id": "vaultwarden-backup",
"repo": "hidrive-backup",
"paths": ["/volumes/vaultwarden_data"],
"schedule": {
"cron": "0 8 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 12,
"yearly": 3
}
}
}
]
}

39
vault/compose.yaml Normal file
View File

@@ -0,0 +1,39 @@
services:
vaultwarden:
image: ${VAULT_IMAGE:-vaultwarden/server:latest}
container_name: ${VAULT_COMPOSE_PROJECT_NAME}_app
restart: unless-stopped
volumes:
- vaultwarden_data:/data
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
DOMAIN: https://${VAULT_TRAEFIK_HOST}
WEBSOCKET_ENABLED: ${VAULT_WEBSOCKET_ENABLED:-true}
SIGNUPS_ALLOWED: ${VAULT_SIGNUPS_ALLOWED:-false}
INVITATIONS_ALLOWED: ${VAULT_INVITATIONS_ALLOWED:-true}
SHOW_PASSWORD_HINT: ${VAULT_SHOW_PASSWORD_HINT:-false}
networks:
- compose_network
labels:
- 'traefik.enable=${VAULT_TRAEFIK_ENABLED}'
- 'traefik.http.middlewares.${VAULT_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${VAULT_COMPOSE_PROJECT_NAME}-web.middlewares=${VAULT_COMPOSE_PROJECT_NAME}-redirect-web-secure'
- 'traefik.http.routers.${VAULT_COMPOSE_PROJECT_NAME}-web.rule=Host(`${VAULT_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${VAULT_COMPOSE_PROJECT_NAME}-web.entrypoints=web'
- 'traefik.http.routers.${VAULT_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${VAULT_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${VAULT_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${VAULT_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure'
- 'traefik.http.middlewares.${VAULT_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true'
- 'traefik.http.routers.${VAULT_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${VAULT_COMPOSE_PROJECT_NAME}-web-secure-compress'
- 'traefik.http.services.${VAULT_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=80'
- 'traefik.docker.network=${NETWORK_NAME}'
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
volumes:
vaultwarden_data:
name: ${VAULT_COMPOSE_PROJECT_NAME}_data
networks:
compose_network:
name: ${NETWORK_NAME}
external: true