242 lines
8.0 KiB
Bash
Executable File
242 lines
8.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# scripts/deploy.sh - Automated deploy for SyncServer
|
|
#
|
|
# Usage:
|
|
# ./scripts/deploy.sh # Full deploy
|
|
# ./scripts/deploy.sh --dry-run # Simulate without making changes
|
|
# ./scripts/deploy.sh --help # Show help
|
|
# SKIP_BUILD=1 ./scripts/deploy.sh # Skip rebuild (use existing .deb)
|
|
# LXC_HOST=user@host ./scripts/deploy.sh # Custom target
|
|
#
|
|
# Environment variables:
|
|
# LXC_HOST - SSH target (default: root@10.5.1.30)
|
|
# LXC_PORT - SSH port (default: 22)
|
|
# REMOTE_DIR - Remote directory (default: ~/move-data-nas)
|
|
# SKIP_BUILD - Skip make package if 1
|
|
# SKIP_VERIFY - Skip post-deploy verification if 1
|
|
# LOG_DIR - Where to write logs (default: logs/)
|
|
|
|
set -euo pipefail
|
|
|
|
# === Configuration ===
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
LXC_HOST="${LXC_HOST:-root@10.5.1.30}"
|
|
LXC_PORT="${LXC_PORT:-22}"
|
|
REMOTE_DIR="${REMOTE_DIR:-~/move-data-nas}"
|
|
LOG_DIR="${LOG_DIR:-$PROJECT_DIR/logs}"
|
|
SKIP_BUILD="${SKIP_BUILD:-0}"
|
|
SKIP_VERIFY="${SKIP_VERIFY:-0}"
|
|
DRY_RUN=0
|
|
HEALTH_TIMEOUT=30
|
|
SSH_OPTS="-o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new -p $LXC_PORT"
|
|
|
|
# === Setup ===
|
|
mkdir -p "$LOG_DIR"
|
|
LOG_FILE="$LOG_DIR/deploy_$(date +%Y%m%d_%H%M%S).log"
|
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
|
|
# === Helpers ===
|
|
log() { echo "→ $*"; }
|
|
success() { echo "✓ $*"; }
|
|
warn() { echo "⚠ $*" >&2; }
|
|
die() { echo "✗ $*" >&2; exit 1; }
|
|
|
|
run_ssh() { ssh $SSH_OPTS "$LXC_HOST" "$@"; }
|
|
run_scp() { scp -P "$LXC_PORT" "$@"; }
|
|
|
|
BACKUP_PATH=""
|
|
rollback() {
|
|
if [ -z "$BACKUP_PATH" ]; then
|
|
warn "No backup path set, cannot rollback"
|
|
return 1
|
|
fi
|
|
warn "Rolling back to $BACKUP_PATH..."
|
|
if run_ssh "systemctl stop syncserver 2>/dev/null; cp $BACKUP_PATH /usr/bin/syncserver; systemctl start syncserver 2>/dev/null || true"; then
|
|
warn "Rollback completed"
|
|
else
|
|
warn "Rollback failed - manual intervention required"
|
|
fi
|
|
}
|
|
|
|
trap 'die "Interrupted by signal"' INT TERM
|
|
|
|
# === Argument parsing ===
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--dry-run) DRY_RUN=1; shift ;;
|
|
--help|-h)
|
|
sed -n '2,16p' "$0"
|
|
exit 0
|
|
;;
|
|
*) die "Unknown argument: $1. Use --help for usage." ;;
|
|
esac
|
|
done
|
|
|
|
# === Step 1: Pre-flight ===
|
|
log "Pre-flight checks..."
|
|
cd "$PROJECT_DIR"
|
|
git diff --quiet || die "Git working tree is dirty. Commit or stash first."
|
|
git diff --cached --quiet || die "Git index has staged changes. Commit first."
|
|
[ "$(git rev-parse --abbrev-ref HEAD)" = "main" ] || die "Must be on main branch"
|
|
[ -f "$SCRIPT_DIR/bump-version.sh" ] || die "bump-version.sh not found"
|
|
[ -f "Makefile" ] || die "Makefile not found"
|
|
[ -d "web" ] || die "web/ directory not found"
|
|
|
|
for tool in ssh scp make go dpkg-deb; do
|
|
command -v "$tool" >/dev/null || die "$tool not found"
|
|
done
|
|
|
|
if ! run_ssh 'true' 2>/dev/null; then
|
|
[ "$DRY_RUN" = "1" ] && warn "Cannot reach $LXC_HOST, but --dry-run so continuing" || die "Cannot ssh to $LXC_HOST"
|
|
fi
|
|
|
|
FREE_KB=$(df -k "$PROJECT_DIR" | tail -1 | awk '{print $4}')
|
|
if [ "$FREE_KB" -lt 512000 ]; then
|
|
warn "Less than 500MB free on local disk"
|
|
fi
|
|
|
|
success "Pre-flight OK"
|
|
|
|
# === Step 2: Bump version ===
|
|
log "Bumping version..."
|
|
CURRENT_VERSION=$(grep -oE 'var version = "[0-9]+\.[0-9]+\.[0-9]+"' cmd/server/main.go | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
|
[ -n "$CURRENT_VERSION" ] || die "Cannot parse current version"
|
|
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
success "Would bump from $CURRENT_VERSION (dry run)"
|
|
NEW_VERSION="$CURRENT_VERSION"
|
|
else
|
|
"$SCRIPT_DIR/bump-version.sh" >/dev/null
|
|
NEW_VERSION=$(grep -oE 'var version = "[0-9]+\.[0-9]+\.[0-9]+"' cmd/server/main.go | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
|
[ "$NEW_VERSION" != "$CURRENT_VERSION" ] || die "Version bump failed"
|
|
|
|
git add -f cmd/server/main.go Makefile
|
|
git commit -m "Bump version to $NEW_VERSION" >/dev/null
|
|
git push || die "git push failed"
|
|
success "Bumped to $NEW_VERSION and pushed"
|
|
fi
|
|
|
|
VERSION="$NEW_VERSION"
|
|
|
|
# === Step 3: Build ===
|
|
if [ "$SKIP_BUILD" = "1" ]; then
|
|
log "Skipping build (SKIP_BUILD=1)"
|
|
DEB="dist/syncserver_${VERSION}_amd64.deb"
|
|
[ -f "$DEB" ] || die "SKIP_BUILD=1 but $DEB does not exist"
|
|
else
|
|
log "Building..."
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
log "[dry-run] Would run: make clean && make package"
|
|
DEB="dist/syncserver_${VERSION}_amd64.deb"
|
|
if [ -f "$DEB" ]; then
|
|
log "[dry-run] Found existing $DEB"
|
|
else
|
|
log "[dry-run] $DEB does not exist yet (would be created by build)"
|
|
fi
|
|
else
|
|
make clean >/dev/null
|
|
make package
|
|
DEB="dist/syncserver_${VERSION}_amd64.deb"
|
|
[ -f "$DEB" ] || die "Build failed: $DEB not found"
|
|
success "Built: $DEB ($(du -h "$DEB" | cut -f1))"
|
|
fi
|
|
fi
|
|
|
|
# === Step 4: Backup on LXC ===
|
|
BACKUP_PATH="/var/backups/syncserver.previous.${VERSION}"
|
|
log "Backing up current binary on LXC to $BACKUP_PATH..."
|
|
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
log "[dry-run] Would backup /usr/bin/syncserver"
|
|
else
|
|
if ! run_ssh "mkdir -p /var/backups && cp /usr/bin/syncserver $BACKUP_PATH && ls -la $BACKUP_PATH" 2>/dev/null; then
|
|
die "Backup failed on $LXC_HOST"
|
|
fi
|
|
success "Backed up to $BACKUP_PATH"
|
|
fi
|
|
|
|
# === Step 5: Deploy ===
|
|
log "Deploying to $LXC_HOST..."
|
|
|
|
if [ "$DRY_RUN" = "1" ]; then
|
|
log "[dry-run] Would scp $DEB and run dpkg -i"
|
|
echo ""
|
|
echo "Dry run complete. No changes made."
|
|
echo "Log file: $LOG_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
if ! run_scp "$DEB" "${LXC_HOST}:${REMOTE_DIR}/dist/" 2>/dev/null; then
|
|
rollback
|
|
die "SCP failed"
|
|
fi
|
|
|
|
if ! run_ssh "cd $REMOTE_DIR && dpkg -i dist/syncserver_${VERSION}_amd64.deb" 2>/dev/null; then
|
|
rollback
|
|
die "dpkg -i failed, rolled back"
|
|
fi
|
|
|
|
success "Deployed $DEB"
|
|
|
|
# === Step 6: Verify ===
|
|
if [ "$SKIP_VERIFY" = "1" ]; then
|
|
log "Skipping verify (SKIP_VERIFY=1)"
|
|
else
|
|
log "Verifying deployment (timeout: ${HEALTH_TIMEOUT}s)..."
|
|
|
|
SERVICE_ACTIVE=""
|
|
for i in $(seq 1 $HEALTH_TIMEOUT); do
|
|
SERVICE_ACTIVE=$(run_ssh 'systemctl is-active syncserver' 2>/dev/null || echo "unknown")
|
|
if [ "$SERVICE_ACTIVE" = "active" ]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
if [ "$SERVICE_ACTIVE" != "active" ]; then
|
|
rollback
|
|
die "Service did not become active within ${HEALTH_TIMEOUT}s (current: $SERVICE_ACTIVE)"
|
|
fi
|
|
success "Service active"
|
|
|
|
REMOTE_VERSION=$(run_ssh '/usr/bin/syncserver --version' 2>/dev/null | tr -d '[:space:]' || echo "unknown")
|
|
if [ "$REMOTE_VERSION" != "$VERSION" ]; then
|
|
rollback
|
|
die "Version mismatch: deployed $VERSION, remote reports $REMOTE_VERSION"
|
|
fi
|
|
success "Version: $REMOTE_VERSION"
|
|
|
|
CACHE_HEADER=$(run_ssh 'curl -sI http://localhost:8080/' 2>/dev/null | grep -i "cache-control:" | head -1 | tr -d '\r')
|
|
if ! echo "$CACHE_HEADER" | grep -qi "no-cache"; then
|
|
warn "Cache-Control header missing or wrong: $CACHE_HEADER"
|
|
else
|
|
success "Cache headers OK"
|
|
fi
|
|
|
|
HTTP_CODE=$(run_ssh 'curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/' 2>/dev/null || echo "000")
|
|
if [ "$HTTP_CODE" != "200" ]; then
|
|
rollback
|
|
die "Health check failed: HTTP $HTTP_CODE"
|
|
fi
|
|
success "HTTP / → 200"
|
|
fi
|
|
|
|
# === Summary ===
|
|
echo ""
|
|
echo "════════════════════════════════════════════"
|
|
echo " Deploy Summary"
|
|
echo "════════════════════════════════════════════"
|
|
echo " Version: $VERSION"
|
|
echo " Host: $LXC_HOST"
|
|
echo " Binary: /usr/bin/syncserver"
|
|
echo " Backup: $BACKUP_PATH"
|
|
echo " Status: active"
|
|
echo " HTTP: 200"
|
|
echo " Headers: $CACHE_HEADER"
|
|
echo " Time: $(date)"
|
|
echo " Log file: $LOG_FILE"
|
|
echo "════════════════════════════════════════════"
|
|
echo ""
|
|
echo "Next: hard reload browser once (Ctrl+Shift+R) to clear stale cache"
|