| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- #!/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 <<EOF
- Smartbotic Update Script
- Usage: $0 [OPTIONS] PACKAGE_DIR
- Options:
- --rolling Rolling update (one service at a time, minimizes downtime)
- --backup Create backup before update
- --skip-restart Update files but don't restart services
- --no-deps Skip dependency installation
- --dry-run Show what would be done
- -v, --verbose Show detailed output
- -q, --quiet Minimal output (errors only)
- -h, --help Show this help
- Log file: Update details are logged to \$PREFIX/update-YYYYMMDD-HHMMSS.log
- Examples:
- # Standard update (stops all, updates, starts all)
- sudo $0 /tmp/packages/
- # Rolling update with backup (recommended for production)
- sudo $0 --rolling --backup /tmp/packages/
- # Update without restarting (for manual control)
- sudo $0 --skip-restart /tmp/packages/
- EOF
- }
- ROLLING=false
- BACKUP=false
- DRY_RUN=false
- SKIP_RESTART=false
- NO_DEPS=false
- PACKAGE_DIR=""
- ALL_DEPS=""
- ORIGINAL_ARGS="$*"
- while [[ $# -gt 0 ]]; do
- case $1 in
- --rolling) ROLLING=true; shift ;;
- --backup) BACKUP=true; shift ;;
- --dry-run) DRY_RUN=true; shift ;;
- --skip-restart) SKIP_RESTART=true; shift ;;
- --no-deps) NO_DEPS=true; shift ;;
- -v|--verbose) VERBOSE=true; shift ;;
- -q|--quiet) QUIET=true; shift ;;
- -h|--help) usage; exit 0 ;;
- -*) print_error "Unknown option: $1"; usage; exit 1 ;;
- *) PACKAGE_DIR="$1"; shift ;;
- esac
- done
- if [ -z "$PACKAGE_DIR" ]; then
- print_error "Package directory required"
- usage
- exit 1
- fi
- if [ ! -d "$PACKAGE_DIR" ]; then
- print_error "Package directory does not exist: $PACKAGE_DIR"
- exit 1
- fi
- # Check root
- if [ "$EUID" -ne 0 ] && [ "$DRY_RUN" = false ]; then
- print_error "Must run as root (or use --dry-run)"
- exit 1
- fi
- # Run command, log output
- run_cmd() {
- log "CMD: $*"
- if [ "$DRY_RUN" = true ]; then
- log " [DRY-RUN] Skipped"
- if [ "$VERBOSE" = true ]; then
- echo " [DRY-RUN] $*"
- fi
- else
- local output
- if output=$("$@" 2>&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
|