update.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. #!/bin/bash
  2. # Smartbotic Update Script
  3. # Updates Smartbotic services while preserving all configurations
  4. #
  5. # Usage:
  6. # sudo ./update.sh /path/to/packages/
  7. # sudo ./update.sh --rolling --backup /path/to/packages/
  8. set -e
  9. # Default paths (must match installation)
  10. PREFIX="${PREFIX:-/opt/smartbotic}"
  11. BINDIR="$PREFIX/bin"
  12. CONFDIR="${CONFDIR:-/etc/smartbotic}"
  13. DATADIR="${DATADIR:-/var/lib/smartbotic}"
  14. BACKUP_DIR="/var/backups/smartbotic"
  15. # Log file
  16. UPDATE_LOG=""
  17. # Output control
  18. VERBOSE=false
  19. QUIET=false
  20. # Service order (database first - it's a dependency)
  21. SERVICES=(
  22. "smartbotic-database"
  23. "smartbotic-microbit"
  24. )
  25. # Colors for output
  26. RED='\033[0;31m'
  27. GREEN='\033[0;32m'
  28. YELLOW='\033[1;33m'
  29. BLUE='\033[0;34m'
  30. NC='\033[0m'
  31. # Initialize logging
  32. init_logging() {
  33. mkdir -p "$PREFIX"
  34. UPDATE_LOG="$PREFIX/update-$(date +%Y%m%d-%H%M%S).log"
  35. echo "Smartbotic Update Log - $(date)" > "$UPDATE_LOG"
  36. echo "Command: $0 $ORIGINAL_ARGS" >> "$UPDATE_LOG"
  37. echo "========================================" >> "$UPDATE_LOG"
  38. }
  39. # Log to file only
  40. log() {
  41. echo "[$(date +%H:%M:%S)] $1" >> "$UPDATE_LOG"
  42. }
  43. # Print to console (respects quiet mode)
  44. print_info() {
  45. log "INFO: $1"
  46. if [ "$QUIET" = false ] && [ "$VERBOSE" = true ]; then
  47. echo -e "${GREEN}[INFO]${NC} $1"
  48. fi
  49. }
  50. print_warn() {
  51. log "WARN: $1"
  52. if [ "$QUIET" = false ]; then
  53. echo -e "${YELLOW}[WARN]${NC} $1"
  54. fi
  55. }
  56. print_error() {
  57. log "ERROR: $1"
  58. echo -e "${RED}[ERROR]${NC} $1" >&2
  59. }
  60. # Always show steps (progress indicator)
  61. print_step() {
  62. log "STEP: $1"
  63. if [ "$QUIET" = false ]; then
  64. echo -e "${BLUE}▶${NC} $1"
  65. fi
  66. }
  67. # Summary line (always shown unless quiet)
  68. print_summary() {
  69. log "SUMMARY: $1"
  70. if [ "$QUIET" = false ]; then
  71. echo -e " ${GREEN}✓${NC} $1"
  72. fi
  73. }
  74. usage() {
  75. cat <<EOF
  76. Smartbotic Update Script
  77. Usage: $0 [OPTIONS] PACKAGE_DIR
  78. Options:
  79. --rolling Rolling update (one service at a time, minimizes downtime)
  80. --backup Create backup before update
  81. --skip-restart Update files but don't restart services
  82. --no-deps Skip dependency installation
  83. --dry-run Show what would be done
  84. -v, --verbose Show detailed output
  85. -q, --quiet Minimal output (errors only)
  86. -h, --help Show this help
  87. Log file: Update details are logged to \$PREFIX/update-YYYYMMDD-HHMMSS.log
  88. Examples:
  89. # Standard update (stops all, updates, starts all)
  90. sudo $0 /tmp/packages/
  91. # Rolling update with backup (recommended for production)
  92. sudo $0 --rolling --backup /tmp/packages/
  93. # Update without restarting (for manual control)
  94. sudo $0 --skip-restart /tmp/packages/
  95. EOF
  96. }
  97. ROLLING=false
  98. BACKUP=false
  99. DRY_RUN=false
  100. SKIP_RESTART=false
  101. NO_DEPS=false
  102. PACKAGE_DIR=""
  103. ALL_DEPS=""
  104. ORIGINAL_ARGS="$*"
  105. while [[ $# -gt 0 ]]; do
  106. case $1 in
  107. --rolling) ROLLING=true; shift ;;
  108. --backup) BACKUP=true; shift ;;
  109. --dry-run) DRY_RUN=true; shift ;;
  110. --skip-restart) SKIP_RESTART=true; shift ;;
  111. --no-deps) NO_DEPS=true; shift ;;
  112. -v|--verbose) VERBOSE=true; shift ;;
  113. -q|--quiet) QUIET=true; shift ;;
  114. -h|--help) usage; exit 0 ;;
  115. -*) print_error "Unknown option: $1"; usage; exit 1 ;;
  116. *) PACKAGE_DIR="$1"; shift ;;
  117. esac
  118. done
  119. if [ -z "$PACKAGE_DIR" ]; then
  120. print_error "Package directory required"
  121. usage
  122. exit 1
  123. fi
  124. if [ ! -d "$PACKAGE_DIR" ]; then
  125. print_error "Package directory does not exist: $PACKAGE_DIR"
  126. exit 1
  127. fi
  128. # Check root
  129. if [ "$EUID" -ne 0 ] && [ "$DRY_RUN" = false ]; then
  130. print_error "Must run as root (or use --dry-run)"
  131. exit 1
  132. fi
  133. # Run command, log output
  134. run_cmd() {
  135. log "CMD: $*"
  136. if [ "$DRY_RUN" = true ]; then
  137. log " [DRY-RUN] Skipped"
  138. if [ "$VERBOSE" = true ]; then
  139. echo " [DRY-RUN] $*"
  140. fi
  141. else
  142. local output
  143. if output=$("$@" 2>&1); then
  144. if [ -n "$output" ]; then
  145. echo "$output" >> "$UPDATE_LOG"
  146. fi
  147. else
  148. local exit_code=$?
  149. if [ -n "$output" ]; then
  150. echo "$output" >> "$UPDATE_LOG"
  151. fi
  152. return $exit_code
  153. fi
  154. fi
  155. }
  156. # Collect dependencies from package
  157. collect_deps() {
  158. local deps="$1"
  159. if [ -n "$deps" ]; then
  160. for dep in $deps; do
  161. if [[ ! " $ALL_DEPS " =~ " $dep " ]]; then
  162. ALL_DEPS="$ALL_DEPS $dep"
  163. fi
  164. done
  165. fi
  166. }
  167. # Install new dependencies
  168. install_deps() {
  169. if [ "$NO_DEPS" = true ]; then
  170. return
  171. fi
  172. ALL_DEPS=$(echo "$ALL_DEPS" | xargs)
  173. if [ -z "$ALL_DEPS" ]; then
  174. return
  175. fi
  176. print_step "Installing dependencies..."
  177. log "Dependencies: $ALL_DEPS"
  178. if [ "$DRY_RUN" = true ]; then
  179. log "[DRY-RUN] apt-get update && apt-get install -y $ALL_DEPS"
  180. else
  181. apt-get update -qq >> "$UPDATE_LOG" 2>&1
  182. apt-get install -y --no-install-recommends $ALL_DEPS >> "$UPDATE_LOG" 2>&1
  183. fi
  184. print_summary "Dependencies installed"
  185. }
  186. # Create backup
  187. create_backup() {
  188. if [ "$BACKUP" = false ]; then
  189. return
  190. fi
  191. local backup_name="backup-$(date +%Y%m%d-%H%M%S)"
  192. local backup_path="$BACKUP_DIR/$backup_name"
  193. print_step "Creating backup..."
  194. log "Backup path: $backup_path"
  195. run_cmd mkdir -p "$backup_path"
  196. run_cmd cp -a "$PREFIX" "$backup_path/opt-smartbotic"
  197. run_cmd cp -a "$CONFDIR" "$backup_path/etc-smartbotic"
  198. # Save current versions
  199. if [ "$DRY_RUN" = false ]; then
  200. for service in "${SERVICES[@]}"; do
  201. local binary="$BINDIR/$service"
  202. if [ -f "$binary" ]; then
  203. echo "$service: $(md5sum "$binary" | cut -d' ' -f1)" >> "$backup_path/versions.txt"
  204. fi
  205. done
  206. fi
  207. print_summary "Backup: $backup_path"
  208. }
  209. # Stop a service gracefully
  210. stop_service() {
  211. local service="$1"
  212. if systemctl is-active --quiet "$service" 2>/dev/null; then
  213. log "Stopping $service..."
  214. run_cmd systemctl stop "$service"
  215. if [ "$DRY_RUN" = false ]; then
  216. sleep 2
  217. fi
  218. fi
  219. }
  220. # Start a service
  221. start_service() {
  222. local service="$1"
  223. if systemctl is-enabled --quiet "$service" 2>/dev/null; then
  224. log "Starting $service..."
  225. run_cmd systemctl start "$service"
  226. if [ "$DRY_RUN" = false ]; then
  227. sleep 3
  228. if ! systemctl is-active --quiet "$service"; then
  229. print_error "$service failed to start! Check: journalctl -u $service -n 50"
  230. log "Service $service failed to start"
  231. return 1
  232. fi
  233. log "$service started successfully"
  234. fi
  235. else
  236. log "$service is not enabled, skipping start"
  237. fi
  238. }
  239. # Update a single service
  240. update_service() {
  241. local service="$1"
  242. local service_name="${service#smartbotic-}"
  243. local pkg
  244. pkg=$(find "$PACKAGE_DIR" -name "$service-*.tar.gz" -type f | head -1)
  245. if [ -z "$pkg" ]; then
  246. log "No package found for $service, skipping"
  247. return 0
  248. fi
  249. print_step "Updating $service_name..."
  250. if [ "$ROLLING" = true ] && [ "$SKIP_RESTART" = false ]; then
  251. stop_service "$service"
  252. fi
  253. local tmpdir
  254. tmpdir=$(mktemp -d)
  255. tar -xzf "$pkg" -C "$tmpdir"
  256. local pkg_root
  257. pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1)
  258. # Update binary
  259. if [ -f "$pkg_root/bin/$service" ]; then
  260. log "Installing new binary..."
  261. run_cmd install -m 755 "$pkg_root/bin/$service" "$BINDIR/$service"
  262. fi
  263. # Update management scripts
  264. for script_bin in "$pkg_root/bin"/smartbotic-install "$pkg_root/bin"/smartbotic-update "$pkg_root/bin"/smartbotic-setup-nginx; do
  265. if [ -f "$script_bin" ]; then
  266. local script_name=$(basename "$script_bin")
  267. log "Updating script: $script_name"
  268. run_cmd install -m 755 "$script_bin" "$BINDIR/$script_name"
  269. fi
  270. done
  271. # Update systemd service file
  272. if [ -d "$pkg_root/etc/systemd/system" ]; then
  273. for svc_file in "$pkg_root/etc/systemd/system"/*.service; do
  274. if [ -f "$svc_file" ]; then
  275. log "Updating systemd service..."
  276. run_cmd install -m 644 "$svc_file" "/etc/systemd/system/"
  277. fi
  278. done
  279. fi
  280. # Check for new config defaults (never overwrite)
  281. if [ -d "$pkg_root/etc/smartbotic" ]; then
  282. for cfg in "$pkg_root/etc/smartbotic"/*; do
  283. local cfg_name=$(basename "$cfg")
  284. local dest_name="${cfg_name%.default}"
  285. local dest="$CONFDIR/$dest_name"
  286. if [ -f "$dest" ]; then
  287. if ! diff -q "$cfg" "$dest" > /dev/null 2>&1; then
  288. log "New config default available: $dest_name.new"
  289. run_cmd cp "$cfg" "$dest.new"
  290. fi
  291. fi
  292. done
  293. fi
  294. # Update data files (migrations, nginx template)
  295. if [ -d "$pkg_root/share/smartbotic" ]; then
  296. if [ -d "$pkg_root/share/smartbotic/database/migrations" ] && [ "$DRY_RUN" = false ]; then
  297. log "Updating database migrations..."
  298. cp -a "$pkg_root/share/smartbotic/database/migrations"/* "$DATADIR/database/migrations/" 2>/dev/null || true
  299. chown -R smartbotic:smartbotic "$DATADIR/database/migrations"
  300. fi
  301. if [ -d "$pkg_root/share/smartbotic/nginx" ] && [ "$DRY_RUN" = false ]; then
  302. log "Updating nginx template..."
  303. cp -a "$pkg_root/share/smartbotic/nginx"/* "$PREFIX/share/smartbotic/nginx/" 2>/dev/null || true
  304. fi
  305. fi
  306. rm -rf "$tmpdir"
  307. if [ "$ROLLING" = true ] && [ "$SKIP_RESTART" = false ]; then
  308. start_service "$service"
  309. fi
  310. print_summary "$service_name updated"
  311. }
  312. # Update WebUI
  313. update_webui() {
  314. local pkg
  315. pkg=$(find "$PACKAGE_DIR" -name "smartbotic-webui-*.tar.gz" -type f | head -1)
  316. if [ -z "$pkg" ]; then
  317. log "No WebUI package found, skipping"
  318. return
  319. fi
  320. print_step "Updating webui..."
  321. local tmpdir
  322. tmpdir=$(mktemp -d)
  323. trap "rm -rf '$tmpdir'" RETURN
  324. tar -xzf "$pkg" -C "$tmpdir"
  325. local pkg_root
  326. pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1)
  327. if [ -d "$pkg_root/share/smartbotic/webui" ]; then
  328. log "Installing WebUI files..."
  329. if [ "$DRY_RUN" = false ]; then
  330. rm -rf "$DATADIR/microbit/webui"/*
  331. cp -a "$pkg_root/share/smartbotic/webui"/* "$DATADIR/microbit/webui/"
  332. chown -R smartbotic:smartbotic "$DATADIR/microbit/webui"
  333. fi
  334. fi
  335. print_summary "WebUI updated"
  336. }
  337. # Main
  338. main() {
  339. # Initialize logging
  340. init_logging
  341. if [ "$QUIET" = false ]; then
  342. echo ""
  343. echo -e "${GREEN}Smartbotic Update${NC}"
  344. echo "================="
  345. fi
  346. log "Package dir: $PACKAGE_DIR"
  347. log "Rolling: $ROLLING"
  348. log "Backup: $BACKUP"
  349. if [ "$DRY_RUN" = true ]; then
  350. print_warn "DRY-RUN MODE - no changes will be made"
  351. fi
  352. # Create backup if requested
  353. create_backup
  354. # Collect dependencies from all packages
  355. print_step "Checking packages..."
  356. local pkg_count=0
  357. for pkg in "$PACKAGE_DIR"/smartbotic-*.tar.gz; do
  358. if [ -f "$pkg" ]; then
  359. pkg_count=$((pkg_count + 1))
  360. local tmpdir=$(mktemp -d)
  361. tar -xzf "$pkg" -C "$tmpdir" 2>/dev/null
  362. local pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1)
  363. if [ -f "$pkg_root/DEPS" ]; then
  364. collect_deps "$(cat "$pkg_root/DEPS")"
  365. fi
  366. rm -rf "$tmpdir"
  367. fi
  368. done
  369. print_summary "Found $pkg_count packages"
  370. install_deps
  371. if [ "$ROLLING" = false ] && [ "$SKIP_RESTART" = false ]; then
  372. # Stop all services in reverse order
  373. print_step "Stopping services..."
  374. for service in $(printf '%s\n' "${SERVICES[@]}" | tac); do
  375. stop_service "$service"
  376. done
  377. print_summary "Services stopped"
  378. fi
  379. # Update each service
  380. for service in "${SERVICES[@]}"; do
  381. update_service "$service"
  382. done
  383. # Update WebUI
  384. update_webui
  385. # Reload systemd
  386. print_step "Finalizing..."
  387. run_cmd systemctl daemon-reload
  388. if [ "$ROLLING" = false ] && [ "$SKIP_RESTART" = false ]; then
  389. # Start all services
  390. print_step "Starting services..."
  391. for service in "${SERVICES[@]}"; do
  392. start_service "$service"
  393. done
  394. print_summary "Services started"
  395. fi
  396. print_summary "Update complete"
  397. if [ "$QUIET" = false ]; then
  398. echo ""
  399. echo -e "${GREEN}Done!${NC} Log: $UPDATE_LOG"
  400. echo ""
  401. echo "Verify:"
  402. echo " systemctl status smartbotic-database smartbotic-microbit"
  403. echo " journalctl -u smartbotic-microbit -f"
  404. echo ""
  405. fi
  406. log "Update completed successfully"
  407. }
  408. main