| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- #!/bin/bash
- # Smartbotic Service Installer
- # Installs Smartbotic service packages with config preservation and proper security
- #
- # Usage:
- # sudo ./install.sh smartbotic-*.tar.gz
- # sudo ./install.sh --dry-run smartbotic-database-*.tar.gz smartbotic-microbit-*.tar.gz
- set -e
- # Default paths
- PREFIX="/opt/smartbotic"
- BINDIR="$PREFIX/bin"
- CONFDIR="/etc/smartbotic"
- DATADIR="/var/lib/smartbotic"
- LOGDIR="/var/log/smartbotic"
- SYSTEMD_DIR="/etc/systemd/system"
- # Smartbotic system user
- SMARTBOTIC_USER="smartbotic"
- SMARTBOTIC_GROUP="smartbotic"
- # Log file (set after PREFIX is finalized)
- INSTALL_LOG=""
- # Output control
- VERBOSE=false
- QUIET=false
- # 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"
- INSTALL_LOG="$PREFIX/install-$(date +%Y%m%d-%H%M%S).log"
- echo "Smartbotic Install Log - $(date)" > "$INSTALL_LOG"
- echo "Command: $0 $ORIGINAL_ARGS" >> "$INSTALL_LOG"
- echo "========================================" >> "$INSTALL_LOG"
- }
- # Log to file only
- log() {
- echo "[$(date +%H:%M:%S)] $1" >> "$INSTALL_LOG"
- }
- # Print to console (respects quiet mode)
- print_info() {
- log "INFO: $1"
- if [ "$QUIET" = false ]; then
- if [ "$VERBOSE" = true ]; then
- echo -e "${GREEN}[INFO]${NC} $1"
- fi
- 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 Service Installer
- Usage: $0 [OPTIONS] PACKAGE.tar.gz [PACKAGE2.tar.gz ...]
- Options:
- --prefix DIR Install prefix (default: /opt/smartbotic)
- --confdir DIR Config directory (default: /etc/smartbotic)
- --user USER Service user (default: smartbotic)
- --group GROUP Service group (default: smartbotic)
- --no-systemd Skip systemd service installation
- --no-user Skip user/group creation (use existing)
- --no-deps Skip apt dependency installation
- --preserve-all Preserve all existing files (no overwrites)
- --force Overwrite existing binaries
- --dry-run Show what would be done without making changes
- -v, --verbose Show detailed output
- -q, --quiet Minimal output (errors only)
- -h, --help Show this help
- Log file: Install details are logged to \$PREFIX/install-YYYYMMDD-HHMMSS.log
- Examples:
- # Install all packages (minimal output)
- sudo $0 smartbotic-*.tar.gz
- # Install with verbose output
- sudo $0 -v smartbotic-*.tar.gz
- # Test installation
- $0 --dry-run smartbotic-*.tar.gz
- EOF
- }
- # Parse arguments
- NO_SYSTEMD=false
- NO_USER=false
- NO_DEPS=false
- PRESERVE_ALL=false
- FORCE=false
- DRY_RUN=false
- PACKAGES=()
- ALL_DEPS=""
- ORIGINAL_ARGS="$*"
- while [[ $# -gt 0 ]]; do
- case $1 in
- --prefix)
- PREFIX="$2"
- BINDIR="$PREFIX/bin"
- shift 2
- ;;
- --confdir)
- CONFDIR="$2"
- shift 2
- ;;
- --user)
- SMARTBOTIC_USER="$2"
- shift 2
- ;;
- --group)
- SMARTBOTIC_GROUP="$2"
- shift 2
- ;;
- --no-systemd)
- NO_SYSTEMD=true
- shift
- ;;
- --no-user)
- NO_USER=true
- shift
- ;;
- --no-deps)
- NO_DEPS=true
- shift
- ;;
- --preserve-all)
- PRESERVE_ALL=true
- shift
- ;;
- --force)
- FORCE=true
- shift
- ;;
- --dry-run)
- DRY_RUN=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
- ;;
- *)
- PACKAGES+=("$1")
- shift
- ;;
- esac
- done
- if [ ${#PACKAGES[@]} -eq 0 ]; then
- print_error "No packages specified"
- usage
- exit 1
- fi
- # Check root privileges
- if [ "$EUID" -ne 0 ] && [ "$DRY_RUN" = false ]; then
- print_error "This script must be 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" >> "$INSTALL_LOG"
- fi
- else
- local exit_code=$?
- if [ -n "$output" ]; then
- echo "$output" >> "$INSTALL_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 all collected dependencies
- install_deps() {
- if [ "$NO_DEPS" = true ]; then
- print_info "Skipping dependency installation (--no-deps)"
- return
- fi
- ALL_DEPS=$(echo "$ALL_DEPS" | xargs)
- if [ -z "$ALL_DEPS" ]; then
- print_info "No dependencies to install"
- 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 >> "$INSTALL_LOG" 2>&1
- apt-get install -y --no-install-recommends $ALL_DEPS >> "$INSTALL_LOG" 2>&1
- fi
- print_summary "Dependencies installed"
- }
- # Create system user and group
- create_user() {
- if [ "$NO_USER" = true ]; then
- print_info "Skipping user creation (--no-user)"
- return
- fi
- print_step "Setting up system user..."
- if ! getent group "$SMARTBOTIC_GROUP" > /dev/null 2>&1; then
- log "Creating group: $SMARTBOTIC_GROUP"
- run_cmd groupadd --system "$SMARTBOTIC_GROUP"
- fi
- if ! getent passwd "$SMARTBOTIC_USER" > /dev/null 2>&1; then
- log "Creating user: $SMARTBOTIC_USER"
- run_cmd useradd --system \
- --gid "$SMARTBOTIC_GROUP" \
- --home-dir "$DATADIR" \
- --shell /usr/sbin/nologin \
- --comment "Smartbotic Service User" \
- "$SMARTBOTIC_USER"
- fi
- print_summary "User $SMARTBOTIC_USER ready"
- }
- # Create directory structure
- create_directories() {
- print_step "Creating directories..."
- local dirs=(
- "$PREFIX"
- "$BINDIR"
- "$PREFIX/share/smartbotic/nginx"
- "$CONFDIR"
- "$DATADIR"
- "$DATADIR/database"
- "$DATADIR/database/storage"
- "$DATADIR/database/migrations"
- "$DATADIR/microbit"
- "$DATADIR/microbit/webui"
- "$LOGDIR"
- )
- for dir in "${dirs[@]}"; do
- if [ ! -d "$dir" ]; then
- log "Creating: $dir"
- run_cmd mkdir -p "$dir"
- fi
- done
- if [ "$DRY_RUN" = false ]; then
- chown -R "$SMARTBOTIC_USER:$SMARTBOTIC_GROUP" "$DATADIR" "$LOGDIR"
- chown root:"$SMARTBOTIC_GROUP" "$CONFDIR"
- chmod 750 "$CONFDIR"
- fi
- print_summary "Directories created"
- }
- # Install a single package
- install_package() {
- local pkg="$1"
- local pkg_basename=$(basename "$pkg")
- local pkg_name="${pkg_basename%.tar.gz}"
- # Extract short name (e.g., "database" from "smartbotic-database-0.1.0-debian13")
- local short_name=$(echo "$pkg_name" | sed -E 's/smartbotic-([^-]+).*/\1/')
- print_step "Installing $short_name..."
- local tmpdir
- tmpdir=$(mktemp -d)
- trap "rm -rf '$tmpdir'" RETURN
- tar -xzf "$pkg" -C "$tmpdir"
- local pkg_root="$tmpdir/$pkg_name"
- if [ ! -d "$pkg_root" ]; then
- pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1)
- fi
- if [ ! -d "$pkg_root" ]; then
- print_error "Could not find package root in archive"
- return 1
- fi
- # Collect dependencies
- if [ -f "$pkg_root/DEPS" ]; then
- collect_deps "$(cat "$pkg_root/DEPS")"
- fi
- local installed_items=()
- # Install binaries
- if [ -d "$pkg_root/bin" ]; then
- for binary in "$pkg_root/bin"/*; do
- if [ -f "$binary" ]; then
- local bin_name=$(basename "$binary")
- local dest="$BINDIR/$bin_name"
- if [ -f "$dest" ] && [ "$FORCE" = false ] && [ "$PRESERVE_ALL" = true ]; then
- log "Skipping existing: $dest"
- else
- log "Installing binary: $dest"
- run_cmd install -m 755 "$binary" "$dest"
- installed_items+=("binary")
- fi
- fi
- done
- fi
- # Install configs (never overwrite existing)
- if [ -d "$pkg_root/etc/smartbotic" ]; then
- for cfg in "$pkg_root/etc/smartbotic"/*; do
- if [ -f "$cfg" ]; then
- local cfg_name=$(basename "$cfg")
- local dest_name="${cfg_name%.default}"
- local dest="$CONFDIR/$dest_name"
- if [ -f "$dest" ]; then
- log "Config exists, preserving: $dest"
- if [ "$DRY_RUN" = false ]; then
- cp "$cfg" "$dest.new"
- chown root:"$SMARTBOTIC_GROUP" "$dest.new"
- chmod 640 "$dest.new"
- fi
- else
- log "Installing config: $dest"
- run_cmd install -m 640 -o root -g "$SMARTBOTIC_GROUP" "$cfg" "$dest"
- installed_items+=("config")
- fi
- fi
- done
- fi
- # Install systemd services
- if [ -d "$pkg_root/etc/systemd/system" ] && [ "$NO_SYSTEMD" = false ]; then
- for service in "$pkg_root/etc/systemd/system"/*.service; do
- if [ -f "$service" ]; then
- local svc_name=$(basename "$service")
- log "Installing systemd service: $svc_name"
- run_cmd install -m 644 "$service" "$SYSTEMD_DIR/$svc_name"
- installed_items+=("service")
- fi
- done
- fi
- # Install data files
- if [ -d "$pkg_root/share/smartbotic" ]; then
- log "Installing data files..."
- if [ "$DRY_RUN" = false ]; then
- # WebUI files
- if [ -d "$pkg_root/share/smartbotic/webui" ]; then
- rm -rf "$DATADIR/microbit/webui"/*
- cp -a "$pkg_root/share/smartbotic/webui"/* "$DATADIR/microbit/webui/" 2>/dev/null || true
- installed_items+=("webui")
- fi
- # Database migrations
- if [ -d "$pkg_root/share/smartbotic/database/migrations" ]; then
- cp -a "$pkg_root/share/smartbotic/database/migrations"/* "$DATADIR/database/migrations/" 2>/dev/null || true
- installed_items+=("migrations")
- fi
- # Nginx template
- if [ -d "$pkg_root/share/smartbotic/nginx" ]; then
- cp -a "$pkg_root/share/smartbotic/nginx"/* "$PREFIX/share/smartbotic/nginx/" 2>/dev/null || true
- installed_items+=("nginx")
- fi
- chown -R "$SMARTBOTIC_USER:$SMARTBOTIC_GROUP" "$DATADIR"
- fi
- fi
- print_summary "$short_name installed"
- }
- # Main installation
- main() {
- # Initialize logging first
- init_logging
- if [ "$QUIET" = false ]; then
- echo ""
- echo -e "${GREEN}Smartbotic Installer${NC}"
- echo "===================="
- fi
- log "PREFIX=$PREFIX"
- log "CONFDIR=$CONFDIR"
- log "DATADIR=$DATADIR"
- log "USER=$SMARTBOTIC_USER"
- log "PACKAGES=${#PACKAGES[@]}"
- if [ "$DRY_RUN" = true ]; then
- print_warn "DRY-RUN MODE - no changes will be made"
- fi
- # Create user and directories
- create_user
- create_directories
- # Sort packages: database first, then microbit, then webui, then others
- local sorted_packages=()
- local database_pkg=""
- local microbit_pkg=""
- local webui_pkg=""
- local other_packages=()
- for pkg in "${PACKAGES[@]}"; do
- case "$pkg" in
- *smartbotic-database*) database_pkg="$pkg" ;;
- *smartbotic-microbit*) microbit_pkg="$pkg" ;;
- *smartbotic-webui*) webui_pkg="$pkg" ;;
- *) other_packages+=("$pkg") ;;
- esac
- done
- [ -n "$database_pkg" ] && sorted_packages+=("$database_pkg")
- [ -n "$microbit_pkg" ] && sorted_packages+=("$microbit_pkg")
- [ -n "$webui_pkg" ] && sorted_packages+=("$webui_pkg")
- sorted_packages+=("${other_packages[@]}")
- # First pass: collect dependencies
- for pkg in "${sorted_packages[@]}"; do
- if [ -f "$pkg" ]; then
- 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
- # Install dependencies
- install_deps
- # Install packages
- for pkg in "${sorted_packages[@]}"; do
- if [ ! -f "$pkg" ]; then
- print_error "Package not found: $pkg"
- continue
- fi
- install_package "$pkg"
- done
- # Post-installation
- print_step "Finalizing..."
- if [ "$NO_SYSTEMD" = false ] && [ "$DRY_RUN" = false ]; then
- log "Reloading systemd daemon..."
- systemctl daemon-reload >> "$INSTALL_LOG" 2>&1
- fi
- print_summary "Installation complete"
- if [ "$QUIET" = false ]; then
- echo ""
- echo -e "${GREEN}Done!${NC} Log: $INSTALL_LOG"
- echo ""
- echo "Next steps:"
- echo " 1. Configure: $CONFDIR/smartbotic.env"
- echo " (copy from $CONFDIR/smartbotic.env.default if not exists)"
- echo " Set JWT_SECRET, SMTP, and CallerAI API settings"
- echo " 2. Review configs: $CONFDIR/database.json, $CONFDIR/microbit.json"
- echo " 3. Enable services:"
- echo " systemctl enable smartbotic-database smartbotic-microbit"
- echo " 4. Start services:"
- echo " systemctl start smartbotic-database"
- echo " systemctl start smartbotic-microbit"
- echo " 5. Setup nginx (optional):"
- echo " $BINDIR/smartbotic-setup-nginx --domain example.com --email admin@example.com"
- echo ""
- fi
- log "Installation completed successfully"
- }
- main
|