install.sh 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180
  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. # --ssh-pubkey KEY Bambuddy SSH public key for remote updates
  20. # --yes, -y Non-interactive mode, accept defaults
  21. # --help, -h Show this help message
  22. #
  23. set -e
  24. # ─────────────────────────────────────────────────────────────────────────────
  25. # Constants
  26. # ─────────────────────────────────────────────────────────────────────────────
  27. RED='\033[0;31m'
  28. GREEN='\033[0;32m'
  29. YELLOW='\033[1;33m'
  30. CYAN='\033[0;36m'
  31. BOLD='\033[1m'
  32. NC='\033[0m'
  33. GITHUB_REPO="https://github.com/maziggy/bambuddy.git"
  34. SPOOLBUDDY_SERVICE_USER="spoolbuddy"
  35. BAMBUDDY_SERVICE_USER="bambuddy"
  36. # Packages needed for SpoolBuddy hardware (NFC reader + scale)
  37. SYSTEM_PACKAGES="python3 python3-pip python3-venv python3-dev python3-spidev python3-libgpiod gpiod libgpiod-dev i2c-tools git"
  38. # Python packages for SpoolBuddy daemon
  39. SPOOLBUDDY_PIP_PACKAGES="spidev gpiod smbus2 httpx"
  40. # ─────────────────────────────────────────────────────────────────────────────
  41. # Variables (set by args or prompts)
  42. # ─────────────────────────────────────────────────────────────────────────────
  43. INSTALL_MODE="" # "spoolbuddy" or "full"
  44. INSTALL_PATH=""
  45. BAMBUDDY_URL=""
  46. API_KEY=""
  47. BAMBUDDY_PORT="8000"
  48. NON_INTERACTIVE="false"
  49. REBOOT_NEEDED="false"
  50. KIOSK_USER="" # auto-detected from $SUDO_USER
  51. KIOSK_URL="" # derived from $BAMBUDDY_URL/spoolbuddy?token=$API_KEY
  52. SSH_PUBKEY="" # Bambuddy's SSH public key for remote updates
  53. # ─────────────────────────────────────────────────────────────────────────────
  54. # Helpers
  55. # ─────────────────────────────────────────────────────────────────────────────
  56. info() { echo -e "${CYAN}[INFO]${NC} $1"; }
  57. success() { echo -e "${GREEN}[OK]${NC} $1"; }
  58. warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
  59. error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
  60. # Run a long-running command with a spinner + live progress output.
  61. # Usage: run_with_progress "description" command [args...]
  62. run_with_progress() {
  63. local desc="$1"
  64. shift
  65. local log_file
  66. log_file=$(mktemp /tmp/spoolbuddy-install.XXXXXX)
  67. local start_time=$SECONDS
  68. # Run command in background, capture stdout+stderr
  69. "$@" > "$log_file" 2>&1 &
  70. local pid=$!
  71. # Spinner frames (braille pattern)
  72. local -a spin=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
  73. local i=0
  74. while kill -0 "$pid" 2>/dev/null; do
  75. local elapsed=$(( SECONDS - start_time ))
  76. local time_str
  77. if (( elapsed >= 60 )); then
  78. time_str="$(( elapsed / 60 ))m$(printf '%02d' $(( elapsed % 60 )))s"
  79. else
  80. time_str="${elapsed}s"
  81. fi
  82. # Last chunk of output (handles \r progress lines and regular \n lines)
  83. local last_line=""
  84. last_line=$(tail -c 4096 "$log_file" 2>/dev/null | tr '\r' '\n' | sed 's/\x1b\[[0-9;]*[mGKHJ]//g' | sed '/^[[:space:]]*$/d' | tail -1 | sed 's/^[[:space:]]*//' | cut -c1-50) || true
  85. printf "\r ${spin[$((i % 10))]} %-36s ${CYAN}%6s${NC} %s\033[K" "$desc" "$time_str" "$last_line"
  86. i=$(( i + 1 ))
  87. sleep 0.15
  88. done
  89. local exit_code=0
  90. wait "$pid" || exit_code=$?
  91. # Clear spinner line
  92. printf "\r\033[K"
  93. # Format elapsed time for summary
  94. local elapsed=$(( SECONDS - start_time ))
  95. local time_suffix=""
  96. if (( elapsed >= 60 )); then
  97. time_suffix=" ($(( elapsed / 60 ))m $(( elapsed % 60 ))s)"
  98. elif (( elapsed >= 5 )); then
  99. time_suffix=" (${elapsed}s)"
  100. fi
  101. if [[ $exit_code -eq 0 ]]; then
  102. success "${desc}${time_suffix}"
  103. rm -f "$log_file"
  104. else
  105. echo -e "${RED}[FAIL]${NC} ${desc}${time_suffix}"
  106. echo ""
  107. echo -e " ${YELLOW}Last 20 lines:${NC}"
  108. tail -20 "$log_file" 2>/dev/null | sed 's/^/ /'
  109. echo ""
  110. echo -e " Full log: ${CYAN}$log_file${NC}"
  111. exit 1
  112. fi
  113. }
  114. prompt() {
  115. local prompt_text="$1"
  116. local default_value="$2"
  117. local var_name="$3"
  118. if [[ "$NON_INTERACTIVE" == "true" ]]; then
  119. eval "$var_name=\"$default_value\""
  120. return
  121. fi
  122. if [[ -n "$default_value" ]]; then
  123. echo -en "${BOLD}$prompt_text${NC} [${CYAN}$default_value${NC}]: "
  124. else
  125. echo -en "${BOLD}$prompt_text${NC}: "
  126. fi
  127. read -r input
  128. if [[ -z "$input" ]]; then
  129. eval "$var_name=\"$default_value\""
  130. else
  131. eval "$var_name=\"$input\""
  132. fi
  133. }
  134. prompt_yes_no() {
  135. local prompt_text="$1"
  136. local default="$2"
  137. if [[ "$NON_INTERACTIVE" == "true" ]]; then
  138. [[ "$default" == "y" ]] && return 0 || return 1
  139. fi
  140. local yn_hint="[y/n]"
  141. [[ "$default" == "y" ]] && yn_hint="[Y/n]"
  142. [[ "$default" == "n" ]] && yn_hint="[y/N]"
  143. while true; do
  144. echo -en "${BOLD}$prompt_text${NC} $yn_hint: "
  145. read -r yn
  146. [[ -z "$yn" ]] && yn="$default"
  147. case "$yn" in
  148. [Yy]* ) return 0;;
  149. [Nn]* ) return 1;;
  150. * ) echo "Please answer yes or no.";;
  151. esac
  152. done
  153. }
  154. show_help() {
  155. echo "SpoolBuddy Installation Script for Raspberry Pi"
  156. echo ""
  157. echo "Usage: sudo $0 [OPTIONS]"
  158. echo ""
  159. echo "Options:"
  160. echo " --mode MODE \"spoolbuddy\" (companion only) or \"full\" (Bambuddy + SpoolBuddy)"
  161. echo " --bambuddy-url URL Bambuddy server URL (required for spoolbuddy mode)"
  162. echo " --api-key KEY Bambuddy API key (required for spoolbuddy mode)"
  163. echo " --path PATH Installation directory (default: /opt/spoolbuddy or /opt/bambuddy)"
  164. echo " --port PORT Bambuddy port (full mode only, default: 8000)"
  165. echo " --ssh-pubkey KEY Bambuddy SSH public key for remote updates"
  166. echo " --yes, -y Non-interactive mode, accept defaults"
  167. echo " --help, -h Show this help message"
  168. echo ""
  169. echo "Examples:"
  170. echo " Interactive:"
  171. echo " sudo ./install.sh"
  172. echo ""
  173. echo " SpoolBuddy companion (unattended):"
  174. echo " sudo ./install.sh --mode spoolbuddy --bambuddy-url http://192.168.1.100:8000 --api-key bb_xxx -y"
  175. echo ""
  176. echo " Full install (unattended):"
  177. echo " sudo ./install.sh --mode full --port 8000 -y"
  178. exit 0
  179. }
  180. # ─────────────────────────────────────────────────────────────────────────────
  181. # Pre-flight Checks
  182. # ─────────────────────────────────────────────────────────────────────────────
  183. check_root() {
  184. if [[ $EUID -ne 0 ]]; then
  185. error "This script must be run as root (use sudo)"
  186. fi
  187. }
  188. check_raspberry_pi() {
  189. if ! grep -q "Raspberry Pi\|BCM2" /proc/cpuinfo 2>/dev/null; then
  190. error "This script is designed for Raspberry Pi only"
  191. fi
  192. # Detect Pi model for hardware recommendations
  193. local model
  194. model=$(tr -d '\0' < /proc/device-tree/model 2>/dev/null) || model="Unknown"
  195. success "Detected: $model"
  196. }
  197. check_raspberry_pi_os() {
  198. if [[ ! -f /etc/os-release ]]; then
  199. error "Cannot detect operating system"
  200. fi
  201. . /etc/os-release
  202. if [[ "$ID" != "raspbian" && "$ID" != "debian" ]]; then
  203. warn "Expected Raspberry Pi OS (Debian-based), found: $ID"
  204. if ! prompt_yes_no "Continue anyway?" "n"; then
  205. exit 0
  206. fi
  207. fi
  208. success "OS: $PRETTY_NAME"
  209. }
  210. detect_python() {
  211. local cmd=""
  212. if command -v python3 &>/dev/null; then
  213. cmd="python3"
  214. elif command -v python &>/dev/null; then
  215. local ver
  216. ver=$(python --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f1)
  217. if [[ "$ver" -ge 3 ]]; then
  218. cmd="python"
  219. fi
  220. fi
  221. if [[ -z "$cmd" ]]; then
  222. return 1
  223. fi
  224. local version
  225. version=$($cmd -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
  226. local major minor
  227. major=$(echo "$version" | cut -d'.' -f1)
  228. minor=$(echo "$version" | cut -d'.' -f2)
  229. if [[ "$major" -lt 3 ]] || { [[ "$major" -eq 3 ]] && [[ "$minor" -lt 10 ]]; }; then
  230. warn "Python $version found, but 3.10+ is required"
  231. return 1
  232. fi
  233. PYTHON_CMD="$cmd"
  234. success "Found Python $version"
  235. return 0
  236. }
  237. # ─────────────────────────────────────────────────────────────────────────────
  238. # Raspberry Pi Hardware Configuration
  239. # ─────────────────────────────────────────────────────────────────────────────
  240. enable_spi() {
  241. if raspi-config nonint get_spi 2>/dev/null | grep -q "1"; then
  242. info "Enabling SPI..."
  243. raspi-config nonint do_spi 0
  244. REBOOT_NEEDED="true"
  245. success "SPI enabled"
  246. else
  247. success "SPI already enabled"
  248. fi
  249. }
  250. enable_i2c() {
  251. if raspi-config nonint get_i2c 2>/dev/null | grep -q "1"; then
  252. info "Enabling I2C..."
  253. raspi-config nonint do_i2c 0
  254. REBOOT_NEEDED="true"
  255. success "I2C enabled"
  256. else
  257. success "I2C already enabled"
  258. fi
  259. }
  260. configure_boot_config() {
  261. # Find the boot config file (Bookworm+ uses /boot/firmware/config.txt)
  262. local boot_config="/boot/firmware/config.txt"
  263. if [[ ! -f "$boot_config" ]]; then
  264. boot_config="/boot/config.txt"
  265. fi
  266. if [[ ! -f "$boot_config" ]]; then
  267. warn "Boot config not found at /boot/firmware/config.txt or /boot/config.txt"
  268. warn "You may need to manually add: dtparam=i2c_vc=on and dtoverlay=spi0-0cs"
  269. return
  270. fi
  271. info "Configuring $boot_config..."
  272. # Enable I2C bus 0 (GPIO0/GPIO1) for NAU7802 scale
  273. if ! grep -q "^dtparam=i2c_vc=on" "$boot_config"; then
  274. echo "" >> "$boot_config"
  275. echo "# SpoolBuddy: I2C bus 0 for NAU7802 scale (GPIO0/GPIO1)" >> "$boot_config"
  276. echo "dtparam=i2c_vc=on" >> "$boot_config"
  277. REBOOT_NEEDED="true"
  278. success "Added dtparam=i2c_vc=on"
  279. else
  280. success "dtparam=i2c_vc=on already set"
  281. fi
  282. # Disable SPI auto chip-select (manual CS on GPIO23 for PN5180)
  283. if ! grep -q "^dtoverlay=spi0-0cs" "$boot_config"; then
  284. echo "" >> "$boot_config"
  285. echo "# SpoolBuddy: Disable SPI auto CS (manual CS on GPIO23 for PN5180)" >> "$boot_config"
  286. echo "dtoverlay=spi0-0cs" >> "$boot_config"
  287. REBOOT_NEEDED="true"
  288. success "Added dtoverlay=spi0-0cs"
  289. else
  290. success "dtoverlay=spi0-0cs already set"
  291. fi
  292. }
  293. # ─────────────────────────────────────────────────────────────────────────────
  294. # Package Installation
  295. # ─────────────────────────────────────────────────────────────────────────────
  296. install_system_packages() {
  297. run_with_progress "Updating package lists" apt-get update
  298. run_with_progress "Installing system packages" apt-get install -y $SYSTEM_PACKAGES
  299. }
  300. # ─────────────────────────────────────────────────────────────────────────────
  301. # SpoolBuddy Installation
  302. # ─────────────────────────────────────────────────────────────────────────────
  303. create_spoolbuddy_user() {
  304. if id "$SPOOLBUDDY_SERVICE_USER" &>/dev/null; then
  305. info "User '$SPOOLBUDDY_SERVICE_USER' already exists"
  306. # Ensure existing installs get a real shell for SSH access
  307. usermod --shell /bin/bash "$SPOOLBUDDY_SERVICE_USER" 2>/dev/null || true
  308. else
  309. info "Creating service user '$SPOOLBUDDY_SERVICE_USER'..."
  310. useradd --system --shell /bin/bash --home-dir "$INSTALL_PATH" "$SPOOLBUDDY_SERVICE_USER"
  311. success "Service user created"
  312. fi
  313. # Add to hardware access groups (gpio, spi, i2c, video for backlight)
  314. for group in gpio spi i2c video; do
  315. if getent group "$group" &>/dev/null; then
  316. usermod -aG "$group" "$SPOOLBUDDY_SERVICE_USER" 2>/dev/null || true
  317. fi
  318. done
  319. success "User added to gpio, spi, i2c, video groups"
  320. # Allow passwordless restart of daemon + kiosk (needed for SSH-based updates from Bambuddy)
  321. cat > /etc/sudoers.d/spoolbuddy << 'SUDOERS'
  322. spoolbuddy ALL=(root) NOPASSWD: /usr/bin/systemctl restart spoolbuddy.service
  323. spoolbuddy ALL=(root) NOPASSWD: /usr/bin/systemctl restart getty@tty1.service
  324. SUDOERS
  325. chmod 440 /etc/sudoers.d/spoolbuddy
  326. success "Sudoers entries created for service and kiosk restart"
  327. }
  328. download_spoolbuddy() {
  329. if [[ -d "$INSTALL_PATH/.git" ]]; then
  330. info "Existing installation found, updating..."
  331. git config --global --add safe.directory "$INSTALL_PATH" 2>/dev/null || true
  332. cd "$INSTALL_PATH"
  333. run_with_progress "Fetching updates" git fetch origin
  334. git reset --hard origin/main > /dev/null 2>&1
  335. else
  336. mkdir -p "$INSTALL_PATH"
  337. run_with_progress "Cloning repository" git clone "$GITHUB_REPO" "$INSTALL_PATH"
  338. fi
  339. chown -R "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$INSTALL_PATH"
  340. }
  341. setup_spoolbuddy_venv() {
  342. cd "$INSTALL_PATH/spoolbuddy"
  343. run_with_progress "Creating SpoolBuddy venv" $PYTHON_CMD -m venv --system-site-packages venv
  344. run_with_progress "Upgrading pip" "$INSTALL_PATH/spoolbuddy/venv/bin/pip" install --upgrade pip
  345. run_with_progress "Installing SpoolBuddy packages" "$INSTALL_PATH/spoolbuddy/venv/bin/pip" install $SPOOLBUDDY_PIP_PACKAGES
  346. chown -R "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$INSTALL_PATH/spoolbuddy/venv"
  347. }
  348. create_spoolbuddy_env() {
  349. info "Creating SpoolBuddy configuration..."
  350. local env_file="$INSTALL_PATH/spoolbuddy/.env"
  351. cat > "$env_file" << EOF
  352. # SpoolBuddy Configuration
  353. # Generated by install.sh on $(date)
  354. # Bambuddy backend URL
  355. SPOOLBUDDY_BACKEND_URL=$BAMBUDDY_URL
  356. # API key (create one in Bambuddy Settings -> API Keys)
  357. SPOOLBUDDY_API_KEY=$API_KEY
  358. EOF
  359. chown "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$env_file"
  360. chmod 600 "$env_file"
  361. success "Configuration saved to $env_file"
  362. }
  363. setup_ssh_key() {
  364. info "Setting up SSH access for Bambuddy remote updates..."
  365. local ssh_dir="$INSTALL_PATH/.ssh"
  366. local auth_keys="$ssh_dir/authorized_keys"
  367. mkdir -p "$ssh_dir"
  368. chmod 700 "$ssh_dir"
  369. if [[ -n "$SSH_PUBKEY" ]]; then
  370. # Manual key provided via --ssh-pubkey flag
  371. if [[ -f "$auth_keys" ]] && grep -qF "$SSH_PUBKEY" "$auth_keys" 2>/dev/null; then
  372. info "SSH key already present in authorized_keys"
  373. else
  374. echo "$SSH_PUBKEY" >> "$auth_keys"
  375. success "SSH public key added"
  376. fi
  377. else
  378. # No manual key — the daemon will auto-deploy it on first registration
  379. info "SSH key will be deployed automatically when the daemon connects to Bambuddy"
  380. touch "$auth_keys"
  381. fi
  382. chmod 600 "$auth_keys"
  383. chown -R "$SPOOLBUDDY_SERVICE_USER:$SPOOLBUDDY_SERVICE_USER" "$ssh_dir"
  384. }
  385. create_spoolbuddy_service() {
  386. info "Creating SpoolBuddy systemd service..."
  387. local after_line="After=network-online.target"
  388. if [[ "$INSTALL_MODE" == "full" ]]; then
  389. after_line="After=network-online.target bambuddy.service"
  390. fi
  391. cat > /etc/systemd/system/spoolbuddy.service << EOF
  392. [Unit]
  393. Description=SpoolBuddy - NFC Spool Management Daemon
  394. Documentation=https://github.com/maziggy/bambuddy
  395. $after_line
  396. Wants=network-online.target
  397. [Service]
  398. Type=simple
  399. User=$SPOOLBUDDY_SERVICE_USER
  400. WorkingDirectory=$INSTALL_PATH/spoolbuddy
  401. EnvironmentFile=$INSTALL_PATH/spoolbuddy/.env
  402. ExecStart=$INSTALL_PATH/spoolbuddy/venv/bin/python -m daemon.main
  403. Restart=always
  404. RestartSec=5
  405. StandardOutput=journal
  406. StandardError=journal
  407. [Install]
  408. WantedBy=multi-user.target
  409. EOF
  410. systemctl daemon-reload
  411. systemctl enable spoolbuddy.service
  412. success "SpoolBuddy service created and enabled"
  413. }
  414. # ─────────────────────────────────────────────────────────────────────────────
  415. # Bambuddy Installation (full mode only)
  416. # ─────────────────────────────────────────────────────────────────────────────
  417. create_bambuddy_user() {
  418. if id "$BAMBUDDY_SERVICE_USER" &>/dev/null; then
  419. info "User '$BAMBUDDY_SERVICE_USER' already exists"
  420. return
  421. fi
  422. info "Creating service user '$BAMBUDDY_SERVICE_USER'..."
  423. useradd --system --shell /usr/sbin/nologin --home-dir "$INSTALL_PATH" "$BAMBUDDY_SERVICE_USER"
  424. success "Service user created"
  425. }
  426. setup_bambuddy_venv() {
  427. cd "$INSTALL_PATH"
  428. run_with_progress "Creating Bambuddy venv" $PYTHON_CMD -m venv venv
  429. run_with_progress "Upgrading pip" "$INSTALL_PATH/venv/bin/pip" install --upgrade pip
  430. run_with_progress "Installing Bambuddy dependencies" "$INSTALL_PATH/venv/bin/pip" install -r requirements.txt
  431. chown -R "$BAMBUDDY_SERVICE_USER:$BAMBUDDY_SERVICE_USER" "$INSTALL_PATH/venv"
  432. }
  433. install_nodejs() {
  434. if command -v node &>/dev/null; then
  435. local version
  436. version=$(node --version 2>/dev/null | sed 's/^v//')
  437. local major
  438. major=$(echo "$version" | cut -d'.' -f1)
  439. if [[ "$major" -ge 20 ]]; then
  440. success "Found Node.js v$version"
  441. return
  442. fi
  443. fi
  444. apt-get remove -y nodejs npm > /dev/null 2>&1 || true
  445. run_with_progress "Setting up Node.js repository" bash -c "curl -fsSL https://deb.nodesource.com/setup_22.x | bash -"
  446. run_with_progress "Installing Node.js" apt-get install -y nodejs
  447. hash -r 2>/dev/null || true
  448. success "Node.js installed: $(node --version)"
  449. }
  450. build_frontend() {
  451. cd "$INSTALL_PATH/frontend"
  452. run_with_progress "Installing frontend dependencies" npm ci
  453. run_with_progress "Building frontend" npm run build
  454. }
  455. create_bambuddy_env() {
  456. info "Creating Bambuddy configuration..."
  457. local env_file="$INSTALL_PATH/.env"
  458. cat > "$env_file" << EOF
  459. # Bambuddy Configuration
  460. # Generated by install.sh on $(date)
  461. DEBUG=false
  462. LOG_LEVEL=INFO
  463. LOG_TO_FILE=true
  464. EOF
  465. chown "$BAMBUDDY_SERVICE_USER:$BAMBUDDY_SERVICE_USER" "$env_file"
  466. chmod 600 "$env_file"
  467. success "Configuration saved to $env_file"
  468. }
  469. create_bambuddy_directories() {
  470. mkdir -p "$INSTALL_PATH/data" "$INSTALL_PATH/logs"
  471. chown -R "$BAMBUDDY_SERVICE_USER:$BAMBUDDY_SERVICE_USER" "$INSTALL_PATH/data" "$INSTALL_PATH/logs"
  472. success "Data directories created"
  473. }
  474. create_bambuddy_service() {
  475. info "Creating Bambuddy systemd service..."
  476. cat > /etc/systemd/system/bambuddy.service << EOF
  477. [Unit]
  478. Description=Bambuddy - Bambu Lab Print Management
  479. Documentation=https://github.com/maziggy/bambuddy
  480. After=network.target
  481. [Service]
  482. Type=simple
  483. User=$BAMBUDDY_SERVICE_USER
  484. Group=$BAMBUDDY_SERVICE_USER
  485. WorkingDirectory=$INSTALL_PATH
  486. EnvironmentFile=$INSTALL_PATH/.env
  487. Environment="DATA_DIR=$INSTALL_PATH/data"
  488. Environment="LOG_DIR=$INSTALL_PATH/logs"
  489. ExecStart=$INSTALL_PATH/venv/bin/uvicorn backend.app.main:app --host 0.0.0.0 --port $BAMBUDDY_PORT
  490. Restart=on-failure
  491. RestartSec=5
  492. StandardOutput=journal
  493. StandardError=journal
  494. NoNewPrivileges=true
  495. PrivateTmp=true
  496. ProtectSystem=strict
  497. ProtectHome=true
  498. ReadWritePaths=$INSTALL_PATH/data $INSTALL_PATH/logs $INSTALL_PATH
  499. [Install]
  500. WantedBy=multi-user.target
  501. EOF
  502. systemctl daemon-reload
  503. systemctl enable bambuddy.service
  504. success "Bambuddy service created and enabled"
  505. }
  506. # ─────────────────────────────────────────────────────────────────────────────
  507. # System Strip-Down (dedicated appliance — remove unnecessary services/packages)
  508. # ─────────────────────────────────────────────────────────────────────────────
  509. strip_services() {
  510. info "Disabling unnecessary services..."
  511. local services=(
  512. bluetooth.service
  513. lightdm.service
  514. cloud-init-local.service
  515. cloud-init.service
  516. cloud-init-network.service
  517. cloud-config.service
  518. cloud-final.service
  519. cloud-init-hotplugd.socket
  520. avahi-daemon.service
  521. avahi-daemon.socket
  522. ModemManager.service
  523. udisks2.service
  524. apparmor.service
  525. man-db.timer
  526. e2scrub_all.timer
  527. e2scrub_reap.service
  528. )
  529. local disabled=0
  530. for svc in "${services[@]}"; do
  531. if systemctl is-enabled "$svc" &>/dev/null; then
  532. systemctl disable "$svc" 2>/dev/null || true
  533. (( ++disabled ))
  534. fi
  535. done
  536. if (( disabled > 0 )); then
  537. success "Disabled $disabled unnecessary services"
  538. else
  539. success "No unnecessary services to disable"
  540. fi
  541. }
  542. strip_packages() {
  543. info "Removing unnecessary packages..."
  544. local packages=(
  545. mkvtoolnix
  546. firmware-atheros
  547. firmware-mediatek
  548. cloud-init
  549. rpi-cloud-init-mods
  550. rpi-connect-lite
  551. avahi-daemon
  552. modemmanager
  553. udisks2
  554. )
  555. local to_remove=()
  556. for pkg in "${packages[@]}"; do
  557. if dpkg -l "$pkg" &>/dev/null 2>&1; then
  558. to_remove+=("$pkg")
  559. fi
  560. done
  561. if (( ${#to_remove[@]} > 0 )); then
  562. run_with_progress "Removing ${#to_remove[@]} packages" apt-get remove --purge -y "${to_remove[@]}"
  563. run_with_progress "Cleaning up dependencies" apt-get autoremove --purge -y
  564. else
  565. success "No unnecessary packages to remove"
  566. fi
  567. }
  568. # ─────────────────────────────────────────────────────────────────────────────
  569. # Kiosk Setup (labwc + Chromium + Plymouth splash)
  570. # ─────────────────────────────────────────────────────────────────────────────
  571. setup_kiosk() {
  572. info "Setting up touchscreen kiosk..."
  573. # Detect kiosk user (the human user who ran sudo)
  574. KIOSK_USER="${SUDO_USER:-$(logname 2>/dev/null || echo pi)}"
  575. KIOSK_URL="${BAMBUDDY_URL}/spoolbuddy?token=${API_KEY}"
  576. local KIOSK_HOME
  577. KIOSK_HOME=$(eval echo "~$KIOSK_USER")
  578. info "Kiosk user: $KIOSK_USER (home: $KIOSK_HOME)"
  579. info "Kiosk URL: $KIOSK_URL"
  580. # ── Install kiosk packages ────────────────────────────────────────────
  581. run_with_progress "Installing kiosk packages" apt-get install -y labwc chromium plymouth wlr-randr
  582. # ── config.txt tweaks ─────────────────────────────────────────────────
  583. local boot_config="/boot/firmware/config.txt"
  584. if [[ ! -f "$boot_config" ]]; then
  585. boot_config="/boot/config.txt"
  586. fi
  587. if [[ -f "$boot_config" ]]; then
  588. info "Configuring $boot_config for kiosk..."
  589. # Disable audio (change existing on→off)
  590. sed -i 's/^dtparam=audio=on/dtparam=audio=off/' "$boot_config"
  591. # Disable camera auto-detect (change existing 1→0)
  592. sed -i 's/^camera_auto_detect=1/camera_auto_detect=0/' "$boot_config"
  593. # Append if missing: gpu_mem=32
  594. if ! grep -q "^gpu_mem=" "$boot_config"; then
  595. echo "" >> "$boot_config"
  596. echo "# Kiosk: Minimal GPU firmware memory (KMS uses CMA from system RAM)" >> "$boot_config"
  597. echo "gpu_mem=32" >> "$boot_config"
  598. fi
  599. # Append if missing: dtoverlay=disable-bt
  600. if ! grep -q "^dtoverlay=disable-bt" "$boot_config"; then
  601. echo "" >> "$boot_config"
  602. echo "# Kiosk: Disable Bluetooth hardware" >> "$boot_config"
  603. echo "dtoverlay=disable-bt" >> "$boot_config"
  604. fi
  605. # Append if missing: disable_splash=1
  606. if ! grep -q "^disable_splash=" "$boot_config"; then
  607. echo "" >> "$boot_config"
  608. echo "# Kiosk: Disable Raspberry Pi firmware splash, use custom splash.png" >> "$boot_config"
  609. echo "disable_splash=1" >> "$boot_config"
  610. fi
  611. success "Boot config updated"
  612. fi
  613. # ── cmdline.txt tweaks ────────────────────────────────────────────────
  614. local cmdline="/boot/firmware/cmdline.txt"
  615. if [[ ! -f "$cmdline" ]]; then
  616. cmdline="/boot/cmdline.txt"
  617. fi
  618. if [[ -f "$cmdline" ]]; then
  619. info "Configuring $cmdline for kiosk..."
  620. # Remove serial console (Plymouth needs tty-only console)
  621. sed -i 's/console=serial0,[0-9]* //' "$cmdline"
  622. # Add splash quiet loglevel=3 logo.nologo if missing
  623. grep -q "splash" "$cmdline" || sed -i 's/$/ splash quiet loglevel=3 logo.nologo/' "$cmdline"
  624. # Add video mode if missing
  625. grep -q "video=HDMI-A-1" "$cmdline" || sed -i 's/$/ video=HDMI-A-1:1024x600@60/' "$cmdline"
  626. success "Kernel cmdline updated"
  627. fi
  628. # ── Plymouth splash theme ─────────────────────────────────────────────
  629. info "Installing Plymouth boot splash..."
  630. local theme_dir="/usr/share/plymouth/themes/spoolbuddy"
  631. mkdir -p "$theme_dir"
  632. # Copy bundled splash image from the install directory
  633. local script_dir
  634. script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  635. if [[ -f "$script_dir/splash.png" ]]; then
  636. cp "$script_dir/splash.png" "$theme_dir/splash.png"
  637. elif [[ -f "$INSTALL_PATH/spoolbuddy/install/splash.png" ]]; then
  638. cp "$INSTALL_PATH/spoolbuddy/install/splash.png" "$theme_dir/splash.png"
  639. else
  640. warn "splash.png not found — Plymouth splash will not display an image"
  641. fi
  642. # Write .plymouth theme file
  643. cat > "$theme_dir/spoolbuddy.plymouth" << 'EOF'
  644. [Plymouth Theme]
  645. Name=SpoolBuddy
  646. Description=SpoolBuddy boot splash
  647. ModuleName=script
  648. [script]
  649. ImageDir=/usr/share/plymouth/themes/spoolbuddy
  650. ScriptFile=/usr/share/plymouth/themes/spoolbuddy/spoolbuddy.script
  651. EOF
  652. # Write .script theme file
  653. cat > "$theme_dir/spoolbuddy.script" << 'EOF'
  654. wallpaper_image = Image("splash.png");
  655. screen_width = Window.GetWidth();
  656. screen_height = Window.GetHeight();
  657. resized_wallpaper_image = wallpaper_image.Scale(screen_width, screen_height);
  658. wallpaper_sprite = Sprite(resized_wallpaper_image);
  659. wallpaper_sprite.SetZ(-100);
  660. EOF
  661. plymouth-set-default-theme spoolbuddy
  662. run_with_progress "Updating initramfs" update-initramfs -u
  663. success "Plymouth splash installed"
  664. # ── Auto-login on tty1 ────────────────────────────────────────────────
  665. info "Configuring auto-login for $KIOSK_USER..."
  666. mkdir -p /etc/systemd/system/getty@tty1.service.d
  667. cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf << EOF
  668. [Unit]
  669. After=network-online.target
  670. Wants=network-online.target
  671. [Service]
  672. ExecStart=
  673. ExecStart=-/sbin/agetty --autologin $KIOSK_USER --noclear %I \$TERM
  674. EOF
  675. success "Auto-login configured"
  676. # ── labwc rc.xml (no decorations, no keybinds) ────────────────────────
  677. info "Configuring labwc window manager..."
  678. local labwc_dir="$KIOSK_HOME/.config/labwc"
  679. mkdir -p "$labwc_dir"
  680. cat > "$labwc_dir/rc.xml" << 'EOF'
  681. <?xml version="1.0"?>
  682. <labwc_config>
  683. <theme>
  684. <name></name>
  685. <cornerRadius>0</cornerRadius>
  686. </theme>
  687. <!-- Disable all keybindings - kiosk lockdown -->
  688. <keyboard>
  689. </keyboard>
  690. <!-- Disable right-click menu -->
  691. <mouse>
  692. <default />
  693. </mouse>
  694. <!-- Remove window decorations, maximize Chromium, prevent unfullscreen -->
  695. <windowRules>
  696. <windowRule identifier="*">
  697. <serverDecoration>no</serverDecoration>
  698. </windowRule>
  699. <windowRule identifier="chromium">
  700. <skipTaskbar>yes</skipTaskbar>
  701. <fixedPosition>yes</fixedPosition>
  702. </windowRule>
  703. </windowRules>
  704. </labwc_config>
  705. EOF
  706. # ── labwc autostart ───────────────────────────────────────────────────
  707. cat > "$labwc_dir/autostart" << EOF
  708. # Force 1024x600 (panel doesn't advertise this natively)
  709. wlr-randr --output HDMI-A-1 --custom-mode 1024x600@60 &
  710. # Launch Chromium in kiosk mode (virtual keyboard is embedded in the web app)
  711. chromium --kiosk --no-first-run --disable-infobars \\
  712. --disable-session-crashed-bubble --disable-features=TranslateUI \\
  713. --noerrdialogs --disable-component-update \\
  714. --disk-cache-size=0 \\
  715. --overscroll-history-navigation=0 \\
  716. --ozone-platform=wayland \\
  717. $KIOSK_URL &
  718. EOF
  719. chown -R "$KIOSK_USER:$KIOSK_USER" "$labwc_dir"
  720. # ── .bash_profile (source .bashrc, exec labwc on tty1) ────────────────
  721. cat > "$KIOSK_HOME/.bash_profile" << 'EOF'
  722. # Source .bashrc if it exists
  723. if [ -f ~/.bashrc ]; then
  724. . ~/.bashrc
  725. fi
  726. # Auto-start kiosk on tty1
  727. if [ "$(tty)" = "/dev/tty1" ]; then
  728. exec labwc
  729. fi
  730. EOF
  731. chown "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME/.bash_profile"
  732. REBOOT_NEEDED="true"
  733. success "Kiosk setup complete"
  734. }
  735. # ─────────────────────────────────────────────────────────────────────────────
  736. # User Prompts
  737. # ─────────────────────────────────────────────────────────────────────────────
  738. parse_args() {
  739. while [[ $# -gt 0 ]]; do
  740. case "$1" in
  741. --mode)
  742. INSTALL_MODE="$2"
  743. shift 2
  744. ;;
  745. --bambuddy-url)
  746. BAMBUDDY_URL="$2"
  747. shift 2
  748. ;;
  749. --api-key)
  750. API_KEY="$2"
  751. shift 2
  752. ;;
  753. --path)
  754. INSTALL_PATH="$2"
  755. shift 2
  756. ;;
  757. --port)
  758. BAMBUDDY_PORT="$2"
  759. shift 2
  760. ;;
  761. --ssh-pubkey)
  762. SSH_PUBKEY="$2"
  763. shift 2
  764. ;;
  765. --yes|-y)
  766. NON_INTERACTIVE="true"
  767. shift
  768. ;;
  769. --help|-h)
  770. show_help
  771. ;;
  772. *)
  773. error "Unknown option: $1 (use --help for usage)"
  774. ;;
  775. esac
  776. done
  777. }
  778. ask_install_mode() {
  779. if [[ -n "$INSTALL_MODE" ]]; then
  780. return
  781. fi
  782. echo ""
  783. echo -e "${BOLD}How would you like to set up SpoolBuddy?${NC}"
  784. echo ""
  785. echo -e " ${CYAN}1)${NC} SpoolBuddy only"
  786. echo " NFC reader + scale on this RPi, Bambuddy runs on another device"
  787. echo ""
  788. echo -e " ${CYAN}2)${NC} SpoolBuddy + Bambuddy"
  789. echo " Both running natively on this Raspberry Pi"
  790. echo ""
  791. while true; do
  792. echo -en "${BOLD}Choose${NC} [${CYAN}1${NC}/${CYAN}2${NC}]: "
  793. read -r choice
  794. case "$choice" in
  795. 1) INSTALL_MODE="spoolbuddy"; return;;
  796. 2) INSTALL_MODE="full"; return;;
  797. *) echo "Please enter 1 or 2.";;
  798. esac
  799. done
  800. }
  801. gather_config() {
  802. echo ""
  803. echo -e "${BOLD}Configuration${NC}"
  804. echo -e "${CYAN}─────────────────────────────────────────${NC}"
  805. echo ""
  806. # Set default install path based on mode
  807. if [[ -z "$INSTALL_PATH" ]]; then
  808. if [[ "$INSTALL_MODE" == "full" ]]; then
  809. INSTALL_PATH="/opt/bambuddy"
  810. else
  811. INSTALL_PATH="/opt/bambuddy"
  812. fi
  813. fi
  814. prompt "Installation directory" "$INSTALL_PATH" INSTALL_PATH
  815. if [[ "$INSTALL_MODE" == "spoolbuddy" ]]; then
  816. # Need remote Bambuddy URL and API key
  817. echo ""
  818. info "SpoolBuddy needs to connect to your Bambuddy server."
  819. info "You can find/create an API key in Bambuddy under Settings -> API Keys."
  820. echo ""
  821. while [[ -z "$BAMBUDDY_URL" ]]; do
  822. prompt "Bambuddy server URL (e.g. http://192.168.1.100:8000)" "" BAMBUDDY_URL
  823. if [[ -z "$BAMBUDDY_URL" ]]; then
  824. warn "Bambuddy URL is required"
  825. fi
  826. done
  827. while [[ -z "$API_KEY" ]]; do
  828. prompt "Bambuddy API key" "" API_KEY
  829. if [[ -z "$API_KEY" ]]; then
  830. warn "API key is required"
  831. fi
  832. done
  833. else
  834. # Full mode — Bambuddy runs locally
  835. prompt "Bambuddy port" "$BAMBUDDY_PORT" BAMBUDDY_PORT
  836. BAMBUDDY_URL="http://localhost:$BAMBUDDY_PORT"
  837. echo ""
  838. info "After installation, create an API key in Bambuddy (Settings -> API Keys)"
  839. info "and update it in: $INSTALL_PATH/spoolbuddy/.env"
  840. API_KEY="CHANGE_ME_AFTER_SETUP"
  841. fi
  842. # Summary
  843. echo ""
  844. echo -e "${BOLD}Installation Summary${NC}"
  845. echo -e "${CYAN}─────────────────────────────────────────${NC}"
  846. echo -e " Mode: ${GREEN}$([ "$INSTALL_MODE" == "full" ] && echo "Bambuddy + SpoolBuddy" || echo "SpoolBuddy only")${NC}"
  847. echo -e " Install path: ${GREEN}$INSTALL_PATH${NC}"
  848. if [[ "$INSTALL_MODE" == "full" ]]; then
  849. echo -e " Bambuddy port: ${GREEN}$BAMBUDDY_PORT${NC}"
  850. echo -e " Bambuddy URL: ${GREEN}$BAMBUDDY_URL${NC}"
  851. else
  852. echo -e " Bambuddy URL: ${GREEN}$BAMBUDDY_URL${NC}"
  853. fi
  854. echo ""
  855. if ! prompt_yes_no "Proceed with installation?" "y"; then
  856. echo "Installation cancelled."
  857. exit 0
  858. fi
  859. }
  860. # ─────────────────────────────────────────────────────────────────────────────
  861. # Main
  862. # ─────────────────────────────────────────────────────────────────────────────
  863. main() {
  864. parse_args "$@"
  865. echo ""
  866. echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
  867. echo -e "${CYAN}║ ║${NC}"
  868. echo -e "${CYAN}║ ____ _ ____ _ _ ║${NC}"
  869. echo -e "${CYAN}║ / ___| _ __ ___ ___ | | __ ) _ _ __| | __| |_ _ ║${NC}"
  870. echo -e "${CYAN}║ \\___ \\| '_ \\ / _ \\ / _ \\| | _ \\| | | |/ _\` |/ _\` | | | |║${NC}"
  871. echo -e "${CYAN}║ ___) | |_) | (_) | (_) | | |_) | |_| | (_| | (_| | |_| |║${NC}"
  872. echo -e "${CYAN}║ |____/| .__/ \\___/ \\___/|_|____/ \\__,_|\\__,_|\\__,_|\\__, |║${NC}"
  873. echo -e "${CYAN}║ |_| |___/ ║${NC}"
  874. echo -e "${CYAN}║ ║${NC}"
  875. echo -e "${CYAN}║ NFC Spool Management for Bambuddy ║${NC}"
  876. echo -e "${CYAN}║ ║${NC}"
  877. echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
  878. echo ""
  879. # Check if running via pipe without -y
  880. if [[ ! -t 0 ]] && [[ "$NON_INTERACTIVE" != "true" ]]; then
  881. error "Interactive mode requires a terminal. Use -y for unattended install, or download and run directly."
  882. fi
  883. # Pre-flight checks
  884. check_root
  885. check_raspberry_pi
  886. check_raspberry_pi_os
  887. if ! detect_python; then
  888. info "Python 3.10+ not found, will install..."
  889. fi
  890. # Gather user preferences
  891. ask_install_mode
  892. gather_config
  893. # Validate mode
  894. if [[ "$INSTALL_MODE" != "spoolbuddy" && "$INSTALL_MODE" != "full" ]]; then
  895. error "Invalid mode: $INSTALL_MODE (must be 'spoolbuddy' or 'full')"
  896. fi
  897. echo ""
  898. echo -e "${BOLD}Starting Installation${NC}"
  899. echo -e "${CYAN}─────────────────────────────────────────${NC}"
  900. echo ""
  901. # ── Step 1: Raspberry Pi hardware config ──────────────────────────────
  902. info "Configuring Raspberry Pi hardware..."
  903. enable_spi
  904. enable_i2c
  905. configure_boot_config
  906. echo ""
  907. # ── Step 2: System packages ───────────────────────────────────────────
  908. install_system_packages
  909. detect_python || error "Failed to install Python 3.10+"
  910. echo ""
  911. # ── Step 2b: Strip unnecessary services & packages ────────────────────
  912. strip_services
  913. strip_packages
  914. echo ""
  915. # ── Step 3: Download source code ──────────────────────────────────────
  916. create_spoolbuddy_user
  917. download_spoolbuddy
  918. echo ""
  919. # ── Step 3b: Kiosk setup (labwc + Chromium + squeekboard + Plymouth) ──
  920. setup_kiosk
  921. echo ""
  922. # ── Step 4: SpoolBuddy setup ──────────────────────────────────────────
  923. info "Setting up SpoolBuddy..."
  924. setup_spoolbuddy_venv
  925. create_spoolbuddy_env
  926. setup_ssh_key
  927. create_spoolbuddy_service
  928. echo ""
  929. # ── Step 5: Bambuddy setup (full mode only) ───────────────────────────
  930. if [[ "$INSTALL_MODE" == "full" ]]; then
  931. info "Setting up Bambuddy..."
  932. create_bambuddy_user
  933. setup_bambuddy_venv
  934. install_nodejs
  935. build_frontend
  936. create_bambuddy_directories
  937. create_bambuddy_env
  938. create_bambuddy_service
  939. echo ""
  940. fi
  941. # ── Done ──────────────────────────────────────────────────────────────
  942. echo ""
  943. echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
  944. echo -e "${GREEN}║ ║${NC}"
  945. echo -e "${GREEN}║ Installation Complete! ║${NC}"
  946. echo -e "${GREEN}║ ║${NC}"
  947. echo -e "${GREEN}╚══════════════════════════════════════════════════════════╝${NC}"
  948. echo ""
  949. local ip_addr
  950. ip_addr=$(hostname -I 2>/dev/null | awk '{print $1}') || ip_addr="<your-ip>"
  951. if [[ "$INSTALL_MODE" == "full" ]]; then
  952. echo -e " ${BOLD}Bambuddy:${NC} ${CYAN}http://$ip_addr:$BAMBUDDY_PORT${NC}"
  953. else
  954. echo -e " ${BOLD}SpoolBuddy:${NC} Connecting to ${CYAN}$BAMBUDDY_URL${NC}"
  955. fi
  956. echo -e " ${BOLD}Kiosk URL:${NC} ${CYAN}$KIOSK_URL${NC}"
  957. echo -e " ${BOLD}Kiosk user:${NC} ${CYAN}$KIOSK_USER${NC}"
  958. echo ""
  959. if [[ "$INSTALL_MODE" == "full" ]]; then
  960. echo -e " ${BOLD}Next steps:${NC}"
  961. echo -e " 1. Reboot (required for kiosk, Plymouth splash, and hardware changes)"
  962. echo -e " 2. The touchscreen kiosk will start automatically after reboot"
  963. echo -e " 3. On another device, open ${CYAN}http://$ip_addr:$BAMBUDDY_PORT${NC}"
  964. echo -e " 4. Go to Settings -> API Keys and create an API key"
  965. echo -e " 5. Update the API key in: ${CYAN}$INSTALL_PATH/spoolbuddy/.env${NC}"
  966. echo -e " 6. Restart SpoolBuddy: ${CYAN}sudo systemctl restart spoolbuddy${NC}"
  967. fi
  968. echo ""
  969. echo -e " ${BOLD}Manage services:${NC}"
  970. echo -e " SpoolBuddy status: ${CYAN}sudo systemctl status spoolbuddy${NC}"
  971. echo -e " SpoolBuddy logs: ${CYAN}sudo journalctl -u spoolbuddy -f${NC}"
  972. if [[ "$INSTALL_MODE" == "full" ]]; then
  973. echo -e " Bambuddy status: ${CYAN}sudo systemctl status bambuddy${NC}"
  974. echo -e " Bambuddy logs: ${CYAN}sudo journalctl -u bambuddy -f${NC}"
  975. fi
  976. echo ""
  977. echo -e " ${BOLD}Configuration:${NC} ${CYAN}$INSTALL_PATH/spoolbuddy/.env${NC}"
  978. echo -e " ${BOLD}Hardware wiring:${NC} ${CYAN}$INSTALL_PATH/spoolbuddy/README.md${NC}"
  979. echo -e " ${BOLD}Diagnostics:${NC} ${CYAN}sudo $INSTALL_PATH/spoolbuddy/venv/bin/python $INSTALL_PATH/spoolbuddy/pn5180_diag.py${NC}"
  980. echo ""
  981. echo -e " ${YELLOW}A reboot is required to apply all changes (kiosk, Plymouth splash, hardware).${NC}"
  982. echo ""
  983. if prompt_yes_no "Reboot now?" "y"; then
  984. reboot
  985. else
  986. echo -e " Run ${CYAN}sudo reboot${NC} when ready."
  987. fi
  988. echo ""
  989. }
  990. main "$@"