Compare commits

...

39 Commits

Author SHA1 Message Date
valknar b69951f917 fix(help): align update subcommands and add backup descriptions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 23:49:57 +02:00
valknar d7829cb5ae docs: fix README backup/update/notifications sections
- Source .env before restic init so RESTIC_REPOSITORY is available
- Remove stale references to deleted _backup/.env and _update/.env
- Update Notifications section to point at root .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 21:13:00 +02:00
valknar 8ae9c9e878 fix(update): compare local image store IDs, not running container IDs
docker compose images -q reports the image IDs of currently running
containers, which don't change after a pull — so before == after always
and containers were never recreated.

Fix: resolve each service's image tag to its local SHA256 ID via
docker image inspect, which reads the local image store and correctly
reflects the newly pulled image. Falls back from 'config --images'
(compose v2.19+) to parsing 'config' yaml for older versions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 21:09:56 +02:00
valknar fcff6f3298 refactor: absorb _backup and _update into stacks.sh
- Inline update logic (pull → compare digests → up -d → prune → notify)
- Inline backup logic with dynamic Postgres detection: any running
  <stack>_db container is dumped using the <stack>/<stack> convention
- Systemd unit files are now generated on `install` from embedded
  heredocs pointing at stacks.sh itself — no external scripts needed
- Root .env (WEBHOOK_URL, RESTIC_REPOSITORY, RESTIC_PASSWORD) replaces
  the per-service .env files in _backup/ and _update/
- Remove _backup/ and _update/ directories entirely
- Update README accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 20:56:04 +02:00
valknar e3cd2df372 docs: document stacks.sh in README
Replace manual docker compose / systemctl snippets with stacks.sh
equivalents and add a dedicated section covering all commands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 19:55:05 +02:00
valknar 067d017ea6 feat(stacks): add --static flag to completion command
Bakes the current stack list into the generated completion script instead
of using runtime directory discovery. Useful for remote hosts where the
stacks dir path differs from the local repo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 19:52:55 +02:00
valknar 3251f27edb feat: add stacks.sh stack manager CLI
Bash script for managing Docker Compose stacks with glob filtering,
parallel execution, service management, scaffolding, and shell completion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 19:13:43 +02:00
valknar cf32e669cb fix(gitea): use .RunModeIsProd and hardcode Umami values in header template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 10:34:21 +02:00
valknar e613d766ec fix(gitea): revert to UMAMI_ID/UMAMI_SRC and use .UmamiSrc/.UmamiId template vars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 10:26:39 +02:00
valknar 53608a7195 fix(gitea): use .UmamiSrc/.UmamiScript template vars for Umami analytics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 10:24:57 +02:00
valknar 735adfabea feat(gitea): add conditional Umami analytics via env vars in header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 10:17:19 +02:00
valknar a9eb3de90d docs: add code stack to README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 18:18:17 +02:00
valknar c27a012d54 fix(code): consolidate volumes into single /home/coder mount
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 18:05:43 +02:00
valknar d367540eac fix(code): mount workspace volume to /home/coder/workspace
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 18:01:19 +02:00
valknar c0aa3acbfc fix(code): mount workspace volume to /home/coder for full home persistence
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 17:59:53 +02:00
valknar d9bbfea9c4 fix(code): remove separate workspace volume
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 17:53:22 +02:00
valknar 27c9e1c8a5 feat(code): add code-server stack with Traefik and Anthropic API support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 17:31:58 +02:00
valknar d4327bd152 fix(update): detect real image changes and improve webhook messages
Use image ID diff instead of grepping "Pulled" (which appears even
when images are already up to date). Add timestamp, stack count, and
updated/total ratio to all notification messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:58:55 +02:00
valknar 2130069836 fix(update): set executable bit on update.sh in git index
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:41:24 +02:00
valknar 6eb603205a fix(update): make update.sh executable and fix Pulled grep pattern
- Add +x permission to update.sh (was causing systemd 203/EXEC failure)
- Remove $ anchor from grep so trailing space in docker output is matched
- Add .data/ to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 16:19:36 +00:00
valknar f231dd5432 feat: externalize EMAIL_FROM and EMAIL_USER to .env for gitea and passbolt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 18:38:22 +02:00
valknar afec1c03f5 feat(gitea): set mailer FROM address
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 18:01:08 +02:00
valknar 9c7a34e9c0 feat: remove Affine stack
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 13:43:21 +02:00
valknar 40dfe5776a feat(gitea): add Umami analytics via direct custom header template
Mount templates/custom/ directly to Gitea's default GITEA_CUSTOM templates
path. No entrypoint override or envsubst needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:58:25 +02:00
valknar e5e6a2c302 feat(gitea): add Umami analytics via custom header template
Injects the Umami tracking script into every Gitea page using a custom
header.tmpl generated at startup via envsubst. Script URL and website ID
are externalized to UMAMI_SCRIPT_URL / UMAMI_WEBSITE_ID env vars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:49:26 +02:00
valknar 9c65cfc9e3 revert(passbolt): remove clock-skew patch — metadata key already created
The patched PublicKeyValidationService.php and its volume mount are
no longer needed now that the metadata key exists in the database.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:48:41 +02:00
valknar 15ce2e3f4b chore: remove accidentally committed log file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:47:34 +02:00
valknar 300c685d50 feat: remove Vaultwarden — migrated to Passbolt
Credentials migrated to Passbolt CE at passbolt.pivoine.art.
Removed stack files and README entry. Data wiped on VPS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:47:07 +02:00
valknar 5c398ee77c fix(passbolt): add 300s clock-skew tolerance to key creation date check
The isNotCreatedInTheFutureRule has zero tolerance, causing the
browser extension to fail when generating a metadata key if the
browser clock is even 1 second ahead of the server. Patching
isDateInFuture to allow 300 seconds tolerance and mounting the
file as a read-only volume so the fix survives image updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:16:56 +02:00
valknar 2e31c1dcc9 fix(passbolt): persist GPG keyring as volume to survive restarts
Passbolt's entrypoint creates /var/lib/passbolt/.gnupg/pubring.kbx
as root while PHP-FPM runs as www-data. Without a volume this file
is recreated with wrong ownership on every container recreate, breaking
all GPG operations. Mounting the dir as a volume keeps the chown
33:33 fix permanent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:44:20 +02:00
valknar 6f12bf9af7 fix(passbolt): disable metadata encryption for new instance setup
Passbolt 5.x's isNotCreatedInTheFutureRule has zero tolerance for
clock skew — even 1 second between browser and server causes the
metadata key creation to fail during first setup. Disabling the
automatic metadata setup for new instances allows the browser
extension to complete account setup successfully.

Encrypted metadata can be enabled from the admin panel post-setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:24:51 +02:00
valknar 758e69300f fix(passbolt): add TZ env var (Europe/Amsterdam)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:06:01 +02:00
valknar ae81935376 fix(passbolt): clean setup with correct GPG fingerprint
Passbolt 5.x does not auto-persist the server key fingerprint across
container restarts (no passbolt.php is written). The fingerprint env var
is required and corresponds to the key auto-generated on first clean start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 20:39:55 +02:00
valknar d8cfcd23d1 fix(passbolt): fix DB hostname, encoding, and GPG fingerprint
- Use container_name passbolt_db instead of service name db (service names
  are ambiguous on the shared falcon_network — 6 other stacks also have a
  service named db)
- Add DATASOURCES_DEFAULT_ENCODING=utf8 to override MySQL's utf8mb4 default
- Add DATASOURCES_QUOTE_IDENTIFIER=true for PostgreSQL identifier quoting
- Set PASSBOLT_GPG_SERVER_KEY_FINGERPRINT for the auto-generated server key
- Add PASSBOLT_GPG_SERVER_KEY_EMAIL for correct server key identity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 20:14:57 +02:00
valknar a1f0f7091b feat(passbolt): add Passbolt CE stack
Password manager with GPG encryption. Uses PostgreSQL for consistency
with other stacks. Backed up alongside existing databases. Vaultwarden
kept running during migration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 20:00:05 +02:00
valknar 31841d1ac3 fix(compose): remove empty labels keys left after watchtower label removal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 19:44:01 +02:00
valknar 4c522961a9 feat(_update): replace watchtower with custom nightly update script
Removes the watchtower container in favour of a host-side script that
runs daily at 2:00 AM via systemd timer.  Mirrors the _backup pattern:
auto-discovers stacks, pulls images, recreates changed containers,
prunes dangling images, and notifies via n8n → Telegram.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 19:42:25 +02:00
valknar cb241c9696 feat(affine): add SMTP config, AFFINE_SERVER_EXTERNAL_URL; remove blinko dir
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 06:57:45 +02:00
valknar 4b99e21a99 feat: replace blinko with affine, route notifications to telegram
- Add AFFiNE stack (affine.pivoine.art): main app, migration job,
  redis, pgvector postgres
- Remove blinko stack
- Update backup.sh: swap blinko_db → affine_db
- Update README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 06:28:27 +02:00
27 changed files with 1574 additions and 264 deletions
+6
View File
@@ -0,0 +1,6 @@
# Webhook for update/backup notifications (n8n → Telegram)
WEBHOOK_URL=https://n8n.example.com/webhook/your-webhook-id
# Restic backup repository and password
RESTIC_REPOSITORY=/mnt/hidrive/users/valknar/Backup/stacks
RESTIC_PASSWORD=changeme
+2 -1
View File
@@ -1,4 +1,5 @@
.claude .claude
.env .env
*.sql *.sql
*.log *.log
.data/
+85 -27
View File
@@ -10,20 +10,81 @@ Each stack is independently deployable with its own `compose.yml` and `.env`. Al
|---|---|---| |---|---|---|
| `traefik` | Reverse proxy, TLS termination | traefik | | `traefik` | Reverse proxy, TLS termination | traefik |
| `mailpit` | SMTP relay (no web UI) | mailpit | | `mailpit` | SMTP relay (no web UI) | mailpit |
| `watchtower` | Automatic container updates | watchtower |
| `umami` | Web analytics | umami, db | | `umami` | Web analytics | umami, db |
| `immich` | Photo & video management | immich, ml, redis, db | | `immich` | Photo & video management | immich, ml, redis, db |
| `blinko` | AI-powered personal notes | blinko, db |
| `n8n` | Workflow automation & notification relay | n8n, db | | `n8n` | Workflow automation & notification relay | n8n, db |
| `gitea` | Git hosting + CI runner | gitea, runner, db | | `gitea` | Git hosting + CI runner | gitea, runner, db |
| `coolify` | Deployment platform | coolify, realtime, redis, db | | `coolify` | Deployment platform | coolify, realtime, redis, db |
| `vaultwarden` | Password manager | vaultwarden | | `passbolt` | Password manager (GPG-encrypted, team sharing) | passbolt, db |
| `code` | Browser-based VS Code IDE with Anthropic API access | code |
## Tools ## Tools
| Directory | Description | | File | Description |
|---|---| |---|---|
| `_backup` | Daily restic backups to HiDrive (host script + systemd timer) | | `stacks.sh` | CLI to manage stacks, services, scaffolding, updates, and backups |
| `.env` | Root config: `WEBHOOK_URL`, `RESTIC_REPOSITORY`, `RESTIC_PASSWORD` (gitignored) |
| `.env.example` | Template for the root `.env` |
## stacks.sh
`stacks.sh` is the primary management CLI. It wraps `docker compose` with glob-based multi-stack targeting, manages the update and backup systemd services, scaffolds new stacks, and generates shell completions.
```bash
./stacks.sh help
```
**Stack commands** — all accept one or more stack names or glob patterns (omit for all stacks):
```bash
./stacks.sh ls # list all stacks with live container status
./stacks.sh ps gitea # container status table
./stacks.sh up # start all stacks
./stacks.sh up gitea traefik # start specific stacks
./stacks.sh down 'g*' # stop stacks matching glob
./stacks.sh restart 'g*,traefik' # glob + exact name, comma-separated
./stacks.sh pull --parallel # pull all images in parallel
./stacks.sh logs -f gitea # follow logs
./stacks.sh logs -n 100 gitea n8n # tail multiple stacks
./stacks.sh exec gitea gitea gitea admin user list # exec in container
./stacks.sh run passbolt passbolt bin/cake passbolt healthcheck
```
**Service management** (reads `WEBHOOK_URL`, `RESTIC_REPOSITORY`, `RESTIC_PASSWORD` from root `.env`):
```bash
./stacks.sh update install # write & enable systemd update timer
./stacks.sh update run # run update now
./stacks.sh update status # show timer/service status
./stacks.sh update logs # show journal logs
./stacks.sh backup install # write & enable systemd backup timer
./stacks.sh backup run # run backup now (auto-detects <stack>_db containers)
./stacks.sh backup snapshots # list restic snapshots
```
**Scaffold a new stack:**
```bash
./stacks.sh new myapp # basic stack with Traefik labels
./stacks.sh new myapp --db postgres # with Postgres service
./stacks.sh new myapp --db postgres --redis # with Postgres + Redis
./stacks.sh new myapp --no-traefik # expose port instead of Traefik
```
Generates `compose.yml` (with healthchecks, `../.data/` volumes, Traefik labels) and `.env.example`.
**Shell completion:**
```bash
# Dynamic — discovers stacks at tab-complete time (default)
./stacks.sh completion zsh --install
# Static — bakes current stack list in; useful on the VPS
./stacks.sh completion zsh --static --install
```
**Global flags:** `--dry-run`, `--parallel`, `--verbose`, `--quiet`
## Deployment ## Deployment
@@ -44,39 +105,36 @@ All stacks share the external `falcon_network` Docker network for inter-service
## Backup ## Backup
The `_backup` stack runs a daily restic backup at 3:00 AM. It dumps all Postgres databases, then backs up the entire `.data/` directory to HiDrive. Retention: 7 daily, 4 weekly, 6 monthly snapshots. Notifications go to Telegram via n8n. Runs daily at 3:00 AM via a systemd timer. Detects Postgres databases automatically by convention (`<stack>_db` container, user `<stack>`, database `<stack>`), dumps each one, then runs a full restic backup of `.data/`. Retention: 7 daily, 4 weekly, 6 monthly. Notifications go to Telegram via n8n.
```bash ```bash
# Deploy backup stack # First-time setup on VPS
rsync -avz _backup/ vps:~/stacks/_backup/ cp .env.example .env && $EDITOR .env # set RESTIC_REPOSITORY, RESTIC_PASSWORD, WEBHOOK_URL
source .env && restic init # initialise restic repo
# Initialize restic repo (first time only) ./stacks.sh backup install # write & enable systemd unit + timer
ssh vps 'source ~/stacks/_backup/.env && restic init -r /mnt/hidrive/users/valknar/Backup/stacks' ./stacks.sh backup run # test run
./stacks.sh backup snapshots # list snapshots
./stacks.sh backup status # timer/service status
./stacks.sh backup logs # journald logs
```
# Install systemd units ## Updates
ssh vps 'ln -sf ~/stacks/_backup/stacks-backup.service /etc/systemd/system/ && \
ln -sf ~/stacks/_backup/stacks-backup.timer /etc/systemd/system/ && \
systemctl daemon-reload && systemctl enable --now stacks-backup.timer'
# Manual test run Runs nightly at 2:00 AM via a systemd timer. Pulls the latest image for every stack, recreates any container whose image changed, prunes dangling images, and sends a Telegram notification via n8n.
ssh vps '~/stacks/_backup/backup.sh'
# Check timer status ```bash
ssh vps 'systemctl status stacks-backup.timer' ./stacks.sh update install # write & enable systemd unit + timer
./stacks.sh update run # test run
# View snapshots ./stacks.sh update status # timer/service status
ssh vps 'source ~/stacks/_backup/.env && restic -r /mnt/hidrive/users/valknar/Backup/stacks snapshots' ./stacks.sh update logs # journald logs
``` ```
## Notifications ## Notifications
Watchtower and the backup script both POST to an n8n webhook, which forwards messages to Telegram. Both update and backup POST to an n8n webhook on completion, which forwards the message to Telegram.
The webhook URL is set in two places: The webhook URL is set via `WEBHOOK_URL` in the root `.env`. Both services point to the same n8n workflow at `https://n8n.pivoine.art`, which accepts `{ "message": "...", "color": "..." }` and forwards it to Telegram.
- `_backup/.env``WEBHOOK_URL`
- `watchtower/.env``NOTIFICATION_URL` (uses `generic+https://` shoutrrr scheme)
Both point to the same n8n workflow at `https://n8n.pivoine.art`. The workflow accepts `{ "message": "..." }` from both senders and forwards it to Telegram.
## Data ## Data
-3
View File
@@ -1,3 +0,0 @@
RESTIC_REPOSITORY=/mnt/hidrive/users/valknar/Backup/stacks
RESTIC_PASSWORD=change_me
WEBHOOK_URL=https://n8n.example.com/webhook/change_me
-83
View File
@@ -1,83 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
STACKS_DIR="$(dirname "$SCRIPT_DIR")"
DATA_DIR="$STACKS_DIR/.data"
DUMP_DIR="$DATA_DIR/backup/dumps"
LOG_FILE="$SCRIPT_DIR/backup.log"
# Load environment
set -a
source "$SCRIPT_DIR/.env"
set +a
export RESTIC_REPOSITORY RESTIC_PASSWORD
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
notify() {
local color="$1" text="$2"
curl -sf -o /dev/null -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"message\":\"$text\",\"color\":\"$color\"}"
}
# Truncate log on each run
: > "$LOG_FILE"
log "Starting backup"
# --- Postgres dumps ---
mkdir -p "$DUMP_DIR"
declare -A DATABASES=(
[umami_db]="umami:umami"
[blinko_db]="blinko:blinko"
[gitea_db]="gitea:gitea"
[n8n_db]="n8n:n8n"
[immich_db]="immich:immich"
[coolify_db]="coolify:coolify"
)
dump_errors=()
for container in "${!DATABASES[@]}"; do
IFS=: read -r user db <<< "${DATABASES[$container]}"
log "Dumping $db from $container"
if docker exec "$container" pg_dump -U "$user" "$db" > "$DUMP_DIR/$db.sql" 2>>"$LOG_FILE"; then
log " OK ($(du -h "$DUMP_DIR/$db.sql" | cut -f1))"
else
log " FAILED: $container"
dump_errors+=("$db")
fi
done
if [ ${#dump_errors[@]} -gt 0 ]; then
log "WARNING: Failed dumps: ${dump_errors[*]}"
fi
# --- Restic backup ---
log "Running restic backup"
if ! restic backup "$DATA_DIR" --tag stacks 2>&1 | tee -a "$LOG_FILE"; then
log "FATAL: restic backup failed"
notify "#cc0000" "❌ **Backup failed** — restic backup error. Check \`$LOG_FILE\`."
exit 1
fi
# --- Restic prune ---
log "Pruning old snapshots"
if ! restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6 2>&1 | tee -a "$LOG_FILE"; then
log "WARNING: restic forget failed"
fi
# --- Summary ---
snapshot_info=$(restic snapshots --latest 1 --compact 2>/dev/null | tail -3 | head -1)
repo_stats=$(restic stats 2>/dev/null | grep "Total Size" || true)
summary="✅ **Backup complete**"
[ ${#dump_errors[@]} -gt 0 ] && summary+="\n⚠️ Failed dumps: ${dump_errors[*]}"
summary+="\nLatest: \`$snapshot_info\`"
[ -n "$repo_stats" ] && summary+="\nRepo: $repo_stats"
notify "#36a64f" "$summary"
log "Backup complete"
-10
View File
@@ -1,10 +0,0 @@
[Unit]
Description=Backup stacks data to HiDrive via restic
After=network-online.target docker.service
Wants=network-online.target
[Service]
Type=oneshot
User=root
ExecStart=/root/stacks/_backup/backup.sh
Environment=HOME=/root
-9
View File
@@ -1,9 +0,0 @@
[Unit]
Description=Daily stacks backup at 3:00 AM
[Timer]
OnCalendar=*-*-* 03:00:00 Europe/Amsterdam
Persistent=true
[Install]
WantedBy=timers.target
-3
View File
@@ -1,3 +0,0 @@
NETWORK_NAME=falcon_network
TRAEFIK_HOST=blinko.example.com
BLINKO_NEXTAUTH_SECRET=change_me
-53
View File
@@ -1,53 +0,0 @@
services:
blinko:
image: blinkospace/blinko:latest
container_name: blinko
environment:
TZ: ${TIMEZONE:-Europe/Amsterdam}
NODE_ENV: production
NEXTAUTH_URL: https://${TRAEFIK_HOST}
NEXT_PUBLIC_BASE_URL: https://${TRAEFIK_HOST}
NEXTAUTH_SECRET: ${BLINKO_NEXTAUTH_SECRET}
DATABASE_URL: postgresql://blinko:blinko@blinko_db:5432/blinko
volumes:
- ../.data/blinko/app:/app/.blinko
depends_on:
db:
condition: service_healthy
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.blinko-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.blinko-web.middlewares=blinko-redirect-web-secure"
- "traefik.http.routers.blinko-web.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.blinko-web.entrypoints=web"
- "traefik.http.routers.blinko-web-secure.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.blinko-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.blinko-web-secure.entrypoints=web-secure"
- "traefik.http.routers.blinko-web-secure.middlewares=security-headers@file,no-index@file"
- "traefik.http.services.blinko-web-secure.loadbalancer.server.port=1111"
- "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks:
- compose_network
db:
image: postgres:14-alpine
container_name: blinko_db
environment:
POSTGRES_DB: blinko
POSTGRES_USER: blinko
POSTGRES_PASSWORD: blinko
volumes:
- ../.data/blinko/db:/var/lib/postgresql/data
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
networks:
- compose_network
networks:
compose_network:
name: ${NETWORK_NAME}
external: true
+8
View File
@@ -0,0 +1,8 @@
TRAEFIK_HOST=code.example.com
NETWORK_NAME=falcon_network
TIMEZONE=Europe/Amsterdam
DOCKER_USER=coder
PUID=1000
PGID=1000
PASSWORD=changeme
ANTHROPIC_API_KEY=
+32
View File
@@ -0,0 +1,32 @@
services:
code:
image: codercom/code-server:latest
container_name: code
user: "${PUID:-1000}:${PGID:-1000}"
environment:
TZ: ${TIMEZONE:-Europe/Amsterdam}
DOCKER_USER: ${DOCKER_USER:-coder}
PASSWORD: ${PASSWORD}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
volumes:
- ../.data/code:/home/coder
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.code-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.code-web.middlewares=code-redirect-web-secure"
- "traefik.http.routers.code-web.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.code-web.entrypoints=web"
- "traefik.http.routers.code-web-secure.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.code-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.code-web-secure.entrypoints=web-secure"
- "traefik.http.routers.code-web-secure.middlewares=security-headers@file,no-index@file"
- "traefik.http.services.code-web-secure.loadbalancer.server.port=8080"
- "traefik.docker.network=${NETWORK_NAME}"
networks:
- compose_network
networks:
compose_network:
name: ${NETWORK_NAME}
external: true
-2
View File
@@ -54,7 +54,6 @@ services:
- "traefik.http.routers.coolify-web-secure.priority=1" - "traefik.http.routers.coolify-web-secure.priority=1"
- "traefik.http.services.coolify.loadbalancer.server.port=8080" - "traefik.http.services.coolify.loadbalancer.server.port=8080"
- "traefik.docker.network=${NETWORK_NAME}" - "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
realtime: realtime:
@@ -91,7 +90,6 @@ services:
- "traefik.http.routers.coolify-terminal-ws.priority=100" - "traefik.http.routers.coolify-terminal-ws.priority=100"
- "traefik.http.services.coolify-terminal.loadbalancer.server.port=6002" - "traefik.http.services.coolify-terminal.loadbalancer.server.port=6002"
- "traefik.docker.network=${NETWORK_NAME}" - "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
redis: redis:
+2
View File
@@ -1,3 +1,5 @@
TRAEFIK_HOST=dev.example.com TRAEFIK_HOST=dev.example.com
NETWORK_NAME=falcon_network NETWORK_NAME=falcon_network
RUNNER_TOKEN=change_me RUNNER_TOKEN=change_me
EMAIL_FROM=gitea@pivoine.art
EMAIL_USER=Gitea
+1
View File
@@ -0,0 +1 @@
.env
+3 -3
View File
@@ -25,6 +25,8 @@ services:
GITEA__mailer__PROTOCOL: smtp GITEA__mailer__PROTOCOL: smtp
GITEA__mailer__SMTP_ADDR: mailpit GITEA__mailer__SMTP_ADDR: mailpit
GITEA__mailer__SMTP_PORT: 1025 GITEA__mailer__SMTP_PORT: 1025
GITEA__mailer__FROM: ${EMAIL_FROM}
GITEA__mailer__USER: ${EMAIL_USER}
GITEA__service__DISABLE_REGISTRATION: "true" GITEA__service__DISABLE_REGISTRATION: "true"
GITEA__service__REQUIRE_SIGNIN_VIEW: "false" GITEA__service__REQUIRE_SIGNIN_VIEW: "false"
GITEA__service__ENABLE_NOTIFY_MAIL: "true" GITEA__service__ENABLE_NOTIFY_MAIL: "true"
@@ -41,6 +43,7 @@ services:
- ../.data/gitea/config:/etc/gitea - ../.data/gitea/config:/etc/gitea
- ./css:/data/gitea/public/assets/css:ro - ./css:/data/gitea/public/assets/css:ro
- ./img:/data/gitea/public/assets/img:ro - ./img:/data/gitea/public/assets/img:ro
- ./templates/custom:/data/gitea/templates/custom:ro
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
depends_on: depends_on:
db: db:
@@ -58,7 +61,6 @@ services:
- "traefik.http.routers.gitea-web-secure.middlewares=security-headers@file" - "traefik.http.routers.gitea-web-secure.middlewares=security-headers@file"
- "traefik.http.services.gitea-web-secure.loadbalancer.server.port=3000" - "traefik.http.services.gitea-web-secure.loadbalancer.server.port=3000"
- "traefik.docker.network=${NETWORK_NAME}" - "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
runner: runner:
@@ -77,8 +79,6 @@ services:
- ../.data/gitea/runner:/data - ../.data/gitea/runner:/data
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./runner-config.yaml:/data/config.yaml:ro - ./runner-config.yaml:/data/config.yaml:ro
labels:
- "com.centurylinklabs.watchtower.enable=true"
restart: always restart: always
networks: networks:
- compose_network - compose_network
+3
View File
@@ -0,0 +1,3 @@
{{if and .RunModeIsProd (not .IsSigned)}}
<script defer src="https://umami.pivoine.art/script.js" data-website-id="eee7e810-116b-408a-a46d-f51dcec217c2"></script>
{{end}}
-3
View File
@@ -33,7 +33,6 @@ services:
- "traefik.http.routers.immich-web-secure.middlewares=security-headers@file,no-index@file" - "traefik.http.routers.immich-web-secure.middlewares=security-headers@file,no-index@file"
- "traefik.http.services.immich-web-secure.loadbalancer.server.port=2283" - "traefik.http.services.immich-web-secure.loadbalancer.server.port=2283"
- "traefik.docker.network=${NETWORK_NAME}" - "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
ml: ml:
@@ -44,8 +43,6 @@ services:
volumes: volumes:
- ../.data/immich/model-cache:/cache - ../.data/immich/model-cache:/cache
restart: always restart: always
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
redis: redis:
-2
View File
@@ -18,8 +18,6 @@ services:
volumes: volumes:
- ../.data/mailpit:/data - ../.data/mailpit:/data
restart: always restart: always
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
networks: networks:
-1
View File
@@ -33,7 +33,6 @@ services:
- "traefik.http.routers.n8n-web-secure.middlewares=security-headers@file,no-index@file" - "traefik.http.routers.n8n-web-secure.middlewares=security-headers@file,no-index@file"
- "traefik.http.services.n8n-web-secure.loadbalancer.server.port=5678" - "traefik.http.services.n8n-web-secure.loadbalancer.server.port=5678"
- "traefik.docker.network=${NETWORK_NAME}" - "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
db: db:
+4
View File
@@ -0,0 +1,4 @@
TRAEFIK_HOST=passbolt.example.com
NETWORK_NAME=falcon_network
EMAIL_FROM=passbolt@pivoine.art
EMAIL_USER=Passbolt
+66
View File
@@ -0,0 +1,66 @@
services:
passbolt:
image: passbolt/passbolt:latest-ce
container_name: passbolt
environment:
APP_FULL_BASE_URL: https://${TRAEFIK_HOST}
PASSBOLT_SSL_FORCE: "false"
TZ: ${TIMEZONE:-Europe/Amsterdam}
PASSBOLT_REGISTRATION_PUBLIC: "false"
DATASOURCES_DEFAULT_HOST: passbolt_db
DATASOURCES_DEFAULT_PORT: "5432"
DATASOURCES_DEFAULT_DATABASE: passbolt
DATASOURCES_DEFAULT_USERNAME: passbolt
DATASOURCES_DEFAULT_PASSWORD: passbolt
DATASOURCES_DEFAULT_DRIVER: Cake\Database\Driver\Postgres
DATASOURCES_DEFAULT_ENCODING: utf8
DATASOURCES_QUOTE_IDENTIFIER: "true"
EMAIL_TRANSPORT_DEFAULT_HOST: mailpit
EMAIL_TRANSPORT_DEFAULT_PORT: "1025"
EMAIL_TRANSPORT_DEFAULT_TLS: "false"
EMAIL_DEFAULT_FROM: ${EMAIL_FROM}
EMAIL_DEFAULT_FROM_NAME: ${EMAIL_USER}
volumes:
- ../.data/passbolt/gpg:/etc/passbolt/gpg
- ../.data/passbolt/jwt:/etc/passbolt/jwt
- ../.data/passbolt/gnupg:/var/lib/passbolt/.gnupg
depends_on:
db:
condition: service_healthy
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.passbolt-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.passbolt-web.middlewares=passbolt-redirect-web-secure"
- "traefik.http.routers.passbolt-web.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.passbolt-web.entrypoints=web"
- "traefik.http.routers.passbolt-web-secure.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.passbolt-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.passbolt-web-secure.entrypoints=web-secure"
- "traefik.http.routers.passbolt-web-secure.middlewares=security-headers@file,no-index@file"
- "traefik.http.services.passbolt-web-secure.loadbalancer.server.port=80"
- "traefik.docker.network=${NETWORK_NAME}"
networks:
- compose_network
db:
image: postgres:16-alpine
container_name: passbolt_db
environment:
POSTGRES_DB: passbolt
POSTGRES_USER: passbolt
POSTGRES_PASSWORD: passbolt
POSTGRES_INITDB_ARGS: --data-checksums
volumes:
- ../.data/passbolt/db:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5
networks:
- compose_network
networks:
compose_network:
name: ${NETWORK_NAME}
external: true
Executable
+1362
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -28,7 +28,6 @@ services:
- "traefik.http.routers.umami-web-secure.middlewares=security-headers@file,no-index@file" - "traefik.http.routers.umami-web-secure.middlewares=security-headers@file,no-index@file"
- "traefik.http.services.umami-web-secure.loadbalancer.server.port=3000" - "traefik.http.services.umami-web-secure.loadbalancer.server.port=3000"
- "traefik.docker.network=${NETWORK_NAME}" - "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks: networks:
- compose_network - compose_network
db: db:
-3
View File
@@ -1,3 +0,0 @@
TRAEFIK_HOST=vault.example.com
NETWORK_NAME=falcon_network
SMTP_FROM=vaultwarden@example.com
-38
View File
@@ -1,38 +0,0 @@
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
environment:
TZ: ${TIMEZONE:-Europe/Amsterdam}
DOMAIN: https://${TRAEFIK_HOST}
WEBSOCKET_ENABLED: "true"
SIGNUPS_ALLOWED: "true"
INVITATIONS_ALLOWED: "true"
SHOW_PASSWORD_HINT: "false"
SMTP_HOST: mailpit
SMTP_FROM: ${SMTP_FROM}
SMTP_FROM_NAME: Vaultwarden
SMTP_SECURITY: off
SMTP_PORT: 1025
volumes:
- ../.data/vaultwarden:/data
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.vaultwarden-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.vaultwarden-web.middlewares=vaultwarden-redirect-web-secure"
- "traefik.http.routers.vaultwarden-web.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.vaultwarden-web.entrypoints=web"
- "traefik.http.routers.vaultwarden-web-secure.rule=Host(`${TRAEFIK_HOST}`)"
- "traefik.http.routers.vaultwarden-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.vaultwarden-web-secure.entrypoints=web-secure"
- "traefik.http.routers.vaultwarden-web-secure.middlewares=security-headers@file,no-index@file"
- "traefik.http.services.vaultwarden-web-secure.loadbalancer.server.port=80"
- "traefik.docker.network=${NETWORK_NAME}"
- "com.centurylinklabs.watchtower.enable=true"
networks:
- compose_network
networks:
compose_network:
name: ${NETWORK_NAME}
external: true
-1
View File
@@ -1 +0,0 @@
NOTIFICATION_URL=generic+https://n8n.example.com/webhook/change_me
-21
View File
@@ -1,21 +0,0 @@
services:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
environment:
DOCKER_API_VERSION: "1.44"
WATCHTOWER_POLL_INTERVAL: 300
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_INCLUDE_STOPPED: "false"
WATCHTOWER_INCLUDE_RESTARTING: "true"
WATCHTOWER_RUN_ONCE: "false"
WATCHTOWER_NOTIFICATIONS: ${NOTIFICATIONS:-}
WATCHTOWER_NOTIFICATION_URL: ${NOTIFICATION_URL:-}
WATCHTOWER_LOG_LEVEL: info
WATCHTOWER_ROLLING_RESTART: "false"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: always
labels:
- "com.centurylinklabs.watchtower.enable=true"