install.sh 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. #!/usr/bin/env bash
  2. #
  3. # SpoolBuddy Installation Script for Raspberry Pi
  4. #
  5. # Supports two scenarios:
  6. # 1) SpoolBuddy only — NFC/scale companion connecting to a remote Bambuddy instance
  7. # 2) SpoolBuddy + Bambuddy — both running natively on this Raspberry Pi
  8. #
  9. # Usage:
  10. # Interactive: curl -fsSL https://raw.githubusercontent.com/maziggy/bambuddy/main/spoolbuddy/install.sh -o install.sh && chmod +x install.sh && sudo ./install.sh
  11. # Unattended: sudo ./install.sh --mode spoolbuddy --bambuddy-url http://192.168.1.100:8000 --api-key bb_xxx --yes
  12. #
  13. # Options:
  14. # --mode MODE Installation mode: "spoolbuddy" (companion only) or "full" (both)
  15. # --bambuddy-url URL Bambuddy server URL (required for spoolbuddy mode)
  16. # --api-key KEY Bambuddy API key (required for spoolbuddy mode)
  17. # --path PATH Installation directory (default: /opt/spoolbuddy or /opt/bambuddy)
  18. # --port PORT Bambuddy port (full mode only, default: 8000)
  19. # --yes, -y Non-interactive mode, accept defaults
  20. # --help, -h Show this help message
  21. #
  22. set -e
  23. # ─────────────────────────────────────────────────────────────────────────────
  24. # Constants
  25. # ─────────────────────────────────────────────────────────────────────────────
  26. RED='\033[0;31m'
  27. GREEN='\033[0;32m'
  28. YELLOW='\033[1;33m'
  29. CYAN='\033[0;36m'
  30. BOLD='\033[1m'
  31. NC='\033[0m'
  32. GITHUB_REPO="https://github.com/maziggy/bambuddy.git"
  33. SPOOLBUDDY_SERVICE_USER="spoolbuddy"
  34. BAMBUDDY_SERVICE_USER="bambuddy"
  35. # Packages needed for SpoolBuddy hardware (NFC reader + scale)
  36. SYSTEM_PACKAGES="python3 python3-pip python3-venv python3-dev python3-spidev python3-libgpiod gpiod libgpiod-dev i2c-tools git"
  37. # Python packages for SpoolBuddy daemon
  38. SPOOLBUDDY_PIP_PACKAGES="spidev gpiod smbus2 httpx"
  39. # ─────────────────────────────────────────────────────────────────────────────
  40. # Variables (set by args or prompts)
  41. # ─────────────────────────────────────────────────────────────────────────────
  42. INSTALL_MODE="" # "spoolbuddy" or "full"
  43. INSTALL_PATH=""
  44. BAMBUDDY_URL=""
  45. API_KEY=""
  46. BAMBUDDY_PORT="8000"
  47. NON_INTERACTIVE="false"
  48. REBOOT_NEEDED="false"
  49. # ─────────────────────────────────────────────────────────────────────────────
  50. # Helpers
  51. # ─────────────────────────────────────────────────────────────────────────────
  52. info() { echo -e "${CYAN}[INFO]${NC} $1"; }
  53. success() { echo -e "${GREEN}[OK]${NC} $1"; }
  54. warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
  55. error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
  56. prompt() {
  57. local prompt_text="$1"
  58. local default_value="$2"
  59. local var_name="$3"
  60. if [[ "$NON_INTERACTIVE" == "true" ]]; then
  61. eval "$var_name=\"$default_value\""
  62. return
  63. fi
  64. if [[ -n "$default_value" ]]; then
  65. echo -en "${BOLD}$prompt_text${NC} [${CYAN}$default_value${NC}]: "
  66. else
  67. echo -en "${BOLD}$prompt_text${NC}: "
  68. fi
  69. read -r input
  70. if [[ -z "$input" ]]; then
  71. eval "$var_name=\"$default_value\""
  72. else
  73. eval "$var_name=\"$input\""
  74. fi
  75. }
  76. prompt_yes_no() {
  77. local prompt_text="$1"
  78. local default="$2"
  79. if [[ "$NON_INTERACTIVE" == "true" ]]; then
  80. [[ "$default" == "y" ]] && return 0 || return 1
  81. fi
  82. local yn_hint="[y/n]"
  83. [[ "$default" == "y" ]] && yn_hint="[Y/n]"
  84. [[ "$default" == "n" ]] && yn_hint="[y/N]"
  85. while true; do
  86. echo -en "${BOLD}$prompt_text${NC} $yn_hint: "
  87. read -r yn
  88. [[ -z "$yn" ]] && yn="$default"
  89. case "$yn" in
  90. [Yy]* ) return 0;;
  91. [Nn]* ) return 1;;
  92. * ) echo "Please answer yes or no.";;
  93. esac
  94. done
  95. }
  96. show_help() {
  97. echo "SpoolBuddy Installation Script for Raspberry Pi"
  98. echo ""
  99. echo "Usage: sudo $0 [OPTIONS]"
  100. echo ""
  101. echo "Options:"
  102. echo " --mode MODE \"spoolbuddy\" (companion only) or \"full\" (Bambuddy + SpoolBuddy)"
  103. echo " --bambuddy-url URL Bambuddy server URL (required for spoolbuddy mode)"
  104. echo " --api-key KEY Bambuddy API key (required for spoolbuddy mode)"
  105. echo " --path PATH Installation directory (default: /opt/spoolbuddy or /opt/bambuddy)"
  106. echo " --port PORT Bambuddy port (full mode only, default: 8000)"
  107. echo " --yes, -y Non-interactive mode, accept defaults"
  108. echo " --help, -h Show this help message"
  109. echo ""
  110. echo "Examples:"
  111. echo " Interactive:"
  112. echo " sudo ./install.sh"
  113. echo ""
  114. echo " SpoolBuddy companion (unattended):"
  115. echo " sudo ./install.sh --mode spoolbuddy --bambuddy-url http://192.168.1.100:8000 --api-key bb_xxx -y"
  116. echo ""
  117. echo " Full install (unattended):"
  118. echo " sudo ./install.sh --mode full --port 8000 -y"
  119. exit 0
  120. }
  121. # ─────────────────────────────────────────────────────────────────────────────
  122. # Pre-flight Checks
  123. # ─────────────────────────────────────────────────────────────────────────────
  124. check_root() {
  125. if [[ $EUID -ne 0 ]]; then
  126. error "This script must be run as root (use sudo)"
  127. fi
  128. }
  129. check_raspberry_pi() {
  130. if ! grep -q "Raspberry Pi\|BCM2" /proc/cpuinfo 2>/dev/null; then
  131. error "This script is designed for Raspberry Pi only"
  132. fi
  133. # Detect Pi model for hardware recommendations
  134. local model
  135. model=$(tr -d '\0' < /proc/device-tree/model 2>/dev/null) || model="Unknown"
  136. success "Detected: $model"
  137. }
  138. check_raspberry_pi_os() {
  139. if [[ ! -f /etc/os-release ]]; then
  140. error "Cannot detect operating system"
  141. fi
  142. . /etc/os-release
  143. if [[ "$ID" != "raspbian" && "$ID" != "debian" ]]; then
  144. warn "Expected Raspberry Pi OS (Debian-based), found: $ID"
  145. if ! prompt_yes_no "Continue anyway?" "n"; then
  146. exit 0
  147. fi
  148. fi
  149. success "OS: $PRETTY_NAME"
  150. }
  151. detect_python() {
  152. local cmd=""
  153. if command -v python3 &>/dev/null; then
  154. cmd="python3"
  155. elif command -v python &>/dev/null; then
  156. local ver
  157. ver=$(python --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f1)
  158. if [[ "$ver" -ge 3 ]]; then
  159. cmd="python"
  160. fi
  161. fi
  162. if [[ -z "$cmd" ]]; then
  163. return 1
  164. fi
  165. local version
  166. version=$($cmd -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
  167. local major minor
  168. major=$(echo "$version" | cut -d'.' -f1)
  169. minor=$(echo "$version" | cut -d'.' -f2)
  170. if [[ "$major" -lt 3 ]] || { [[ "$major" -eq 3 ]] && [[ "$minor" -lt 10 ]]; }; then
  171. warn "Python $version found, but 3.10+ is required"
  172. return 1
  173. fi
  174. PYTHON_CMD="$cmd"
  175. success "Found Python $version"
  176. return 0
  177. }
  178. # ─────────────────────────────────────────────────────────────────────────────
  179. # Raspberry Pi Hardware Configuration
  180. # ─────────────────────────────────────────────────────────────────────────────
  181. enable_spi() {
  182. if raspi-config nonint get_spi 2>/dev/null | grep -q "1"; then
  183. info "Enabling SPI..."
  184. raspi-config nonint do_spi 0
  185. REBOOT_NEEDED="true"
  186. success "SPI enabled"
  187. else
  188. success "SPI already enabled"
  189. fi
  190. }
  191. enable_i2c() {
  192. if raspi-config nonint get_i2c 2>/dev/null | grep -q "1"; then
  193. info "Enabling I2C..."
  194. raspi-config nonint do_i2c 0
  195. REBOOT_NEEDED="true"
  196. success "I2C enabled"
  197. else
  198. success "I2C already enabled"
  199. fi
  200. }
  201. configure_boot_config() {
  202. # Find the boot config file (Bookworm+ uses /boot/firmware/config.txt)
  203. local boot_config="/boot/firmware/config.txt"
  204. if [[ ! -f "$boot_config" ]]; then
  205. boot_config="/boot/config.txt"
  206. fi
  207. if [[ ! -f "$boot_config" ]]; then
  208. warn "Boot config not found at /boot/firmware/config.txt or /boot/config.txt"
  209. warn "You may need to manually add: dtparam=i2c_vc=on and dtoverlay=spi0-0cs"
  210. return
  211. fi
  212. info "Configuring $boot_config..."
  213. # Enable I2C bus 0 (GPIO0/GPIO1) for NAU7802 scale
  214. if ! grep -q "^dtparam=i2c_vc=on" "$boot_config"; then
  215. echo "" >> "$boot_config"
  216. echo "# SpoolBuddy: I2C bus 0 for NAU7802 scale (GPIO0/GPIO1)" >> "$boot_config"
  217. echo "dtparam=i2c_vc=on" >> "$boot_config"
  218. REBOOT_NEEDED="true"
  219. success "Added dtparam=i2c_vc=on"
  220. else
  221. success "dtparam=i2c_vc=on already set"
  222. fi
  223. # Disable SPI auto chip-select (manual CS on GPIO23 for PN5180)
  224. if ! grep -q "^dtoverlay=spi0-0cs" "$boot_config"; then
  225. echo "" >> "$boot_config"
  226. echo "# SpoolBuddy: Disable SPI auto CS (manual CS on GPIO23 for PN5180)" >> "$boot_config"
  227. echo "dtoverlay=spi0-0cs" >> "$boot_config"
  228. REBOOT_NEEDED="true"
  229. success "Added dtoverlay=spi0-0cs"
  230. else
  231. success "dtoverlay=spi0-0cs already set"
  232. fi
  233. }
  234. # ─────────────────────────────────────────────────────────────────────────────
  235. # Package Installation
  236. # ─────────────────────────────────────────────────────────────────────────────
  237. install_system_packages() {
  238. info "Updating package lists..."
  239. apt-get update -qq
  240. info "Installing system packages..."
  241. apt-get install -y -qq $SYSTEM_PACKAGES
  242. success "System packages installed"
  243. }
  244. # ─────────────────────────────────────────────────────────────────────────────
  245. # SpoolBuddy Installation
  246. # ─────────────────────────────────────────────────────────────────────────────
  247. create_spoolbuddy_user() {
  248. if id "$SPOOLBUDDY_SERVICE_USER" &>/dev/null; then
  249. info "User '$SPOOLBUDDY_SERVICE_USER' already exists"
  250. else
  251. info "Creating service user '$SPOOLBUDDY_SERVICE_USER'..."
  252. useradd --system --shell /usr/sbin/nologin --home-dir "$INSTALL_PATH" "$SPOOLBUDDY_SERVICE_USER"
  253. success "Service user created"
  254. fi
  255. # Add to hardware access groups (gpio, spi, i2c)
  256. for group in gpio spi i2c; do
  257. if getent group "$group" &>/dev/null; then
  258. usermod -aG "$group" "$SPOOLBUDDY_SERVICE_USER" 2>/dev/null || true
  259. fi
  260. done
  261. success "User added to gpio, spi, i2c groups"
  262. }
  263. download_spoolbuddy() {
  264. info "Downloading SpoolBuddy..."
  265. if [[ -d "$INSTALL_PATH/.git" ]]; then
  266. info "Existing installation found, updating..."
  267. git config --global --add safe.directory "$INSTALL_PATH" 2>/dev/null || true
  268. cd "$INSTALL_PATH"
  269. git fetch origin
  270. git reset --hard origin/main
  271. else
  272. mkdir -p "$INSTALL_PATH"
  273. git clone "$GITHUB_REPO" "$INSTALL_PATH"
  274. fi
  275. chown -R "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$INSTALL_PATH"
  276. success "Downloaded to $INSTALL_PATH"
  277. }
  278. setup_spoolbuddy_venv() {
  279. info "Setting up SpoolBuddy Python environment..."
  280. cd "$INSTALL_PATH/spoolbuddy"
  281. # Create venv with system site packages (needed for python3-spidev, python3-libgpiod)
  282. $PYTHON_CMD -m venv --system-site-packages venv
  283. # Install SpoolBuddy-specific Python packages
  284. "$INSTALL_PATH/spoolbuddy/venv/bin/pip" install --upgrade pip -q
  285. "$INSTALL_PATH/spoolbuddy/venv/bin/pip" install $SPOOLBUDDY_PIP_PACKAGES -q
  286. chown -R "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$INSTALL_PATH/spoolbuddy/venv"
  287. success "SpoolBuddy Python environment ready"
  288. }
  289. create_spoolbuddy_env() {
  290. info "Creating SpoolBuddy configuration..."
  291. local env_file="$INSTALL_PATH/spoolbuddy/.env"
  292. cat > "$env_file" << EOF
  293. # SpoolBuddy Configuration
  294. # Generated by install.sh on $(date)
  295. # Bambuddy backend URL
  296. SPOOLBUDDY_BACKEND_URL=$BAMBUDDY_URL
  297. # API key (create one in Bambuddy Settings -> API Keys)
  298. SPOOLBUDDY_API_KEY=$API_KEY
  299. EOF
  300. chown "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$env_file"
  301. chmod 600 "$env_file"
  302. success "Configuration saved to $env_file"
  303. }
  304. create_spoolbuddy_service() {
  305. info "Creating SpoolBuddy systemd service..."
  306. local after_line="After=network-online.target"
  307. if [[ "$INSTALL_MODE" == "full" ]]; then
  308. after_line="After=network-online.target bambuddy.service"
  309. fi
  310. cat > /etc/systemd/system/spoolbuddy.service << EOF
  311. [Unit]
  312. Description=SpoolBuddy - NFC Spool Management Daemon
  313. Documentation=https://github.com/maziggy/bambuddy
  314. $after_line
  315. Wants=network-online.target
  316. [Service]
  317. Type=simple
  318. User=$SPOOLBUDDY_SERVICE_USER
  319. WorkingDirectory=$INSTALL_PATH/spoolbuddy
  320. EnvironmentFile=$INSTALL_PATH/spoolbuddy/.env
  321. ExecStart=$INSTALL_PATH/spoolbuddy/venv/bin/python -m daemon.main
  322. Restart=always
  323. RestartSec=5
  324. StandardOutput=journal
  325. StandardError=journal
  326. [Install]
  327. WantedBy=multi-user.target
  328. EOF
  329. systemctl daemon-reload
  330. systemctl enable spoolbuddy.service
  331. success "SpoolBuddy service created and enabled"
  332. }
  333. # ─────────────────────────────────────────────────────────────────────────────
  334. # Bambuddy Installation (full mode only)
  335. # ─────────────────────────────────────────────────────────────────────────────
  336. create_bambuddy_user() {
  337. if id "$BAMBUDDY_SERVICE_USER" &>/dev/null; then
  338. info "User '$BAMBUDDY_SERVICE_USER' already exists"
  339. return
  340. fi
  341. info "Creating service user '$BAMBUDDY_SERVICE_USER'..."
  342. useradd --system --shell /usr/sbin/nologin --home-dir "$INSTALL_PATH" "$BAMBUDDY_SERVICE_USER"
  343. success "Service user created"
  344. }
  345. setup_bambuddy_venv() {
  346. info "Setting up Bambuddy Python environment..."
  347. cd "$INSTALL_PATH"
  348. $PYTHON_CMD -m venv venv
  349. "$INSTALL_PATH/venv/bin/pip" install --upgrade pip -q
  350. "$INSTALL_PATH/venv/bin/pip" install -r requirements.txt -q
  351. chown -R "$BAMBUDDY_SERVICE_USER:$BAMBUDDY_SERVICE_USER" "$INSTALL_PATH/venv"
  352. success "Bambuddy Python environment ready"
  353. }
  354. install_nodejs() {
  355. if command -v node &>/dev/null; then
  356. local version
  357. version=$(node --version 2>/dev/null | sed 's/^v//')
  358. local major
  359. major=$(echo "$version" | cut -d'.' -f1)
  360. if [[ "$major" -ge 20 ]]; then
  361. success "Found Node.js v$version"
  362. return
  363. fi
  364. fi
  365. info "Installing Node.js 22..."
  366. apt-get remove -y nodejs npm 2>/dev/null || true
  367. curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
  368. apt-get install -y nodejs
  369. hash -r 2>/dev/null || true
  370. success "Node.js installed: $(node --version)"
  371. }
  372. build_frontend() {
  373. info "Building Bambuddy frontend (this may take a few minutes on RPi)..."
  374. cd "$INSTALL_PATH/frontend"
  375. npm ci --silent
  376. npm run build
  377. success "Frontend built"
  378. }
  379. create_bambuddy_env() {
  380. info "Creating Bambuddy configuration..."
  381. local env_file="$INSTALL_PATH/.env"
  382. cat > "$env_file" << EOF
  383. # Bambuddy Configuration
  384. # Generated by install.sh on $(date)
  385. DEBUG=false
  386. LOG_LEVEL=INFO
  387. LOG_TO_FILE=true
  388. EOF
  389. chown "$BAMBUDDY_SERVICE_USER:$BAMBUDDY_SERVICE_USER" "$env_file"
  390. chmod 600 "$env_file"
  391. success "Configuration saved to $env_file"
  392. }
  393. create_bambuddy_directories() {
  394. mkdir -p "$INSTALL_PATH/data" "$INSTALL_PATH/logs"
  395. chown -R "$BAMBUDDY_SERVICE_USER:$BAMBUDDY_SERVICE_USER" "$INSTALL_PATH/data" "$INSTALL_PATH/logs"
  396. success "Data directories created"
  397. }
  398. create_bambuddy_service() {
  399. info "Creating Bambuddy systemd service..."
  400. cat > /etc/systemd/system/bambuddy.service << EOF
  401. [Unit]
  402. Description=Bambuddy - Bambu Lab Print Management
  403. Documentation=https://github.com/maziggy/bambuddy
  404. After=network.target
  405. [Service]
  406. Type=simple
  407. User=$BAMBUDDY_SERVICE_USER
  408. Group=$BAMBUDDY_SERVICE_USER
  409. WorkingDirectory=$INSTALL_PATH
  410. EnvironmentFile=$INSTALL_PATH/.env
  411. Environment="DATA_DIR=$INSTALL_PATH/data"
  412. Environment="LOG_DIR=$INSTALL_PATH/logs"
  413. ExecStart=$INSTALL_PATH/venv/bin/uvicorn backend.app.main:app --host 0.0.0.0 --port $BAMBUDDY_PORT
  414. Restart=on-failure
  415. RestartSec=5
  416. StandardOutput=journal
  417. StandardError=journal
  418. NoNewPrivileges=true
  419. PrivateTmp=true
  420. ProtectSystem=strict
  421. ProtectHome=true
  422. ReadWritePaths=$INSTALL_PATH/data $INSTALL_PATH/logs $INSTALL_PATH
  423. [Install]
  424. WantedBy=multi-user.target
  425. EOF
  426. systemctl daemon-reload
  427. systemctl enable bambuddy.service
  428. success "Bambuddy service created and enabled"
  429. }
  430. # ─────────────────────────────────────────────────────────────────────────────
  431. # User Prompts
  432. # ─────────────────────────────────────────────────────────────────────────────
  433. parse_args() {
  434. while [[ $# -gt 0 ]]; do
  435. case "$1" in
  436. --mode)
  437. INSTALL_MODE="$2"
  438. shift 2
  439. ;;
  440. --bambuddy-url)
  441. BAMBUDDY_URL="$2"
  442. shift 2
  443. ;;
  444. --api-key)
  445. API_KEY="$2"
  446. shift 2
  447. ;;
  448. --path)
  449. INSTALL_PATH="$2"
  450. shift 2
  451. ;;
  452. --port)
  453. BAMBUDDY_PORT="$2"
  454. shift 2
  455. ;;
  456. --yes|-y)
  457. NON_INTERACTIVE="true"
  458. shift
  459. ;;
  460. --help|-h)
  461. show_help
  462. ;;
  463. *)
  464. error "Unknown option: $1 (use --help for usage)"
  465. ;;
  466. esac
  467. done
  468. }
  469. ask_install_mode() {
  470. if [[ -n "$INSTALL_MODE" ]]; then
  471. return
  472. fi
  473. echo ""
  474. echo -e "${BOLD}How would you like to set up SpoolBuddy?${NC}"
  475. echo ""
  476. echo -e " ${CYAN}1)${NC} SpoolBuddy only"
  477. echo " NFC reader + scale on this RPi, Bambuddy runs on another device"
  478. echo ""
  479. echo -e " ${CYAN}2)${NC} SpoolBuddy + Bambuddy"
  480. echo " Both running natively on this Raspberry Pi"
  481. echo ""
  482. while true; do
  483. echo -en "${BOLD}Choose${NC} [${CYAN}1${NC}/${CYAN}2${NC}]: "
  484. read -r choice
  485. case "$choice" in
  486. 1) INSTALL_MODE="spoolbuddy"; return;;
  487. 2) INSTALL_MODE="full"; return;;
  488. *) echo "Please enter 1 or 2.";;
  489. esac
  490. done
  491. }
  492. gather_config() {
  493. echo ""
  494. echo -e "${BOLD}Configuration${NC}"
  495. echo -e "${CYAN}─────────────────────────────────────────${NC}"
  496. echo ""
  497. # Set default install path based on mode
  498. if [[ -z "$INSTALL_PATH" ]]; then
  499. if [[ "$INSTALL_MODE" == "full" ]]; then
  500. INSTALL_PATH="/opt/bambuddy"
  501. else
  502. INSTALL_PATH="/opt/bambuddy"
  503. fi
  504. fi
  505. prompt "Installation directory" "$INSTALL_PATH" INSTALL_PATH
  506. if [[ "$INSTALL_MODE" == "spoolbuddy" ]]; then
  507. # Need remote Bambuddy URL and API key
  508. echo ""
  509. info "SpoolBuddy needs to connect to your Bambuddy server."
  510. info "You can find/create an API key in Bambuddy under Settings -> API Keys."
  511. echo ""
  512. while [[ -z "$BAMBUDDY_URL" ]]; do
  513. prompt "Bambuddy server URL (e.g. http://192.168.1.100:8000)" "" BAMBUDDY_URL
  514. if [[ -z "$BAMBUDDY_URL" ]]; then
  515. warn "Bambuddy URL is required"
  516. fi
  517. done
  518. while [[ -z "$API_KEY" ]]; do
  519. prompt "Bambuddy API key" "" API_KEY
  520. if [[ -z "$API_KEY" ]]; then
  521. warn "API key is required"
  522. fi
  523. done
  524. else
  525. # Full mode — Bambuddy runs locally
  526. prompt "Bambuddy port" "$BAMBUDDY_PORT" BAMBUDDY_PORT
  527. BAMBUDDY_URL="http://localhost:$BAMBUDDY_PORT"
  528. echo ""
  529. info "After installation, create an API key in Bambuddy (Settings -> API Keys)"
  530. info "and update it in: $INSTALL_PATH/spoolbuddy/.env"
  531. API_KEY="CHANGE_ME_AFTER_SETUP"
  532. fi
  533. # Summary
  534. echo ""
  535. echo -e "${BOLD}Installation Summary${NC}"
  536. echo -e "${CYAN}─────────────────────────────────────────${NC}"
  537. echo -e " Mode: ${GREEN}$([ "$INSTALL_MODE" == "full" ] && echo "Bambuddy + SpoolBuddy" || echo "SpoolBuddy only")${NC}"
  538. echo -e " Install path: ${GREEN}$INSTALL_PATH${NC}"
  539. if [[ "$INSTALL_MODE" == "full" ]]; then
  540. echo -e " Bambuddy port: ${GREEN}$BAMBUDDY_PORT${NC}"
  541. echo -e " Bambuddy URL: ${GREEN}$BAMBUDDY_URL${NC}"
  542. else
  543. echo -e " Bambuddy URL: ${GREEN}$BAMBUDDY_URL${NC}"
  544. fi
  545. echo ""
  546. if ! prompt_yes_no "Proceed with installation?" "y"; then
  547. echo "Installation cancelled."
  548. exit 0
  549. fi
  550. }
  551. # ─────────────────────────────────────────────────────────────────────────────
  552. # Main
  553. # ─────────────────────────────────────────────────────────────────────────────
  554. main() {
  555. parse_args "$@"
  556. echo ""
  557. echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
  558. echo -e "${CYAN}║ ║${NC}"
  559. echo -e "${CYAN}║ ____ _ ____ _ _ ║${NC}"
  560. echo -e "${CYAN}║ / ___| _ __ ___ ___ | | __ ) _ _ __| | __| |_ _ ║${NC}"
  561. echo -e "${CYAN}║ \\___ \\| '_ \\ / _ \\ / _ \\| | _ \\| | | |/ _\` |/ _\` | | | |║${NC}"
  562. echo -e "${CYAN}║ ___) | |_) | (_) | (_) | | |_) | |_| | (_| | (_| | |_| |║${NC}"
  563. echo -e "${CYAN}║ |____/| .__/ \\___/ \\___/|_|____/ \\__,_|\\__,_|\\__,_|\\__, |║${NC}"
  564. echo -e "${CYAN}║ |_| |___/ ║${NC}"
  565. echo -e "${CYAN}║ ║${NC}"
  566. echo -e "${CYAN}║ NFC Spool Management for Bambuddy ║${NC}"
  567. echo -e "${CYAN}║ ║${NC}"
  568. echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
  569. echo ""
  570. # Check if running via pipe without -y
  571. if [[ ! -t 0 ]] && [[ "$NON_INTERACTIVE" != "true" ]]; then
  572. error "Interactive mode requires a terminal. Use -y for unattended install, or download and run directly."
  573. fi
  574. # Pre-flight checks
  575. check_root
  576. check_raspberry_pi
  577. check_raspberry_pi_os
  578. if ! detect_python; then
  579. info "Python 3.10+ not found, will install..."
  580. fi
  581. # Gather user preferences
  582. ask_install_mode
  583. gather_config
  584. # Validate mode
  585. if [[ "$INSTALL_MODE" != "spoolbuddy" && "$INSTALL_MODE" != "full" ]]; then
  586. error "Invalid mode: $INSTALL_MODE (must be 'spoolbuddy' or 'full')"
  587. fi
  588. echo ""
  589. echo -e "${BOLD}Starting Installation${NC}"
  590. echo -e "${CYAN}─────────────────────────────────────────${NC}"
  591. echo ""
  592. # ── Step 1: Raspberry Pi hardware config ──────────────────────────────
  593. info "Configuring Raspberry Pi hardware..."
  594. enable_spi
  595. enable_i2c
  596. configure_boot_config
  597. echo ""
  598. # ── Step 2: System packages ───────────────────────────────────────────
  599. install_system_packages
  600. detect_python || error "Failed to install Python 3.10+"
  601. echo ""
  602. # ── Step 3: Download source code ──────────────────────────────────────
  603. create_spoolbuddy_user
  604. download_spoolbuddy
  605. echo ""
  606. # ── Step 4: SpoolBuddy setup ──────────────────────────────────────────
  607. info "Setting up SpoolBuddy..."
  608. setup_spoolbuddy_venv
  609. create_spoolbuddy_env
  610. create_spoolbuddy_service
  611. echo ""
  612. # ── Step 5: Bambuddy setup (full mode only) ───────────────────────────
  613. if [[ "$INSTALL_MODE" == "full" ]]; then
  614. info "Setting up Bambuddy..."
  615. create_bambuddy_user
  616. setup_bambuddy_venv
  617. install_nodejs
  618. build_frontend
  619. create_bambuddy_directories
  620. create_bambuddy_env
  621. create_bambuddy_service
  622. echo ""
  623. fi
  624. # ── Done ──────────────────────────────────────────────────────────────
  625. echo ""
  626. echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
  627. echo -e "${GREEN}║ ║${NC}"
  628. echo -e "${GREEN}║ Installation Complete! ║${NC}"
  629. echo -e "${GREEN}║ ║${NC}"
  630. echo -e "${GREEN}╚══════════════════════════════════════════════════════════╝${NC}"
  631. echo ""
  632. if [[ "$INSTALL_MODE" == "full" ]]; then
  633. local ip_addr
  634. ip_addr=$(hostname -I 2>/dev/null | awk '{print $1}') || ip_addr="<your-ip>"
  635. echo -e " ${BOLD}Bambuddy:${NC} ${CYAN}http://$ip_addr:$BAMBUDDY_PORT${NC}"
  636. echo ""
  637. echo -e " ${BOLD}Next steps:${NC}"
  638. echo -e " 1. Reboot to apply hardware changes"
  639. echo -e " 2. Open Bambuddy in your browser"
  640. echo -e " 3. Go to Settings -> API Keys and create an API key"
  641. echo -e " 4. Update the API key in: ${CYAN}$INSTALL_PATH/spoolbuddy/.env${NC}"
  642. echo -e " 5. Restart SpoolBuddy: ${CYAN}sudo systemctl restart spoolbuddy${NC}"
  643. else
  644. echo -e " ${BOLD}SpoolBuddy:${NC} Connecting to ${CYAN}$BAMBUDDY_URL${NC}"
  645. fi
  646. echo ""
  647. echo -e " ${BOLD}Manage services:${NC}"
  648. echo -e " SpoolBuddy status: ${CYAN}sudo systemctl status spoolbuddy${NC}"
  649. echo -e " SpoolBuddy logs: ${CYAN}sudo journalctl -u spoolbuddy -f${NC}"
  650. if [[ "$INSTALL_MODE" == "full" ]]; then
  651. echo -e " Bambuddy status: ${CYAN}sudo systemctl status bambuddy${NC}"
  652. echo -e " Bambuddy logs: ${CYAN}sudo journalctl -u bambuddy -f${NC}"
  653. fi
  654. echo ""
  655. echo -e " ${BOLD}Configuration:${NC} ${CYAN}$INSTALL_PATH/spoolbuddy/.env${NC}"
  656. echo -e " ${BOLD}Hardware wiring:${NC} ${CYAN}$INSTALL_PATH/spoolbuddy/README.md${NC}"
  657. echo -e " ${BOLD}Diagnostics:${NC} ${CYAN}sudo $INSTALL_PATH/spoolbuddy/venv/bin/python $INSTALL_PATH/spoolbuddy/pn5180_diag.py${NC}"
  658. echo ""
  659. if [[ "$REBOOT_NEEDED" == "true" ]]; then
  660. echo -e " ${YELLOW}A reboot is required to apply SPI/I2C changes.${NC}"
  661. echo ""
  662. if prompt_yes_no "Reboot now?" "y"; then
  663. reboot
  664. else
  665. echo -e " Run ${CYAN}sudo reboot${NC} when ready."
  666. fi
  667. else
  668. # SPI/I2C already configured — start services now
  669. if prompt_yes_no "Start services now?" "y"; then
  670. if [[ "$INSTALL_MODE" == "full" ]]; then
  671. systemctl start bambuddy
  672. sleep 2
  673. if systemctl is-active --quiet bambuddy; then
  674. success "Bambuddy is running"
  675. else
  676. warn "Bambuddy may have failed to start. Check: sudo journalctl -u bambuddy -f"
  677. fi
  678. fi
  679. systemctl start spoolbuddy
  680. sleep 2
  681. if systemctl is-active --quiet spoolbuddy; then
  682. success "SpoolBuddy is running"
  683. else
  684. warn "SpoolBuddy may have failed to start. Check: sudo journalctl -u spoolbuddy -f"
  685. fi
  686. fi
  687. fi
  688. echo ""
  689. }
  690. main "$@"