feat: add Docker support with deployment scripts and database backup functionality

This commit is contained in:
syntaxbullet
2026-07-09 13:29:41 +02:00
parent 5a08350b6a
commit 1216a37049
12 changed files with 418 additions and 167 deletions

69
scripts/deploy-vps.sh Executable file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
SSH_USER="${SSH_USER:-syntaxbullet}"
VPS_HOST="${VPS_HOST:-srv1.bayerischer-mittelstandspreis.de}"
VPS_PORT="${VPS_PORT:-22}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/bmp-vps-deploy}"
APP_ROOT="${APP_ROOT:-/var/www/bmp-website}"
APP_DOMAIN="${APP_DOMAIN:-srv1.bayerischer-mittelstandspreis.de}"
PUBLIC_URL="${PUBLIC_URL:-https://$APP_DOMAIN}"
ALLOW_DIRTY="${ALLOW_DIRTY:-0}"
DEPLOY_CADDY="${DEPLOY_CADDY:-0}"
SKIP_VERIFY="${SKIP_VERIFY:-0}"
SSH_TARGET="$SSH_USER@$VPS_HOST"
COMPOSE_FILE="$APP_ROOT/current/deploy/vps/docker-compose.yml"
if [[ ! -f "$SSH_KEY" ]]; then
echo "Missing SSH key: $SSH_KEY" >&2
exit 1
fi
if [[ "$ALLOW_DIRTY" != "1" && -n "$(git status --porcelain)" ]]; then
echo "Refusing to deploy a dirty worktree." >&2
echo "Commit or stash changes first, or run with ALLOW_DIRTY=1 for an intentional ad-hoc deploy." >&2
exit 1
fi
REVISION="$(git rev-parse --short=12 HEAD)"
echo "Deploying $REVISION to $SSH_TARGET:$APP_ROOT"
rsync -az --delete \
--exclude '.git/' \
--exclude '.env' \
--include '.env.example' \
--exclude '.env.*' \
--exclude '.next/' \
--exclude 'node_modules/' \
--exclude 'content.db' \
--exclude 'public/media/' \
--exclude '.DS_Store' \
--exclude 'tsconfig.tsbuildinfo' \
-e "ssh -i $SSH_KEY -p $VPS_PORT" \
./ "$SSH_TARGET:$APP_ROOT/current/"
ssh -i "$SSH_KEY" -p "$VPS_PORT" "$SSH_TARGET" "printf '%s\n' '$REVISION' > '$APP_ROOT/current/.deploy-revision'"
ssh -i "$SSH_KEY" -p "$VPS_PORT" "$SSH_TARGET" "
set -e
cd '$APP_ROOT'
APP_ROOT='$APP_ROOT' APP_DOMAIN='$APP_DOMAIN' sudo docker compose -f '$COMPOSE_FILE' build app
APP_ROOT='$APP_ROOT' APP_DOMAIN='$APP_DOMAIN' sudo docker compose -f '$COMPOSE_FILE' up -d app
if [ '$DEPLOY_CADDY' = '1' ]; then
APP_ROOT='$APP_ROOT' APP_DOMAIN='$APP_DOMAIN' sudo docker compose -f '$COMPOSE_FILE' up -d caddy
fi
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
"
if [[ "$SKIP_VERIFY" != "1" ]]; then
echo "Verifying $PUBLIC_URL"
curl -fsSIL --max-time 20 "$PUBLIC_URL/" | sed -n '1,12p'
curl -fsSIL --max-time 20 "$PUBLIC_URL/admin" | sed -n '1,12p'
fi
echo "Deploy complete: $REVISION"

32
scripts/vps-backup-db.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
SSH_USER="${SSH_USER:-syntaxbullet}"
VPS_HOST="${VPS_HOST:-srv1.bayerischer-mittelstandspreis.de}"
VPS_PORT="${VPS_PORT:-22}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/bmp-vps-deploy}"
APP_ROOT="${APP_ROOT:-/var/www/bmp-website}"
APP_DOMAIN="${APP_DOMAIN:-srv1.bayerischer-mittelstandspreis.de}"
SSH_TARGET="$SSH_USER@$VPS_HOST"
COMPOSE_FILE="$APP_ROOT/current/deploy/vps/docker-compose.yml"
ssh -i "$SSH_KEY" -p "$VPS_PORT" "$SSH_TARGET" "
set -e
backup_dir='$APP_ROOT/backups'
backup_file=\"\$backup_dir/content-\$(date +%Y%m%d-%H%M%S).db\"
sudo install -d -o '$SSH_USER' -g '$SSH_USER' \"\$backup_dir\"
restart_app() {
APP_ROOT='$APP_ROOT' APP_DOMAIN='$APP_DOMAIN' sudo docker compose -f '$COMPOSE_FILE' up -d app >/dev/null
}
trap restart_app EXIT
APP_ROOT='$APP_ROOT' APP_DOMAIN='$APP_DOMAIN' sudo docker compose -f '$COMPOSE_FILE' stop app >/dev/null
cp -p '$APP_ROOT/shared/content.db' \"\$backup_file\"
restart_app
trap - EXIT
ls -lh \"\$backup_file\"
"

26
scripts/vps-rollback-n8n.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
SSH_USER="${SSH_USER:-syntaxbullet}"
VPS_HOST="${VPS_HOST:-srv1.bayerischer-mittelstandspreis.de}"
VPS_PORT="${VPS_PORT:-22}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/bmp-vps-deploy}"
APP_ROOT="${APP_ROOT:-/var/www/bmp-website}"
CONFIRM="${CONFIRM:-}"
if [[ "$CONFIRM" != "replace-bmp-with-n8n" ]]; then
echo "This stops the BMP website and starts the old n8n compose project." >&2
echo "Run with CONFIRM=replace-bmp-with-n8n when you intentionally want that rollback." >&2
exit 1
fi
SSH_TARGET="$SSH_USER@$VPS_HOST"
BMP_COMPOSE="$APP_ROOT/current/deploy/vps/docker-compose.yml"
N8N_COMPOSE="/root/n8n-docker/docker-compose.yml"
ssh -i "$SSH_KEY" -p "$VPS_PORT" "$SSH_TARGET" "
set -e
sudo docker compose -f '$BMP_COMPOSE' stop caddy app
sudo docker compose -f '$N8N_COMPOSE' start
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
"

31
scripts/vps-status.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
SSH_USER="${SSH_USER:-syntaxbullet}"
VPS_HOST="${VPS_HOST:-srv1.bayerischer-mittelstandspreis.de}"
VPS_PORT="${VPS_PORT:-22}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/bmp-vps-deploy}"
APP_ROOT="${APP_ROOT:-/var/www/bmp-website}"
LOG_LINES="${LOG_LINES:-80}"
SSH_TARGET="$SSH_USER@$VPS_HOST"
ssh -i "$SSH_KEY" -p "$VPS_PORT" "$SSH_TARGET" "
set -e
printf '== revision ==\n'
cat '$APP_ROOT/current/.deploy-revision' 2>/dev/null || true
printf '\n== containers ==\n'
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
printf '\n== old n8n containers ==\n'
sudo docker ps -a --filter name=n8n_automation --filter name=caddy_proxy --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
printf '\n== shared state ==\n'
ls -lh '$APP_ROOT/shared/.env' '$APP_ROOT/shared/content.db'
find '$APP_ROOT/shared/media' -type f | wc -l
du -sh '$APP_ROOT/shared/media'
printf '\n== disk ==\n'
df -h / /var/lib/docker '$APP_ROOT' 2>/dev/null || df -h
printf '\n== app logs ==\n'
sudo docker logs --tail='$LOG_LINES' bmp_website
printf '\n== caddy logs ==\n'
sudo docker logs --tail='$LOG_LINES' bmp_caddy
"