update.sh 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. INSTALL_DIR="${INSTALL_DIR:-/opt/bambuddy}"
  4. SERVICE_NAME="${SERVICE_NAME:-bambuddy}"
  5. BRANCH="${BRANCH:-}"
  6. VENV_PIP="${VENV_PIP:-$INSTALL_DIR/venv/bin/pip}"
  7. FRONTEND_DIR="${FRONTEND_DIR:-$INSTALL_DIR/frontend}"
  8. BACKUP_DIR="${BACKUP_DIR:-$INSTALL_DIR/backups}"
  9. BAMBUDDY_API_URL="${BAMBUDDY_API_URL:-http://127.0.0.1:8000/api/v1}"
  10. BAMBUDDY_API_KEY="${BAMBUDDY_API_KEY:-}"
  11. BACKUP_MODE="${BACKUP_MODE:-auto}" # auto|require|skip
  12. FORCE="${FORCE:-0}"
  13. SERVICE_STOPPED=0
  14. log() {
  15. printf '[bambuddy-update] %s\n' "$*"
  16. }
  17. warn() {
  18. printf '[bambuddy-update] WARNING: %s\n' "$*" >&2
  19. }
  20. die() {
  21. printf '[bambuddy-update] ERROR: %s\n' "$*" >&2
  22. exit 1
  23. }
  24. require_cmd() {
  25. command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
  26. }
  27. on_error() {
  28. local exit_code="$1"
  29. if [ "$SERVICE_STOPPED" -eq 1 ]; then
  30. warn "Update failed, attempting to restart service: $SERVICE_NAME"
  31. systemctl start "$SERVICE_NAME" || true
  32. fi
  33. exit "$exit_code"
  34. }
  35. trap 'on_error $?' ERR
  36. create_backup() {
  37. local ts backup_file
  38. if [ "$BACKUP_MODE" = "skip" ]; then
  39. log "Skipping backup (BACKUP_MODE=skip)"
  40. return 0
  41. fi
  42. if ! systemctl is-active --quiet "$SERVICE_NAME"; then
  43. if [ "$BACKUP_MODE" = "require" ]; then
  44. die "Service is not running; cannot call built-in backup API."
  45. fi
  46. warn "Service is not running; skipping built-in backup API call."
  47. return 0
  48. fi
  49. mkdir -p "$BACKUP_DIR"
  50. ts="$(date +%Y%m%d-%H%M%S)"
  51. backup_file="$BACKUP_DIR/bambuddy-backup-$ts.zip"
  52. log "Creating built-in backup via API: $backup_file"
  53. if [ -n "$BAMBUDDY_API_KEY" ]; then
  54. if curl --silent --show-error --fail --location \
  55. --connect-timeout 5 --max-time 900 \
  56. -H "X-API-Key: $BAMBUDDY_API_KEY" \
  57. "$BAMBUDDY_API_URL/settings/backup" \
  58. --output "$backup_file"; then
  59. log "Backup created successfully"
  60. return 0
  61. fi
  62. else
  63. if curl --silent --show-error --fail --location \
  64. --connect-timeout 5 --max-time 900 \
  65. "$BAMBUDDY_API_URL/settings/backup" \
  66. --output "$backup_file"; then
  67. log "Backup created successfully"
  68. return 0
  69. fi
  70. fi
  71. rm -f "$backup_file"
  72. if [ "$BACKUP_MODE" = "require" ]; then
  73. die "Built-in backup API call failed (BACKUP_MODE=require)."
  74. fi
  75. warn "Built-in backup API call failed. Continuing because BACKUP_MODE=auto."
  76. }
  77. [ "${EUID:-$(id -u)}" -eq 0 ] || die "Run as root (or with sudo)."
  78. case "$BACKUP_MODE" in
  79. auto|require|skip) ;;
  80. *) die "Invalid BACKUP_MODE '$BACKUP_MODE' (expected: auto, require, skip)." ;;
  81. esac
  82. require_cmd git
  83. require_cmd systemctl
  84. require_cmd curl
  85. [ -d "$INSTALL_DIR" ] || die "Install directory not found: $INSTALL_DIR"
  86. cd "$INSTALL_DIR"
  87. [ -d .git ] || die "No git repository found in: $INSTALL_DIR"
  88. if [ -z "$BRANCH" ]; then
  89. BRANCH="$(git rev-parse --abbrev-ref HEAD)"
  90. [ "$BRANCH" = "HEAD" ] && BRANCH="main"
  91. fi
  92. if ! systemctl list-unit-files --type=service | grep -q "^${SERVICE_NAME}\.service"; then
  93. die "Service not found: ${SERVICE_NAME}.service"
  94. fi
  95. if [ -n "$(git status --porcelain)" ]; then
  96. warn "Local changes detected in $INSTALL_DIR."
  97. warn "This update uses: git reset --hard origin/$BRANCH"
  98. if [ "$FORCE" != "1" ]; then
  99. read -r -p "Discard local changes and continue? [y/N]: " answer
  100. case "${answer:-}" in
  101. y|Y|yes|YES) ;;
  102. *) die "Update cancelled by user." ;;
  103. esac
  104. else
  105. warn "Proceeding without prompt because FORCE=1."
  106. fi
  107. fi
  108. create_backup
  109. old_commit="$(git rev-parse --short HEAD || true)"
  110. log "Stopping service: $SERVICE_NAME"
  111. systemctl stop "$SERVICE_NAME"
  112. SERVICE_STOPPED=1
  113. log "Updating code from origin/$BRANCH"
  114. git fetch --prune origin
  115. git reset --hard "origin/$BRANCH"
  116. if [ -x "$VENV_PIP" ] && [ -f requirements.txt ]; then
  117. log "Updating Python dependencies"
  118. "$VENV_PIP" install -r requirements.txt
  119. else
  120. warn "Skipping Python dependency update (venv pip or requirements.txt missing)."
  121. fi
  122. if [ -f "$FRONTEND_DIR/package.json" ]; then
  123. if command -v npm >/dev/null 2>&1; then
  124. log "Building frontend"
  125. (
  126. cd "$FRONTEND_DIR"
  127. npm ci
  128. npm run build
  129. )
  130. else
  131. warn "Skipping frontend build (npm not installed)."
  132. fi
  133. else
  134. warn "Skipping frontend build (frontend/package.json not found)."
  135. fi
  136. log "Starting service: $SERVICE_NAME"
  137. systemctl start "$SERVICE_NAME"
  138. SERVICE_STOPPED=0
  139. systemctl --no-pager --lines=8 status "$SERVICE_NAME"
  140. new_commit="$(git rev-parse --short HEAD || true)"
  141. log "Update complete: ${old_commit:-unknown} -> ${new_commit:-unknown}"