install.sh 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. #!/bin/bash
  2. # Smartbotic Service Installer
  3. # Installs Smartbotic service packages with config preservation and proper security
  4. #
  5. # Usage:
  6. # sudo ./install.sh smartbotic-*.tar.gz
  7. # sudo ./install.sh --dry-run smartbotic-database-*.tar.gz smartbotic-microbit-*.tar.gz
  8. set -e
  9. # Default paths
  10. PREFIX="/opt/smartbotic"
  11. BINDIR="$PREFIX/bin"
  12. CONFDIR="/etc/smartbotic"
  13. DATADIR="/var/lib/smartbotic"
  14. LOGDIR="/var/log/smartbotic"
  15. SYSTEMD_DIR="/etc/systemd/system"
  16. # Smartbotic system user
  17. SMARTBOTIC_USER="smartbotic"
  18. SMARTBOTIC_GROUP="smartbotic"
  19. # Log file (set after PREFIX is finalized)
  20. INSTALL_LOG=""
  21. # Output control
  22. VERBOSE=false
  23. QUIET=false
  24. # Colors for output
  25. RED='\033[0;31m'
  26. GREEN='\033[0;32m'
  27. YELLOW='\033[1;33m'
  28. BLUE='\033[0;34m'
  29. NC='\033[0m'
  30. # Initialize logging
  31. init_logging() {
  32. mkdir -p "$PREFIX"
  33. INSTALL_LOG="$PREFIX/install-$(date +%Y%m%d-%H%M%S).log"
  34. echo "Smartbotic Install Log - $(date)" > "$INSTALL_LOG"
  35. echo "Command: $0 $ORIGINAL_ARGS" >> "$INSTALL_LOG"
  36. echo "========================================" >> "$INSTALL_LOG"
  37. }
  38. # Log to file only
  39. log() {
  40. echo "[$(date +%H:%M:%S)] $1" >> "$INSTALL_LOG"
  41. }
  42. # Print to console (respects quiet mode)
  43. print_info() {
  44. log "INFO: $1"
  45. if [ "$QUIET" = false ]; then
  46. if [ "$VERBOSE" = true ]; then
  47. echo -e "${GREEN}[INFO]${NC} $1"
  48. fi
  49. fi
  50. }
  51. print_warn() {
  52. log "WARN: $1"
  53. if [ "$QUIET" = false ]; then
  54. echo -e "${YELLOW}[WARN]${NC} $1"
  55. fi
  56. }
  57. print_error() {
  58. log "ERROR: $1"
  59. echo -e "${RED}[ERROR]${NC} $1" >&2
  60. }
  61. # Always show steps (progress indicator)
  62. print_step() {
  63. log "STEP: $1"
  64. if [ "$QUIET" = false ]; then
  65. echo -e "${BLUE}▶${NC} $1"
  66. fi
  67. }
  68. # Summary line (always shown unless quiet)
  69. print_summary() {
  70. log "SUMMARY: $1"
  71. if [ "$QUIET" = false ]; then
  72. echo -e " ${GREEN}✓${NC} $1"
  73. fi
  74. }
  75. usage() {
  76. cat <<EOF
  77. Smartbotic Service Installer
  78. Usage: $0 [OPTIONS] PACKAGE.tar.gz [PACKAGE2.tar.gz ...]
  79. Options:
  80. --prefix DIR Install prefix (default: /opt/smartbotic)
  81. --confdir DIR Config directory (default: /etc/smartbotic)
  82. --user USER Service user (default: smartbotic)
  83. --group GROUP Service group (default: smartbotic)
  84. --no-systemd Skip systemd service installation
  85. --no-user Skip user/group creation (use existing)
  86. --no-deps Skip apt dependency installation
  87. --preserve-all Preserve all existing files (no overwrites)
  88. --force Overwrite existing binaries
  89. --dry-run Show what would be done without making changes
  90. -v, --verbose Show detailed output
  91. -q, --quiet Minimal output (errors only)
  92. -h, --help Show this help
  93. Log file: Install details are logged to \$PREFIX/install-YYYYMMDD-HHMMSS.log
  94. Examples:
  95. # Install all packages (minimal output)
  96. sudo $0 smartbotic-*.tar.gz
  97. # Install with verbose output
  98. sudo $0 -v smartbotic-*.tar.gz
  99. # Test installation
  100. $0 --dry-run smartbotic-*.tar.gz
  101. EOF
  102. }
  103. # Parse arguments
  104. NO_SYSTEMD=false
  105. NO_USER=false
  106. NO_DEPS=false
  107. PRESERVE_ALL=false
  108. FORCE=false
  109. DRY_RUN=false
  110. PACKAGES=()
  111. ALL_DEPS=""
  112. ORIGINAL_ARGS="$*"
  113. while [[ $# -gt 0 ]]; do
  114. case $1 in
  115. --prefix)
  116. PREFIX="$2"
  117. BINDIR="$PREFIX/bin"
  118. shift 2
  119. ;;
  120. --confdir)
  121. CONFDIR="$2"
  122. shift 2
  123. ;;
  124. --user)
  125. SMARTBOTIC_USER="$2"
  126. shift 2
  127. ;;
  128. --group)
  129. SMARTBOTIC_GROUP="$2"
  130. shift 2
  131. ;;
  132. --no-systemd)
  133. NO_SYSTEMD=true
  134. shift
  135. ;;
  136. --no-user)
  137. NO_USER=true
  138. shift
  139. ;;
  140. --no-deps)
  141. NO_DEPS=true
  142. shift
  143. ;;
  144. --preserve-all)
  145. PRESERVE_ALL=true
  146. shift
  147. ;;
  148. --force)
  149. FORCE=true
  150. shift
  151. ;;
  152. --dry-run)
  153. DRY_RUN=true
  154. shift
  155. ;;
  156. -v|--verbose)
  157. VERBOSE=true
  158. shift
  159. ;;
  160. -q|--quiet)
  161. QUIET=true
  162. shift
  163. ;;
  164. -h|--help)
  165. usage
  166. exit 0
  167. ;;
  168. -*)
  169. print_error "Unknown option: $1"
  170. usage
  171. exit 1
  172. ;;
  173. *)
  174. PACKAGES+=("$1")
  175. shift
  176. ;;
  177. esac
  178. done
  179. if [ ${#PACKAGES[@]} -eq 0 ]; then
  180. print_error "No packages specified"
  181. usage
  182. exit 1
  183. fi
  184. # Check root privileges
  185. if [ "$EUID" -ne 0 ] && [ "$DRY_RUN" = false ]; then
  186. print_error "This script must be run as root (or use --dry-run)"
  187. exit 1
  188. fi
  189. # Run command, log output
  190. run_cmd() {
  191. log "CMD: $*"
  192. if [ "$DRY_RUN" = true ]; then
  193. log " [DRY-RUN] Skipped"
  194. if [ "$VERBOSE" = true ]; then
  195. echo " [DRY-RUN] $*"
  196. fi
  197. else
  198. local output
  199. if output=$("$@" 2>&1); then
  200. if [ -n "$output" ]; then
  201. echo "$output" >> "$INSTALL_LOG"
  202. fi
  203. else
  204. local exit_code=$?
  205. if [ -n "$output" ]; then
  206. echo "$output" >> "$INSTALL_LOG"
  207. fi
  208. return $exit_code
  209. fi
  210. fi
  211. }
  212. # Collect dependencies from package
  213. collect_deps() {
  214. local deps="$1"
  215. if [ -n "$deps" ]; then
  216. for dep in $deps; do
  217. if [[ ! " $ALL_DEPS " =~ " $dep " ]]; then
  218. ALL_DEPS="$ALL_DEPS $dep"
  219. fi
  220. done
  221. fi
  222. }
  223. # Install all collected dependencies
  224. install_deps() {
  225. if [ "$NO_DEPS" = true ]; then
  226. print_info "Skipping dependency installation (--no-deps)"
  227. return
  228. fi
  229. ALL_DEPS=$(echo "$ALL_DEPS" | xargs)
  230. if [ -z "$ALL_DEPS" ]; then
  231. print_info "No dependencies to install"
  232. return
  233. fi
  234. print_step "Installing dependencies..."
  235. log "Dependencies: $ALL_DEPS"
  236. if [ "$DRY_RUN" = true ]; then
  237. log "[DRY-RUN] apt-get update && apt-get install -y $ALL_DEPS"
  238. else
  239. apt-get update -qq >> "$INSTALL_LOG" 2>&1
  240. apt-get install -y --no-install-recommends $ALL_DEPS >> "$INSTALL_LOG" 2>&1
  241. fi
  242. print_summary "Dependencies installed"
  243. }
  244. # Create system user and group
  245. create_user() {
  246. if [ "$NO_USER" = true ]; then
  247. print_info "Skipping user creation (--no-user)"
  248. return
  249. fi
  250. print_step "Setting up system user..."
  251. if ! getent group "$SMARTBOTIC_GROUP" > /dev/null 2>&1; then
  252. log "Creating group: $SMARTBOTIC_GROUP"
  253. run_cmd groupadd --system "$SMARTBOTIC_GROUP"
  254. fi
  255. if ! getent passwd "$SMARTBOTIC_USER" > /dev/null 2>&1; then
  256. log "Creating user: $SMARTBOTIC_USER"
  257. run_cmd useradd --system \
  258. --gid "$SMARTBOTIC_GROUP" \
  259. --home-dir "$DATADIR" \
  260. --shell /usr/sbin/nologin \
  261. --comment "Smartbotic Service User" \
  262. "$SMARTBOTIC_USER"
  263. fi
  264. print_summary "User $SMARTBOTIC_USER ready"
  265. }
  266. # Create directory structure
  267. create_directories() {
  268. print_step "Creating directories..."
  269. local dirs=(
  270. "$PREFIX"
  271. "$BINDIR"
  272. "$PREFIX/share/smartbotic/nginx"
  273. "$CONFDIR"
  274. "$DATADIR"
  275. "$DATADIR/database"
  276. "$DATADIR/database/storage"
  277. "$DATADIR/database/migrations"
  278. "$DATADIR/microbit"
  279. "$DATADIR/microbit/webui"
  280. "$LOGDIR"
  281. )
  282. for dir in "${dirs[@]}"; do
  283. if [ ! -d "$dir" ]; then
  284. log "Creating: $dir"
  285. run_cmd mkdir -p "$dir"
  286. fi
  287. done
  288. if [ "$DRY_RUN" = false ]; then
  289. chown -R "$SMARTBOTIC_USER:$SMARTBOTIC_GROUP" "$DATADIR" "$LOGDIR"
  290. chown root:"$SMARTBOTIC_GROUP" "$CONFDIR"
  291. chmod 750 "$CONFDIR"
  292. fi
  293. print_summary "Directories created"
  294. }
  295. # Install a single package
  296. install_package() {
  297. local pkg="$1"
  298. local pkg_basename=$(basename "$pkg")
  299. local pkg_name="${pkg_basename%.tar.gz}"
  300. # Extract short name (e.g., "database" from "smartbotic-database-0.1.0-debian13")
  301. local short_name=$(echo "$pkg_name" | sed -E 's/smartbotic-([^-]+).*/\1/')
  302. print_step "Installing $short_name..."
  303. local tmpdir
  304. tmpdir=$(mktemp -d)
  305. trap "rm -rf '$tmpdir'" RETURN
  306. tar -xzf "$pkg" -C "$tmpdir"
  307. local pkg_root="$tmpdir/$pkg_name"
  308. if [ ! -d "$pkg_root" ]; then
  309. pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1)
  310. fi
  311. if [ ! -d "$pkg_root" ]; then
  312. print_error "Could not find package root in archive"
  313. return 1
  314. fi
  315. # Collect dependencies
  316. if [ -f "$pkg_root/DEPS" ]; then
  317. collect_deps "$(cat "$pkg_root/DEPS")"
  318. fi
  319. local installed_items=()
  320. # Install binaries
  321. if [ -d "$pkg_root/bin" ]; then
  322. for binary in "$pkg_root/bin"/*; do
  323. if [ -f "$binary" ]; then
  324. local bin_name=$(basename "$binary")
  325. local dest="$BINDIR/$bin_name"
  326. if [ -f "$dest" ] && [ "$FORCE" = false ] && [ "$PRESERVE_ALL" = true ]; then
  327. log "Skipping existing: $dest"
  328. else
  329. log "Installing binary: $dest"
  330. run_cmd install -m 755 "$binary" "$dest"
  331. installed_items+=("binary")
  332. fi
  333. fi
  334. done
  335. fi
  336. # Install configs (never overwrite existing)
  337. if [ -d "$pkg_root/etc/smartbotic" ]; then
  338. for cfg in "$pkg_root/etc/smartbotic"/*; do
  339. if [ -f "$cfg" ]; then
  340. local cfg_name=$(basename "$cfg")
  341. local dest_name="${cfg_name%.default}"
  342. local dest="$CONFDIR/$dest_name"
  343. if [ -f "$dest" ]; then
  344. log "Config exists, preserving: $dest"
  345. if [ "$DRY_RUN" = false ]; then
  346. cp "$cfg" "$dest.new"
  347. chown root:"$SMARTBOTIC_GROUP" "$dest.new"
  348. chmod 640 "$dest.new"
  349. fi
  350. else
  351. log "Installing config: $dest"
  352. run_cmd install -m 640 -o root -g "$SMARTBOTIC_GROUP" "$cfg" "$dest"
  353. installed_items+=("config")
  354. fi
  355. fi
  356. done
  357. fi
  358. # Install systemd services
  359. if [ -d "$pkg_root/etc/systemd/system" ] && [ "$NO_SYSTEMD" = false ]; then
  360. for service in "$pkg_root/etc/systemd/system"/*.service; do
  361. if [ -f "$service" ]; then
  362. local svc_name=$(basename "$service")
  363. log "Installing systemd service: $svc_name"
  364. run_cmd install -m 644 "$service" "$SYSTEMD_DIR/$svc_name"
  365. installed_items+=("service")
  366. fi
  367. done
  368. fi
  369. # Install data files
  370. if [ -d "$pkg_root/share/smartbotic" ]; then
  371. log "Installing data files..."
  372. if [ "$DRY_RUN" = false ]; then
  373. # WebUI files
  374. if [ -d "$pkg_root/share/smartbotic/webui" ]; then
  375. rm -rf "$DATADIR/microbit/webui"/*
  376. cp -a "$pkg_root/share/smartbotic/webui"/* "$DATADIR/microbit/webui/" 2>/dev/null || true
  377. installed_items+=("webui")
  378. fi
  379. # Database migrations
  380. if [ -d "$pkg_root/share/smartbotic/database/migrations" ]; then
  381. cp -a "$pkg_root/share/smartbotic/database/migrations"/* "$DATADIR/database/migrations/" 2>/dev/null || true
  382. installed_items+=("migrations")
  383. fi
  384. # Nginx template
  385. if [ -d "$pkg_root/share/smartbotic/nginx" ]; then
  386. cp -a "$pkg_root/share/smartbotic/nginx"/* "$PREFIX/share/smartbotic/nginx/" 2>/dev/null || true
  387. installed_items+=("nginx")
  388. fi
  389. chown -R "$SMARTBOTIC_USER:$SMARTBOTIC_GROUP" "$DATADIR"
  390. fi
  391. fi
  392. print_summary "$short_name installed"
  393. }
  394. # Main installation
  395. main() {
  396. # Initialize logging first
  397. init_logging
  398. if [ "$QUIET" = false ]; then
  399. echo ""
  400. echo -e "${GREEN}Smartbotic Installer${NC}"
  401. echo "===================="
  402. fi
  403. log "PREFIX=$PREFIX"
  404. log "CONFDIR=$CONFDIR"
  405. log "DATADIR=$DATADIR"
  406. log "USER=$SMARTBOTIC_USER"
  407. log "PACKAGES=${#PACKAGES[@]}"
  408. if [ "$DRY_RUN" = true ]; then
  409. print_warn "DRY-RUN MODE - no changes will be made"
  410. fi
  411. # Create user and directories
  412. create_user
  413. create_directories
  414. # Sort packages: database first, then microbit, then webui, then others
  415. local sorted_packages=()
  416. local database_pkg=""
  417. local microbit_pkg=""
  418. local webui_pkg=""
  419. local other_packages=()
  420. for pkg in "${PACKAGES[@]}"; do
  421. case "$pkg" in
  422. *smartbotic-database*) database_pkg="$pkg" ;;
  423. *smartbotic-microbit*) microbit_pkg="$pkg" ;;
  424. *smartbotic-webui*) webui_pkg="$pkg" ;;
  425. *) other_packages+=("$pkg") ;;
  426. esac
  427. done
  428. [ -n "$database_pkg" ] && sorted_packages+=("$database_pkg")
  429. [ -n "$microbit_pkg" ] && sorted_packages+=("$microbit_pkg")
  430. [ -n "$webui_pkg" ] && sorted_packages+=("$webui_pkg")
  431. sorted_packages+=("${other_packages[@]}")
  432. # First pass: collect dependencies
  433. for pkg in "${sorted_packages[@]}"; do
  434. if [ -f "$pkg" ]; then
  435. local tmpdir=$(mktemp -d)
  436. tar -xzf "$pkg" -C "$tmpdir" 2>/dev/null
  437. local pkg_root=$(find "$tmpdir" -maxdepth 1 -type d ! -path "$tmpdir" | head -1)
  438. if [ -f "$pkg_root/DEPS" ]; then
  439. collect_deps "$(cat "$pkg_root/DEPS")"
  440. fi
  441. rm -rf "$tmpdir"
  442. fi
  443. done
  444. # Install dependencies
  445. install_deps
  446. # Install packages
  447. for pkg in "${sorted_packages[@]}"; do
  448. if [ ! -f "$pkg" ]; then
  449. print_error "Package not found: $pkg"
  450. continue
  451. fi
  452. install_package "$pkg"
  453. done
  454. # Post-installation
  455. print_step "Finalizing..."
  456. if [ "$NO_SYSTEMD" = false ] && [ "$DRY_RUN" = false ]; then
  457. log "Reloading systemd daemon..."
  458. systemctl daemon-reload >> "$INSTALL_LOG" 2>&1
  459. fi
  460. print_summary "Installation complete"
  461. if [ "$QUIET" = false ]; then
  462. echo ""
  463. echo -e "${GREEN}Done!${NC} Log: $INSTALL_LOG"
  464. echo ""
  465. echo "Next steps:"
  466. echo " 1. Configure: $CONFDIR/smartbotic.env"
  467. echo " (copy from $CONFDIR/smartbotic.env.default if not exists)"
  468. echo " Set JWT_SECRET, SMTP, and CallerAI API settings"
  469. echo " 2. Review configs: $CONFDIR/database.json, $CONFDIR/microbit.json"
  470. echo " 3. Enable services:"
  471. echo " systemctl enable smartbotic-database smartbotic-microbit"
  472. echo " 4. Start services:"
  473. echo " systemctl start smartbotic-database"
  474. echo " systemctl start smartbotic-microbit"
  475. echo " 5. Setup nginx (optional):"
  476. echo " $BINDIR/smartbotic-setup-nginx --domain example.com --email admin@example.com"
  477. echo ""
  478. fi
  479. log "Installation completed successfully"
  480. }
  481. main