feat: add Joplin Server stack for note-taking and synchronization
Added Joplin Server deployment at joplin.pivoine.art: **Joplin stack** (joplin.pivoine.art): - joplin: Note-taking server with multi-device sync - PostgreSQL backend for data persistence - End-to-end encryption support - Compatible with official Joplin clients (desktop, mobile, CLI) - Markdown-based notes with attachments Infrastructure updates: - Added joplin database to PostgreSQL init script - Updated compose.yaml to include joplin stack - Added JOPLIN_* environment variables to arty.yml - Added joplin-backup plan to restic (13th backup plan) - Updated restic/compose.yaml with joplin_data volume mount - Updated README.md and CLAUDE.md documentation All services integrated with Traefik for SSL termination and include Watchtower auto-update labels. Daily backups scheduled for 2 AM with 7 daily, 4 weekly, 6 monthly, and 2 yearly retention. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
30
CLAUDE.md
30
CLAUDE.md
@@ -21,6 +21,7 @@ Root `compose.yaml` uses Docker Compose's `include` directive to orchestrate mul
|
|||||||
- **stash**: Filestash web-based file manager
|
- **stash**: Filestash web-based file manager
|
||||||
- **links**: Linkwarden bookmark manager (PostgreSQL + Meilisearch)
|
- **links**: Linkwarden bookmark manager (PostgreSQL + Meilisearch)
|
||||||
- **vault**: Vaultwarden password manager (SQLite)
|
- **vault**: Vaultwarden password manager (SQLite)
|
||||||
|
- **joplin**: Joplin Server note-taking and sync platform (PostgreSQL)
|
||||||
- **restic**: Backrest backup system with restic backend
|
- **restic**: Backrest backup system with restic backend
|
||||||
- **sablier**: Dynamic scaling plugin for Traefik
|
- **sablier**: Dynamic scaling plugin for Traefik
|
||||||
- **vpn**: WireGuard VPN (wg-easy)
|
- **vpn**: WireGuard VPN (wg-easy)
|
||||||
@@ -57,6 +58,7 @@ Services expose themselves via Docker labels:
|
|||||||
- Creates `umami` database for Track analytics
|
- Creates `umami` database for Track analytics
|
||||||
- Creates `n8n` database for workflow automation
|
- Creates `n8n` database for workflow automation
|
||||||
- Creates `linkwarden` database for Links bookmark manager
|
- Creates `linkwarden` database for Links bookmark manager
|
||||||
|
- Creates `joplin` database for Joplin Server
|
||||||
- Grants privileges to `$DB_USER`
|
- Grants privileges to `$DB_USER`
|
||||||
|
|
||||||
## Common Commands
|
## Common Commands
|
||||||
@@ -221,6 +223,30 @@ Vaultwarden password manager (Bitwarden-compatible server):
|
|||||||
- Enable 2FA for all accounts
|
- Enable 2FA for all accounts
|
||||||
- Access admin panel at `/admin` (requires `ADMIN_TOKEN` in `.env`)
|
- Access admin panel at `/admin` (requires `ADMIN_TOKEN` in `.env`)
|
||||||
|
|
||||||
|
### Joplin (joplin/compose.yaml)
|
||||||
|
Joplin Server note-taking and synchronization platform:
|
||||||
|
- **joplin**: Joplin Server app exposed at `joplin.pivoine.art:22300`
|
||||||
|
- Self-hosted sync server for Joplin note-taking clients
|
||||||
|
- End-to-end encryption support for notebooks
|
||||||
|
- Multi-device synchronization (desktop, mobile, CLI)
|
||||||
|
- Markdown-based notes with attachments
|
||||||
|
- PostgreSQL backend for data persistence
|
||||||
|
- Compatible with official Joplin clients (Windows, macOS, Linux, Android, iOS)
|
||||||
|
- Data persisted in PostgreSQL `joplin` database
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
- **APP_BASE_URL**: `https://joplin.pivoine.art` (required for client synchronization)
|
||||||
|
- **APP_PORT**: `22300` (Joplin Server default port)
|
||||||
|
- **DB_CLIENT**: `pg` (PostgreSQL database driver)
|
||||||
|
- Uses shared core PostgreSQL instance
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
|
1. Access https://joplin.pivoine.art to create an account
|
||||||
|
2. In Joplin desktop/mobile app, go to Settings → Synchronization
|
||||||
|
3. Select "Joplin Server" as sync target
|
||||||
|
4. Enter server URL: `https://joplin.pivoine.art`
|
||||||
|
5. Enter email and password created in step 1
|
||||||
|
|
||||||
### Restic (restic/compose.yaml)
|
### Restic (restic/compose.yaml)
|
||||||
Backrest backup system with restic backend:
|
Backrest backup system with restic backend:
|
||||||
- **backrest**: Backrest web UI exposed at `restic.pivoine.art:9898`
|
- **backrest**: Backrest web UI exposed at `restic.pivoine.art:9898`
|
||||||
@@ -290,6 +316,10 @@ Backrest backup system with restic backend:
|
|||||||
- Path: `/volumes/vaultwarden_data`
|
- Path: `/volumes/vaultwarden_data`
|
||||||
- Retention: 7 daily, 4 weekly, 12 monthly, 3 yearly
|
- Retention: 7 daily, 4 weekly, 12 monthly, 3 yearly
|
||||||
|
|
||||||
|
13. **joplin-backup** (2 AM daily)
|
||||||
|
- Path: `/volumes/joplin_data`
|
||||||
|
- Retention: 7 daily, 4 weekly, 6 monthly, 2 yearly
|
||||||
|
|
||||||
**Volume Mounting**:
|
**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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ The **Falcon** is a state-of-the-art containerized starship, powered by Docker's
|
|||||||
| **STASH** | *Universal file management portal* | [stash.pivoine.art](https://stash.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) |
|
| **LINKS** | *Interstellar bookmark archive* | [links.pivoine.art](https://links.pivoine.art) |
|
||||||
| **VAULT** | *Encrypted password vault* | [vault.pivoine.art](https://vault.pivoine.art) |
|
| **VAULT** | *Encrypted password vault* | [vault.pivoine.art](https://vault.pivoine.art) |
|
||||||
|
| **JOPLIN** | *Note-taking server & sync hub* | [joplin.pivoine.art](https://joplin.pivoine.art) |
|
||||||
| **RESTIC** | *Automated backup vault system* | [restic.pivoine.art](https://restic.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) |
|
| **PROXY** | *Shield control dashboard* | [proxy.pivoine.art](https://proxy.pivoine.art) |
|
||||||
| **VPN** | *Cloaking device network* | [vpn.pivoine.art](https://vpn.pivoine.art) |
|
| **VPN** | *Cloaking device network* | [vpn.pivoine.art](https://vpn.pivoine.art) |
|
||||||
@@ -72,7 +73,8 @@ The **Falcon** is a state-of-the-art containerized starship, powered by Docker's
|
|||||||
│ ├─ Directus Sector Database │
|
│ ├─ Directus Sector Database │
|
||||||
│ ├─ Umami Analytics Vault │
|
│ ├─ Umami Analytics Vault │
|
||||||
│ ├─ n8n Workflow Engine Database │
|
│ ├─ n8n Workflow Engine Database │
|
||||||
│ └─ Linkwarden Bookmark Archive │
|
│ ├─ Linkwarden Bookmark Archive │
|
||||||
|
│ └─ Joplin Note-taking Server Database │
|
||||||
├─────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────┤
|
||||||
│ ⚡ REDIS CACHE HYPERDRIVE │
|
│ ⚡ REDIS CACHE HYPERDRIVE │
|
||||||
│ └─ Warp-speed data acceleration │
|
│ └─ Warp-speed data acceleration │
|
||||||
@@ -206,6 +208,7 @@ THE FALCON (falcon_network)
|
|||||||
│ ├─ Filestash Files [stash.pivoine.art]
|
│ ├─ Filestash Files [stash.pivoine.art]
|
||||||
│ ├─ Linkwarden Marks [links.pivoine.art]
|
│ ├─ Linkwarden Marks [links.pivoine.art]
|
||||||
│ ├─ Vaultwarden Vault [vault.pivoine.art]
|
│ ├─ Vaultwarden Vault [vault.pivoine.art]
|
||||||
|
│ ├─ Joplin Sync Server [joplin.pivoine.art]
|
||||||
│ ├─ Backrest Backups [restic.pivoine.art]
|
│ ├─ Backrest Backups [restic.pivoine.art]
|
||||||
│ └─ WireGuard VPN [vpn.pivoine.art]
|
│ └─ WireGuard VPN [vpn.pivoine.art]
|
||||||
│
|
│
|
||||||
@@ -221,6 +224,7 @@ THE FALCON (falcon_network)
|
|||||||
├─ linkwarden_data → Bookmark archives
|
├─ linkwarden_data → Bookmark archives
|
||||||
├─ meili_data → Search index database
|
├─ meili_data → Search index database
|
||||||
├─ vaultwarden_data → Encrypted password vault
|
├─ vaultwarden_data → Encrypted password vault
|
||||||
|
├─ joplin_data → Note-taking server data
|
||||||
├─ backrest_data → Backup system state
|
├─ backrest_data → Backup system state
|
||||||
├─ backrest_config → Backup configurations
|
├─ backrest_config → Backup configurations
|
||||||
└─ letsencrypt_data → Shield certificates
|
└─ letsencrypt_data → Shield certificates
|
||||||
|
|||||||
9
arty.yml
9
arty.yml
@@ -100,9 +100,16 @@ envs:
|
|||||||
VAULT_IMAGE: vaultwarden/server:latest
|
VAULT_IMAGE: vaultwarden/server:latest
|
||||||
VAULT_TRAEFIK_HOST: vault.pivoine.art
|
VAULT_TRAEFIK_HOST: vault.pivoine.art
|
||||||
VAULT_WEBSOCKET_ENABLED: true
|
VAULT_WEBSOCKET_ENABLED: true
|
||||||
VAULT_SIGNUPS_ALLOWED: false
|
VAULT_SIGNUPS_ALLOWED: true
|
||||||
VAULT_INVITATIONS_ALLOWED: true
|
VAULT_INVITATIONS_ALLOWED: true
|
||||||
VAULT_SHOW_PASSWORD_HINT: false
|
VAULT_SHOW_PASSWORD_HINT: false
|
||||||
|
# Joplin
|
||||||
|
JOPLIN_TRAEFIK_ENABLED: true
|
||||||
|
JOPLIN_COMPOSE_PROJECT_NAME: joplin
|
||||||
|
JOPLIN_IMAGE: joplin/server:latest
|
||||||
|
JOPLIN_TRAEFIK_HOST: joplin.pivoine.art
|
||||||
|
JOPLIN_APP_PORT: 22300
|
||||||
|
JOPLIN_DB_NAME: joplin
|
||||||
# Proxy
|
# Proxy
|
||||||
PROXY_COMPOSE_PROJECT_NAME: proxy
|
PROXY_COMPOSE_PROJECT_NAME: proxy
|
||||||
PROXY_DOCKER_IMAGE: traefik:latest
|
PROXY_DOCKER_IMAGE: traefik:latest
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ include:
|
|||||||
- stash/compose.yaml
|
- stash/compose.yaml
|
||||||
- links/compose.yaml
|
- links/compose.yaml
|
||||||
- vault/compose.yaml
|
- vault/compose.yaml
|
||||||
|
- joplin/compose.yaml
|
||||||
- restic/compose.yaml
|
- restic/compose.yaml
|
||||||
- umami/compose.yaml
|
- umami/compose.yaml
|
||||||
- sablier/compose.yaml
|
- sablier/compose.yaml
|
||||||
|
|||||||
@@ -25,16 +25,21 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E
|
|||||||
SELECT 'CREATE DATABASE linkwarden'
|
SELECT 'CREATE DATABASE linkwarden'
|
||||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'linkwarden')\gexec
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'linkwarden')\gexec
|
||||||
|
|
||||||
|
-- Joplin note-taking server database
|
||||||
|
SELECT 'CREATE DATABASE joplin'
|
||||||
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'joplin')\gexec
|
||||||
|
|
||||||
-- Grant privileges to all databases
|
-- Grant privileges to all databases
|
||||||
GRANT ALL PRIVILEGES ON DATABASE directus TO $POSTGRES_USER;
|
GRANT ALL PRIVILEGES ON DATABASE directus TO $POSTGRES_USER;
|
||||||
GRANT ALL PRIVILEGES ON DATABASE umami TO $POSTGRES_USER;
|
GRANT ALL PRIVILEGES ON DATABASE umami TO $POSTGRES_USER;
|
||||||
GRANT ALL PRIVILEGES ON DATABASE n8n TO $POSTGRES_USER;
|
GRANT ALL PRIVILEGES ON DATABASE n8n TO $POSTGRES_USER;
|
||||||
GRANT ALL PRIVILEGES ON DATABASE linkwarden TO $POSTGRES_USER;
|
GRANT ALL PRIVILEGES ON DATABASE linkwarden TO $POSTGRES_USER;
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE joplin TO $POSTGRES_USER;
|
||||||
|
|
||||||
-- Log success
|
-- Log success
|
||||||
SELECT 'Compose databases initialized:' AS status;
|
SELECT 'Compose databases initialized:' AS status;
|
||||||
SELECT datname FROM pg_database
|
SELECT datname FROM pg_database
|
||||||
WHERE datname IN ('directus', 'umami', 'n8n', 'linkwarden')
|
WHERE datname IN ('directus', 'umami', 'n8n', 'linkwarden', 'joplin')
|
||||||
ORDER BY datname;
|
ORDER BY datname;
|
||||||
EOSQL
|
EOSQL
|
||||||
|
|
||||||
@@ -47,4 +52,5 @@ echo " • directus - Sexy application database"
|
|||||||
echo " • umami - Tracking database"
|
echo " • umami - Tracking database"
|
||||||
echo " • n8n - Workflow automation database"
|
echo " • n8n - Workflow automation database"
|
||||||
echo " • linkwarden - Bookmark manager database"
|
echo " • linkwarden - Bookmark manager database"
|
||||||
|
echo " • joplin - Note-taking server database"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
38
joplin/compose.yaml
Normal file
38
joplin/compose.yaml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
services:
|
||||||
|
joplin:
|
||||||
|
image: ${JOPLIN_IMAGE:-joplin/server:latest}
|
||||||
|
container_name: ${JOPLIN_COMPOSE_PROJECT_NAME}_app
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
TZ: ${TIMEZONE:-Europe/Berlin}
|
||||||
|
APP_PORT: ${JOPLIN_APP_PORT:-22300}
|
||||||
|
APP_BASE_URL: https://${JOPLIN_TRAEFIK_HOST}
|
||||||
|
DB_CLIENT: pg
|
||||||
|
POSTGRES_HOST: ${CORE_DB_HOST}
|
||||||
|
POSTGRES_PORT: ${CORE_DB_PORT}
|
||||||
|
POSTGRES_DATABASE: ${JOPLIN_DB_NAME}
|
||||||
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- compose_network
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
labels:
|
||||||
|
- 'traefik.enable=${JOPLIN_TRAEFIK_ENABLED}'
|
||||||
|
- 'traefik.http.middlewares.${JOPLIN_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https'
|
||||||
|
- 'traefik.http.routers.${JOPLIN_COMPOSE_PROJECT_NAME}-web.middlewares=${JOPLIN_COMPOSE_PROJECT_NAME}-redirect-web-secure'
|
||||||
|
- 'traefik.http.routers.${JOPLIN_COMPOSE_PROJECT_NAME}-web.rule=Host(`${JOPLIN_TRAEFIK_HOST}`)'
|
||||||
|
- 'traefik.http.routers.${JOPLIN_COMPOSE_PROJECT_NAME}-web.entrypoints=web'
|
||||||
|
- 'traefik.http.routers.${JOPLIN_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${JOPLIN_TRAEFIK_HOST}`)'
|
||||||
|
- 'traefik.http.routers.${JOPLIN_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver'
|
||||||
|
- 'traefik.http.routers.${JOPLIN_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure'
|
||||||
|
- 'traefik.http.middlewares.${JOPLIN_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true'
|
||||||
|
- 'traefik.http.routers.${JOPLIN_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${JOPLIN_COMPOSE_PROJECT_NAME}-web-secure-compress'
|
||||||
|
- 'traefik.http.services.${JOPLIN_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=22300'
|
||||||
|
- 'traefik.docker.network=${NETWORK_NAME}'
|
||||||
|
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
compose_network:
|
||||||
|
name: ${NETWORK_NAME}
|
||||||
|
external: true
|
||||||
@@ -29,6 +29,7 @@ services:
|
|||||||
- backup_linkwarden_meili_data:/volumes/linkwarden_meili_data:ro
|
- backup_linkwarden_meili_data:/volumes/linkwarden_meili_data:ro
|
||||||
- backup_letsencrypt_data:/volumes/letsencrypt_data:ro
|
- backup_letsencrypt_data:/volumes/letsencrypt_data:ro
|
||||||
- backup_vaultwarden_data:/volumes/vaultwarden_data:ro
|
- backup_vaultwarden_data:/volumes/vaultwarden_data:ro
|
||||||
|
- backup_joplin_data:/volumes/joplin_data:ro
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
TZ: ${TIMEZONE:-Europe/Berlin}
|
TZ: ${TIMEZONE:-Europe/Berlin}
|
||||||
@@ -108,6 +109,9 @@ volumes:
|
|||||||
backup_vaultwarden_data:
|
backup_vaultwarden_data:
|
||||||
name: vault_data
|
name: vault_data
|
||||||
external: true
|
external: true
|
||||||
|
backup_joplin_data:
|
||||||
|
name: joplin_data
|
||||||
|
external: true
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
compose_network:
|
compose_network:
|
||||||
|
|||||||
@@ -210,6 +210,22 @@
|
|||||||
"yearly": 3
|
"yearly": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "joplin-backup",
|
||||||
|
"repo": "hidrive-backup",
|
||||||
|
"paths": ["/volumes/joplin_data"],
|
||||||
|
"schedule": {
|
||||||
|
"cron": "0 2 * * *"
|
||||||
|
},
|
||||||
|
"retention": {
|
||||||
|
"policyTimeBucketed": {
|
||||||
|
"daily": 7,
|
||||||
|
"weekly": 4,
|
||||||
|
"monthly": 6,
|
||||||
|
"yearly": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user