diff --git a/CLAUDE.md b/CLAUDE.md index fa25181..a0f921b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/README.md b/README.md index 47f9579..f99bc00 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/arty.yml b/arty.yml index 9d31c4d..83a3993 100644 --- a/arty.yml +++ b/arty.yml @@ -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 diff --git a/compose.yaml b/compose.yaml index 8d97bdb..b9db258 100644 --- a/compose.yaml +++ b/compose.yaml @@ -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 diff --git a/restic/compose.yaml b/restic/compose.yaml index f5b4b16..246821f 100644 --- a/restic/compose.yaml +++ b/restic/compose.yaml @@ -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: diff --git a/restic/config.json b/restic/config.json index d8629c6..c29341f 100644 --- a/restic/config.json +++ b/restic/config.json @@ -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 + } + } } ] } diff --git a/vault/compose.yaml b/vault/compose.yaml new file mode 100644 index 0000000..866e399 --- /dev/null +++ b/vault/compose.yaml @@ -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