|
|
@@ -0,0 +1,169 @@
|
|
|
+#!/usr/bin/env bash
|
|
|
+set -Eeuo pipefail
|
|
|
+
|
|
|
+INSTALL_DIR="${INSTALL_DIR:-/opt/bambuddy}"
|
|
|
+SERVICE_NAME="${SERVICE_NAME:-bambuddy}"
|
|
|
+BRANCH="${BRANCH:-}"
|
|
|
+VENV_PIP="${VENV_PIP:-$INSTALL_DIR/venv/bin/pip}"
|
|
|
+FRONTEND_DIR="${FRONTEND_DIR:-$INSTALL_DIR/frontend}"
|
|
|
+BACKUP_DIR="${BACKUP_DIR:-$INSTALL_DIR/backups}"
|
|
|
+BAMBUDDY_API_URL="${BAMBUDDY_API_URL:-http://127.0.0.1:8000/api/v1}"
|
|
|
+BAMBUDDY_API_KEY="${BAMBUDDY_API_KEY:-}"
|
|
|
+BACKUP_MODE="${BACKUP_MODE:-auto}" # auto|require|skip
|
|
|
+FORCE="${FORCE:-0}"
|
|
|
+
|
|
|
+SERVICE_STOPPED=0
|
|
|
+
|
|
|
+log() {
|
|
|
+ printf '[bambuddy-update] %s\n' "$*"
|
|
|
+}
|
|
|
+
|
|
|
+warn() {
|
|
|
+ printf '[bambuddy-update] WARNING: %s\n' "$*" >&2
|
|
|
+}
|
|
|
+
|
|
|
+die() {
|
|
|
+ printf '[bambuddy-update] ERROR: %s\n' "$*" >&2
|
|
|
+ exit 1
|
|
|
+}
|
|
|
+
|
|
|
+require_cmd() {
|
|
|
+ command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
|
|
|
+}
|
|
|
+
|
|
|
+on_error() {
|
|
|
+ local exit_code="$1"
|
|
|
+ if [ "$SERVICE_STOPPED" -eq 1 ]; then
|
|
|
+ warn "Update failed, attempting to restart service: $SERVICE_NAME"
|
|
|
+ systemctl start "$SERVICE_NAME" || true
|
|
|
+ fi
|
|
|
+ exit "$exit_code"
|
|
|
+}
|
|
|
+trap 'on_error $?' ERR
|
|
|
+
|
|
|
+create_backup() {
|
|
|
+ local ts backup_file
|
|
|
+
|
|
|
+ if [ "$BACKUP_MODE" = "skip" ]; then
|
|
|
+ log "Skipping backup (BACKUP_MODE=skip)"
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+
|
|
|
+ if ! systemctl is-active --quiet "$SERVICE_NAME"; then
|
|
|
+ if [ "$BACKUP_MODE" = "require" ]; then
|
|
|
+ die "Service is not running; cannot call built-in backup API."
|
|
|
+ fi
|
|
|
+ warn "Service is not running; skipping built-in backup API call."
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+
|
|
|
+ mkdir -p "$BACKUP_DIR"
|
|
|
+ ts="$(date +%Y%m%d-%H%M%S)"
|
|
|
+ backup_file="$BACKUP_DIR/bambuddy-backup-$ts.zip"
|
|
|
+
|
|
|
+ log "Creating built-in backup via API: $backup_file"
|
|
|
+ if [ -n "$BAMBUDDY_API_KEY" ]; then
|
|
|
+ if curl --silent --show-error --fail --location \
|
|
|
+ --connect-timeout 5 --max-time 900 \
|
|
|
+ -H "X-API-Key: $BAMBUDDY_API_KEY" \
|
|
|
+ "$BAMBUDDY_API_URL/settings/backup" \
|
|
|
+ --output "$backup_file"; then
|
|
|
+ log "Backup created successfully"
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+ else
|
|
|
+ if curl --silent --show-error --fail --location \
|
|
|
+ --connect-timeout 5 --max-time 900 \
|
|
|
+ "$BAMBUDDY_API_URL/settings/backup" \
|
|
|
+ --output "$backup_file"; then
|
|
|
+ log "Backup created successfully"
|
|
|
+ return 0
|
|
|
+ fi
|
|
|
+ fi
|
|
|
+
|
|
|
+ rm -f "$backup_file"
|
|
|
+ if [ "$BACKUP_MODE" = "require" ]; then
|
|
|
+ die "Built-in backup API call failed (BACKUP_MODE=require)."
|
|
|
+ fi
|
|
|
+ warn "Built-in backup API call failed. Continuing because BACKUP_MODE=auto."
|
|
|
+}
|
|
|
+
|
|
|
+[ "${EUID:-$(id -u)}" -eq 0 ] || die "Run as root (or with sudo)."
|
|
|
+
|
|
|
+case "$BACKUP_MODE" in
|
|
|
+ auto|require|skip) ;;
|
|
|
+ *) die "Invalid BACKUP_MODE '$BACKUP_MODE' (expected: auto, require, skip)." ;;
|
|
|
+esac
|
|
|
+
|
|
|
+require_cmd git
|
|
|
+require_cmd systemctl
|
|
|
+require_cmd curl
|
|
|
+
|
|
|
+[ -d "$INSTALL_DIR" ] || die "Install directory not found: $INSTALL_DIR"
|
|
|
+cd "$INSTALL_DIR"
|
|
|
+[ -d .git ] || die "No git repository found in: $INSTALL_DIR"
|
|
|
+
|
|
|
+if [ -z "$BRANCH" ]; then
|
|
|
+ BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
|
+ [ "$BRANCH" = "HEAD" ] && BRANCH="main"
|
|
|
+fi
|
|
|
+
|
|
|
+if ! systemctl list-unit-files --type=service | grep -q "^${SERVICE_NAME}\.service"; then
|
|
|
+ die "Service not found: ${SERVICE_NAME}.service"
|
|
|
+fi
|
|
|
+
|
|
|
+if [ -n "$(git status --porcelain)" ]; then
|
|
|
+ warn "Local changes detected in $INSTALL_DIR."
|
|
|
+ warn "This update uses: git reset --hard origin/$BRANCH"
|
|
|
+ if [ "$FORCE" != "1" ]; then
|
|
|
+ read -r -p "Discard local changes and continue? [y/N]: " answer
|
|
|
+ case "${answer:-}" in
|
|
|
+ y|Y|yes|YES) ;;
|
|
|
+ *) die "Update cancelled by user." ;;
|
|
|
+ esac
|
|
|
+ else
|
|
|
+ warn "Proceeding without prompt because FORCE=1."
|
|
|
+ fi
|
|
|
+fi
|
|
|
+
|
|
|
+create_backup
|
|
|
+
|
|
|
+old_commit="$(git rev-parse --short HEAD || true)"
|
|
|
+
|
|
|
+log "Stopping service: $SERVICE_NAME"
|
|
|
+systemctl stop "$SERVICE_NAME"
|
|
|
+SERVICE_STOPPED=1
|
|
|
+
|
|
|
+log "Updating code from origin/$BRANCH"
|
|
|
+git fetch --prune origin
|
|
|
+git reset --hard "origin/$BRANCH"
|
|
|
+
|
|
|
+if [ -x "$VENV_PIP" ] && [ -f requirements.txt ]; then
|
|
|
+ log "Updating Python dependencies"
|
|
|
+ "$VENV_PIP" install -r requirements.txt
|
|
|
+else
|
|
|
+ warn "Skipping Python dependency update (venv pip or requirements.txt missing)."
|
|
|
+fi
|
|
|
+
|
|
|
+if [ -f "$FRONTEND_DIR/package.json" ]; then
|
|
|
+ if command -v npm >/dev/null 2>&1; then
|
|
|
+ log "Building frontend"
|
|
|
+ (
|
|
|
+ cd "$FRONTEND_DIR"
|
|
|
+ npm ci
|
|
|
+ npm run build
|
|
|
+ )
|
|
|
+ else
|
|
|
+ warn "Skipping frontend build (npm not installed)."
|
|
|
+ fi
|
|
|
+else
|
|
|
+ warn "Skipping frontend build (frontend/package.json not found)."
|
|
|
+fi
|
|
|
+
|
|
|
+log "Starting service: $SERVICE_NAME"
|
|
|
+systemctl start "$SERVICE_NAME"
|
|
|
+SERVICE_STOPPED=0
|
|
|
+systemctl --no-pager --lines=8 status "$SERVICE_NAME"
|
|
|
+
|
|
|
+new_commit="$(git rev-parse --short HEAD || true)"
|
|
|
+log "Update complete: ${old_commit:-unknown} -> ${new_commit:-unknown}"
|