#!/bin/bash # Smartbotic Update Script # Updates Smartbotic services while preserving all configurations # # Usage: # sudo ./update.sh /path/to/packages/ # sudo ./update.sh --rolling --backup /path/to/packages/ set -e # Default paths (must match installation) PREFIX="${PREFIX:-/opt/smartbotic}" BINDIR="$PREFIX/bin" CONFDIR="${CONFDIR:-/etc/smartbotic}" DATADIR="${DATADIR:-/var/lib/smartbotic}" BACKUP_DIR="/var/backups/smartbotic" # Log file UPDATE_LOG="" # Output control VERBOSE=false QUIET=false # Service order (database first - it's a dependency) SERVICES=( "smartbotic-database" "smartbotic-microbit" ) # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Initialize logging init_logging() { mkdir -p "$PREFIX" UPDATE_LOG="$PREFIX/update-$(date +%Y%m%d-%H%M%S).log" echo "Smartbotic Update Log - $(date)" > "$UPDATE_LOG" echo "Command: $0 $ORIGINAL_ARGS" >> "$UPDATE_LOG" echo "========================================" >> "$UPDATE_LOG" } # Log to file only log() { echo "[$(date +%H:%M:%S)] $1" >> "$UPDATE_LOG" } # Print to console (respects quiet mode) print_info() { log "INFO: $1" if [ "$QUIET" = false ] && [ "$VERBOSE" = true ]; then echo -e "${GREEN}[INFO]${NC} $1" fi } print_warn() { log "WARN: $1" if [ "$QUIET" = false ]; then echo -e "${YELLOW}[WARN]${NC} $1" fi } print_error() { log "ERROR: $1" echo -e "${RED}[ERROR]${NC} $1" >&2 } # Always show steps (progress indicator) print_step() { log "STEP: $1" if [ "$QUIET" = false ]; then echo -e "${BLUE}▶${NC} $1" fi } # Summary line (always shown unless quiet) print_summary() { log "SUMMARY: $1" if [ "$QUIET" = false ]; then echo -e " ${GREEN}✓${NC} $1" fi } usage() { cat <&1); then if [ -n "$output" ]; then echo "$output" >> "$UPDATE_LOG" fi else local exit_code=$? if [ -n "$output" ]; then echo "$output" >> "$UPDATE_LOG" fi return $exit_code fi fi } # Collect dependencies from package collect_deps() { local deps="$1" if [ -n "$deps" ]; then for dep in $deps; do if [[ ! " $ALL_DEPS " =~ " $dep " ]]; then ALL_DEPS="$ALL_DEPS $dep" fi done fi } # Install new dependencies install_deps() { if [ "$NO_DEPS" = true ]; then return fi ALL_DEPS=$(echo "$ALL_DEPS" | xargs) if [ -z "$ALL_DEPS" ]; then return fi print_step "Installing dependencies..." log "Dependencies: $ALL_DEPS" if [ "$DRY_RUN" = true ]; then log "[DRY-RUN] apt-get update && apt-get install -y $ALL_DEPS" else apt-get update -qq >> "$UPDATE_LOG" 2>&1 apt-get install -y --no-install-recommends $ALL_DEPS >> "$UPDATE_LOG" 2>&1 fi print_summary "Dependencies installed" } # Create backup create_backup() { if [ "$BACKUP" = false ]; then return fi local backup_name="backup-$(date +%Y%m%d-%H%M%S)" local backup_path="$BACKUP_DIR/$backup_name" print_step "Creating backup..." log "Backup path: $backup_path" run_cmd mkdir -p "$backup_path" run_cmd cp -a "$PREFIX" "$backup_path/opt-smartbotic" run_cmd cp -a "$CONFDIR" "$backup_path/etc-smartbotic" # Save current versions if [ "$DRY_RUN" = false ]; then for service in "${SERVICES[@]}"; do local binary="$BINDIR/$service" if [ -f "$binary" ]; then echo "$service: $(md5sum "$binary" | cut -d' ' -f1)" >> "$backup_path/versions.txt" fi done fi print_summary "Backup: $backup_path" } # Stop a service gracefully stop_service() { local service="$1" if systemctl is-active --quiet "$service" 2>/dev/null; then log "Stopping $service..." run_cmd systemctl stop "$service" if [ "$DRY_RUN" = false ]; then sleep 2 fi fi } # Start a service start_service() { local service="$1" if systemctl is-enabled --quiet "$service" 2>/dev/null; then log "Starting $service..." run_cmd systemctl start "$service" if [ "$DRY_RUN" = false ]; then sleep 3 if ! systemctl is-active --quiet "$service"; then print_error "$service failed to start! Check: journalctl -u $service -n 50" log "Service $service failed to start" return 1 fi log "$service started successfully" fi else log "$service is not enabled, skipping start" fi } # Update a single service update_service() { local service="$1" local service_name="${service#smartbotic-}" local pkg pkg=$(find "$PACKAGE_DIR" -name "$service-*.tar.gz" -type f | head -1) if [ -z "$pkg" ]; then log "No package found for $service, skipping" return 0 fi print_step "Updating $service_name..." if [ "$ROLLING" = true ] && [ "$SKIP_RESTART" = false ]; then stop_service "$service" fi local tmpdir tmpdir=$(mktemp -d) tar -xzf "$pkg" -C "$tmpdir" local pkg_root pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1) # Update binary if [ -f "$pkg_root/bin/$service" ]; then log "Installing new binary..." run_cmd install -m 755 "$pkg_root/bin/$service" "$BINDIR/$service" fi # Update management scripts for script_bin in "$pkg_root/bin"/smartbotic-install "$pkg_root/bin"/smartbotic-update "$pkg_root/bin"/smartbotic-setup-nginx; do if [ -f "$script_bin" ]; then local script_name=$(basename "$script_bin") log "Updating script: $script_name" run_cmd install -m 755 "$script_bin" "$BINDIR/$script_name" fi done # Update systemd service file if [ -d "$pkg_root/etc/systemd/system" ]; then for svc_file in "$pkg_root/etc/systemd/system"/*.service; do if [ -f "$svc_file" ]; then log "Updating systemd service..." run_cmd install -m 644 "$svc_file" "/etc/systemd/system/" fi done fi # Check for new config defaults (never overwrite) if [ -d "$pkg_root/etc/smartbotic" ]; then for cfg in "$pkg_root/etc/smartbotic"/*; do local cfg_name=$(basename "$cfg") local dest_name="${cfg_name%.default}" local dest="$CONFDIR/$dest_name" if [ -f "$dest" ]; then if ! diff -q "$cfg" "$dest" > /dev/null 2>&1; then log "New config default available: $dest_name.new" run_cmd cp "$cfg" "$dest.new" fi fi done fi # Update data files (migrations, nginx template) if [ -d "$pkg_root/share/smartbotic" ]; then if [ -d "$pkg_root/share/smartbotic/database/migrations" ] && [ "$DRY_RUN" = false ]; then log "Updating database migrations..." cp -a "$pkg_root/share/smartbotic/database/migrations"/* "$DATADIR/database/migrations/" 2>/dev/null || true chown -R smartbotic:smartbotic "$DATADIR/database/migrations" fi if [ -d "$pkg_root/share/smartbotic/nginx" ] && [ "$DRY_RUN" = false ]; then log "Updating nginx template..." cp -a "$pkg_root/share/smartbotic/nginx"/* "$PREFIX/share/smartbotic/nginx/" 2>/dev/null || true fi fi rm -rf "$tmpdir" if [ "$ROLLING" = true ] && [ "$SKIP_RESTART" = false ]; then start_service "$service" fi print_summary "$service_name updated" } # Update WebUI update_webui() { local pkg pkg=$(find "$PACKAGE_DIR" -name "smartbotic-webui-*.tar.gz" -type f | head -1) if [ -z "$pkg" ]; then log "No WebUI package found, skipping" return fi print_step "Updating webui..." local tmpdir tmpdir=$(mktemp -d) trap "rm -rf '$tmpdir'" RETURN tar -xzf "$pkg" -C "$tmpdir" local pkg_root pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1) if [ -d "$pkg_root/share/smartbotic/webui" ]; then log "Installing WebUI files..." if [ "$DRY_RUN" = false ]; then rm -rf "$DATADIR/microbit/webui"/* cp -a "$pkg_root/share/smartbotic/webui"/* "$DATADIR/microbit/webui/" chown -R smartbotic:smartbotic "$DATADIR/microbit/webui" fi fi print_summary "WebUI updated" } # Main main() { # Initialize logging init_logging if [ "$QUIET" = false ]; then echo "" echo -e "${GREEN}Smartbotic Update${NC}" echo "=================" fi log "Package dir: $PACKAGE_DIR" log "Rolling: $ROLLING" log "Backup: $BACKUP" if [ "$DRY_RUN" = true ]; then print_warn "DRY-RUN MODE - no changes will be made" fi # Create backup if requested create_backup # Collect dependencies from all packages print_step "Checking packages..." local pkg_count=0 for pkg in "$PACKAGE_DIR"/smartbotic-*.tar.gz; do if [ -f "$pkg" ]; then pkg_count=$((pkg_count + 1)) local tmpdir=$(mktemp -d) tar -xzf "$pkg" -C "$tmpdir" 2>/dev/null local pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1) if [ -f "$pkg_root/DEPS" ]; then collect_deps "$(cat "$pkg_root/DEPS")" fi rm -rf "$tmpdir" fi done print_summary "Found $pkg_count packages" install_deps if [ "$ROLLING" = false ] && [ "$SKIP_RESTART" = false ]; then # Stop all services in reverse order print_step "Stopping services..." for service in $(printf '%s\n' "${SERVICES[@]}" | tac); do stop_service "$service" done print_summary "Services stopped" fi # Update each service for service in "${SERVICES[@]}"; do update_service "$service" done # Update WebUI update_webui # Reload systemd print_step "Finalizing..." run_cmd systemctl daemon-reload if [ "$ROLLING" = false ] && [ "$SKIP_RESTART" = false ]; then # Start all services print_step "Starting services..." for service in "${SERVICES[@]}"; do start_service "$service" done print_summary "Services started" fi print_summary "Update complete" if [ "$QUIET" = false ]; then echo "" echo -e "${GREEN}Done!${NC} Log: $UPDATE_LOG" echo "" echo "Verify:" echo " systemctl status smartbotic-database smartbotic-microbit" echo " journalctl -u smartbotic-microbit -f" echo "" fi log "Update completed successfully" } main