setup-pi.sh 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. #!/bin/bash
  2. #
  3. # Dune Weaver Raspberry Pi Setup Script
  4. #
  5. # ONE-COMMAND INSTALL (recommended):
  6. # curl -fsSL https://raw.githubusercontent.com/tuanchris/dune-weaver/main/setup-pi.sh | bash
  7. #
  8. # OR from existing clone:
  9. # git clone https://github.com/tuanchris/dune-weaver --single-branch
  10. # cd dune-weaver
  11. # bash setup-pi.sh
  12. #
  13. # Options:
  14. # --no-wifi-fix Skip WiFi stability fix
  15. # --no-hotspot Skip autohotspot setup
  16. # --help Show help
  17. #
  18. set -e
  19. # Colors for output
  20. RED='\033[0;31m'
  21. GREEN='\033[0;32m'
  22. YELLOW='\033[1;33m'
  23. BLUE='\033[0;34m'
  24. NC='\033[0m' # No Color
  25. # Default options
  26. FIX_WIFI=true # Applied by default for stability
  27. SETUP_HOTSPOT=true # Autohotspot for first-time WiFi setup
  28. INSTALL_DIR="$HOME/dune-weaver"
  29. REPO_URL="https://github.com/tuanchris/dune-weaver"
  30. # Parse arguments
  31. while [[ $# -gt 0 ]]; do
  32. case $1 in
  33. --no-wifi-fix)
  34. FIX_WIFI=false
  35. shift
  36. ;;
  37. --no-hotspot)
  38. SETUP_HOTSPOT=false
  39. shift
  40. ;;
  41. --help|-h)
  42. echo "Dune Weaver Raspberry Pi Setup Script"
  43. echo ""
  44. echo "One-command install:"
  45. echo " curl -fsSL https://raw.githubusercontent.com/tuanchris/dune-weaver/main/setup-pi.sh | bash"
  46. echo ""
  47. echo "Or from existing clone:"
  48. echo " cd ~/dune-weaver && bash setup-pi.sh [OPTIONS]"
  49. echo ""
  50. echo "Options:"
  51. echo " --no-wifi-fix Skip WiFi stability fix (applied by default)"
  52. echo " --no-hotspot Skip autohotspot setup"
  53. echo " --help, -h Show this help message"
  54. exit 0
  55. ;;
  56. *)
  57. echo -e "${RED}Unknown option: $1${NC}"
  58. echo "Use --help for usage information"
  59. exit 1
  60. ;;
  61. esac
  62. done
  63. # Helper functions
  64. print_step() {
  65. echo -e "\n${BLUE}==>${NC} ${GREEN}$1${NC}"
  66. }
  67. print_warning() {
  68. echo -e "${YELLOW}Warning:${NC} $1"
  69. }
  70. print_error() {
  71. echo -e "${RED}Error:${NC} $1"
  72. }
  73. print_success() {
  74. echo -e "${GREEN}$1${NC}"
  75. }
  76. # Install system dependencies
  77. install_system_deps() {
  78. print_step "Installing system dependencies..."
  79. sudo apt update
  80. sudo DEBIAN_FRONTEND=noninteractive apt install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" \
  81. python3-venv python3-pip python3-dev \
  82. gcc g++ make swig unzip wget \
  83. libjpeg-dev zlib1g-dev \
  84. libgpiod-dev gpiod \
  85. nginx git vim
  86. print_success "System dependencies installed"
  87. }
  88. # Ensure lgpio C library is available for pip to build against
  89. install_lgpio() {
  90. local syslib="/usr/lib/aarch64-linux-gnu"
  91. # Raspberry Pi OS Trixie ships liblgpio.so.1 but no unversioned
  92. # liblgpio.so symlink (normally provided by a -dev package).
  93. # The build-time linker needs the unversioned name (-llgpio → liblgpio.so).
  94. if [[ ! -e "$syslib/liblgpio.so" ]]; then
  95. # Check if the versioned library exists (from Pi OS)
  96. local versioned
  97. versioned=$(ls "$syslib"/liblgpio.so.* 2>/dev/null | head -1)
  98. if [[ -n "$versioned" ]]; then
  99. print_step "Creating liblgpio.so build symlink..."
  100. sudo ln -sf "$versioned" "$syslib/liblgpio.so"
  101. sudo ldconfig
  102. print_success "liblgpio.so symlink created (-> $(basename "$versioned"))"
  103. else
  104. # Not in system paths — build from source
  105. print_step "Building lgpio C library from source..."
  106. local tmpdir
  107. tmpdir=$(mktemp -d)
  108. cd "$tmpdir"
  109. wget -q https://github.com/joan2937/lg/archive/master.zip
  110. unzip -q master.zip
  111. cd lg-master
  112. make
  113. sudo make install
  114. cd /
  115. rm -rf "$tmpdir"
  116. # Symlink from /usr/local/lib into system path
  117. if [[ -f /usr/local/lib/liblgpio.so ]]; then
  118. sudo ln -sf /usr/local/lib/liblgpio.so "$syslib/liblgpio.so"
  119. fi
  120. sudo ldconfig
  121. print_success "lgpio C library built and installed"
  122. fi
  123. else
  124. echo "liblgpio.so already available for linking"
  125. fi
  126. }
  127. # Install Node.js 20 via nodesource
  128. install_nodejs() {
  129. print_step "Installing Node.js..."
  130. if command -v node &> /dev/null; then
  131. local node_version
  132. node_version=$(node --version)
  133. echo "Node.js already installed: $node_version"
  134. # Check if version is 20+
  135. local major
  136. major=$(echo "$node_version" | sed 's/v//' | cut -d. -f1)
  137. if [[ "$major" -ge 20 ]]; then
  138. print_success "Node.js version is sufficient"
  139. return
  140. fi
  141. echo "Upgrading to Node.js 20..."
  142. fi
  143. curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
  144. sudo DEBIAN_FRONTEND=noninteractive apt install -y nodejs
  145. print_success "Node.js $(node --version) installed"
  146. }
  147. # Check if running on Raspberry Pi
  148. check_raspberry_pi() {
  149. print_step "Checking system compatibility..."
  150. if [[ ! -f /proc/device-tree/model ]]; then
  151. print_warning "Could not detect Raspberry Pi model. Continuing anyway..."
  152. return
  153. fi
  154. MODEL=$(tr -d '\0' < /proc/device-tree/model)
  155. echo "Detected: $MODEL"
  156. # Check for 64-bit OS
  157. ARCH=$(uname -m)
  158. if [[ "$ARCH" != "aarch64" && "$ARCH" != "arm64" ]]; then
  159. print_error "64-bit OS required. Detected: $ARCH"
  160. echo "Please reinstall Raspberry Pi OS (64-bit) using Raspberry Pi Imager"
  161. exit 1
  162. fi
  163. print_success "64-bit OS detected ($ARCH)"
  164. }
  165. # Disable WLAN power save
  166. disable_wlan_powersave() {
  167. print_step "Disabling WLAN power save for better stability..."
  168. # Check if already disabled
  169. if iwconfig wlan0 2>/dev/null | grep -q "Power Management:off"; then
  170. echo "WLAN power save already disabled"
  171. return
  172. fi
  173. # Create config to persist across reboots
  174. sudo tee /etc/NetworkManager/conf.d/wifi-powersave-off.conf > /dev/null << 'EOF'
  175. [connection]
  176. wifi.powersave = 2
  177. EOF
  178. # Also try immediate disable
  179. sudo iwconfig wlan0 power off 2>/dev/null || true
  180. print_success "WLAN power save disabled"
  181. }
  182. # Apply WiFi stability fix
  183. apply_wifi_fix() {
  184. print_step "Applying WiFi stability fix..."
  185. CMDLINE_FILE="/boot/firmware/cmdline.txt"
  186. if [[ ! -f "$CMDLINE_FILE" ]]; then
  187. CMDLINE_FILE="/boot/cmdline.txt"
  188. fi
  189. if [[ ! -f "$CMDLINE_FILE" ]]; then
  190. print_warning "Could not find cmdline.txt, skipping WiFi fix"
  191. return
  192. fi
  193. # Check if fix already applied
  194. if grep -q "brcmfmac.feature_disable=0x82000" "$CMDLINE_FILE"; then
  195. echo "WiFi fix already applied"
  196. return
  197. fi
  198. # Backup and apply fix
  199. sudo cp "$CMDLINE_FILE" "${CMDLINE_FILE}.backup"
  200. sudo sed -i 's/$/ brcmfmac.feature_disable=0x82000/' "$CMDLINE_FILE"
  201. print_success "WiFi fix applied. A reboot is recommended after setup."
  202. NEEDS_REBOOT=true
  203. }
  204. # Update system packages
  205. update_system() {
  206. print_step "Updating system packages..."
  207. sudo apt update
  208. sudo DEBIAN_FRONTEND=noninteractive apt full-upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"
  209. print_success "System updated"
  210. }
  211. # Verify we're in the dune-weaver directory
  212. ensure_repo() {
  213. print_step "Setting up dune-weaver repository..."
  214. # Check if we're already in the dune-weaver directory
  215. if [[ -f "main.py" ]] && [[ -f "requirements.txt" ]]; then
  216. INSTALL_DIR="$(pwd)"
  217. print_success "Using existing repo at $INSTALL_DIR"
  218. return
  219. fi
  220. # Check if repo exists in home directory
  221. if [[ -d "$INSTALL_DIR" ]] && [[ -f "$INSTALL_DIR/main.py" ]]; then
  222. print_success "Found existing repo at $INSTALL_DIR"
  223. cd "$INSTALL_DIR"
  224. echo "Pulling latest changes..."
  225. git pull
  226. return
  227. fi
  228. # Clone the repository
  229. print_step "Cloning dune-weaver repository..."
  230. git clone "$REPO_URL" --single-branch "$INSTALL_DIR"
  231. cd "$INSTALL_DIR"
  232. print_success "Cloned to $INSTALL_DIR"
  233. }
  234. # Deploy native (venv + systemd + nginx)
  235. deploy_native() {
  236. print_step "Setting up Python virtual environment..."
  237. cd "$INSTALL_DIR"
  238. # Create venv
  239. python3 -m venv .venv
  240. source .venv/bin/activate
  241. # Install dependencies
  242. print_step "Installing Python packages..."
  243. pip install --upgrade pip
  244. pip install -r requirements.txt
  245. # Build frontend
  246. print_step "Building frontend..."
  247. cd "$INSTALL_DIR/frontend"
  248. npm ci
  249. npm run build
  250. cd "$INSTALL_DIR"
  251. # Ensure nginx (www-data) can traverse to static files
  252. # chmod o+x grants traversal only, not directory listing
  253. local dir="$INSTALL_DIR"
  254. while [[ "$dir" != "/" ]]; do
  255. sudo chmod o+x "$dir"
  256. dir=$(dirname "$dir")
  257. done
  258. # Configure nginx
  259. print_step "Configuring nginx..."
  260. sudo cp "$INSTALL_DIR/nginx/dune-weaver.conf" /etc/nginx/sites-available/dune-weaver.conf
  261. sudo sed -i "s|INSTALL_DIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/nginx/sites-available/dune-weaver.conf
  262. sudo ln -sf /etc/nginx/sites-available/dune-weaver.conf /etc/nginx/sites-enabled/dune-weaver.conf
  263. sudo rm -f /etc/nginx/sites-enabled/default
  264. sudo nginx -t
  265. sudo systemctl restart nginx
  266. sudo systemctl enable nginx
  267. # Create systemd service
  268. print_step "Creating systemd service..."
  269. sudo cp "$INSTALL_DIR/dune-weaver.service" /etc/systemd/system/dune-weaver.service
  270. sudo sed -i "s|USER_PLACEHOLDER|$USER|g" /etc/systemd/system/dune-weaver.service
  271. sudo sed -i "s|INSTALL_DIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/systemd/system/dune-weaver.service
  272. # Enable and start service
  273. sudo systemctl daemon-reload
  274. sudo systemctl enable dune-weaver
  275. sudo systemctl start dune-weaver
  276. # Create sudoers entry for passwordless systemctl commands
  277. print_step "Configuring sudo permissions..."
  278. sudo tee /etc/sudoers.d/dune-weaver > /dev/null << EOF
  279. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart dune-weaver
  280. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop dune-weaver
  281. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl start dune-weaver
  282. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl poweroff
  283. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
  284. EOF
  285. sudo chmod 0440 /etc/sudoers.d/dune-weaver
  286. print_success "Native deployment complete!"
  287. }
  288. # Install dw CLI command
  289. install_cli() {
  290. print_step "Installing 'dw' command..."
  291. # Copy dw script to /usr/local/bin
  292. sudo cp "$INSTALL_DIR/dw" /usr/local/bin/dw
  293. sudo chmod +x /usr/local/bin/dw
  294. print_success "'dw' command installed"
  295. }
  296. # Setup autohotspot
  297. setup_autohotspot() {
  298. print_step "Setting up autohotspot..."
  299. if [[ ! -f "$INSTALL_DIR/wifi/setup-wifi.sh" ]]; then
  300. print_warning "wifi/setup-wifi.sh not found, skipping autohotspot setup"
  301. return
  302. fi
  303. bash "$INSTALL_DIR/wifi/setup-wifi.sh"
  304. print_success "Autohotspot setup complete"
  305. }
  306. # Configure UART for GPIO pin connection to DLC32/ESP32
  307. configure_uart() {
  308. local CONFIG_FILE="/boot/firmware/config.txt"
  309. if [[ ! -f "$CONFIG_FILE" ]]; then
  310. CONFIG_FILE="/boot/config.txt"
  311. fi
  312. echo ""
  313. echo -e "${GREEN}How is your Raspberry Pi connected to the sand table controller (DLC32/ESP32)?${NC}"
  314. echo ""
  315. echo " 1) USB cable"
  316. echo " 2) UART over GPIO pins (TX/RX wired to header pins)"
  317. echo ""
  318. echo -e " ${YELLOW}Note: USB is not reliable on Pi 3B+. Use UART for Pi 3B+.${NC}"
  319. echo ""
  320. read -p "Enter choice [1/2] (default: 1): " -n 1 -r uart_choice
  321. echo ""
  322. if [[ "$uart_choice" == "2" ]]; then
  323. echo ""
  324. echo "UART setup requires running raspi-config to change serial port settings."
  325. echo "When prompted:"
  326. echo " - 'Would you like a login shell over serial?' → Select ${GREEN}No${NC}"
  327. echo " - 'Would you like the serial port hardware enabled?' → Select ${GREEN}Yes${NC}"
  328. echo ""
  329. read -p "Press Enter to continue..." -r
  330. echo ""
  331. # Disable serial console, enable serial hardware
  332. if command -v raspi-config &> /dev/null; then
  333. sudo raspi-config nonint do_serial 2
  334. echo "Serial console disabled, serial hardware enabled"
  335. else
  336. print_warning "raspi-config not found, please run 'sudo raspi-config' manually"
  337. print_warning "Go to: 3 Interface Options > I6 Serial Port > No (console) > Yes (hardware)"
  338. fi
  339. print_step "Configuring UART overlays..."
  340. # Add UART overlays to config.txt if not already present
  341. local needs_change=false
  342. for overlay in "dtoverlay=pi3-miniuart-bt" "dtoverlay=miniuart-bt" "enable_uart=1"; do
  343. if ! grep -q "^${overlay}$" "$CONFIG_FILE" 2>/dev/null; then
  344. echo "$overlay" | sudo tee -a "$CONFIG_FILE" > /dev/null
  345. needs_change=true
  346. fi
  347. done
  348. if [[ "$needs_change" == "true" ]]; then
  349. echo "Added UART overlays to $CONFIG_FILE"
  350. else
  351. echo "UART overlays already present in $CONFIG_FILE"
  352. fi
  353. NEEDS_REBOOT=true
  354. print_success "UART configured. A reboot is required for changes to take effect."
  355. else
  356. # USB mode — check if UART config exists and offer to clean it up
  357. local has_uart=false
  358. for overlay in "dtoverlay=pi3-miniuart-bt" "dtoverlay=miniuart-bt" "enable_uart=1"; do
  359. if grep -q "^${overlay}$" "$CONFIG_FILE" 2>/dev/null; then
  360. has_uart=true
  361. break
  362. fi
  363. done
  364. if [[ "$has_uart" == "true" ]]; then
  365. echo -e "${YELLOW}UART overlays found in $CONFIG_FILE from a previous setup.${NC}"
  366. read -p "Remove them? (y/N): " -n 1 -r remove_uart
  367. echo ""
  368. if [[ "$remove_uart" =~ ^[Yy]$ ]]; then
  369. sudo sed -i '/^dtoverlay=pi3-miniuart-bt$/d' "$CONFIG_FILE"
  370. sudo sed -i '/^dtoverlay=miniuart-bt$/d' "$CONFIG_FILE"
  371. sudo sed -i '/^enable_uart=1$/d' "$CONFIG_FILE"
  372. NEEDS_REBOOT=true
  373. print_success "UART overlays removed. A reboot is recommended."
  374. fi
  375. else
  376. echo "USB connection selected, no UART changes needed."
  377. fi
  378. fi
  379. }
  380. # Get IP address
  381. get_ip_address() {
  382. # Try multiple methods to get IP
  383. IP=$(hostname -I 2>/dev/null | awk '{print $1}')
  384. if [[ -z "$IP" ]]; then
  385. IP=$(ip route get 1 2>/dev/null | awk '{print $7}' | head -1)
  386. fi
  387. if [[ -z "$IP" ]]; then
  388. IP="<your-pi-ip>"
  389. fi
  390. echo "$IP"
  391. }
  392. # Print final instructions
  393. print_final_instructions() {
  394. IP=$(get_ip_address)
  395. HOSTNAME=$(hostname)
  396. echo ""
  397. echo -e "${GREEN}============================================${NC}"
  398. echo -e "${GREEN} Dune Weaver Setup Complete!${NC}"
  399. echo -e "${GREEN}============================================${NC}"
  400. echo ""
  401. echo -e "Access the web interface at:"
  402. echo -e " ${BLUE}http://$IP${NC}"
  403. echo -e " ${BLUE}http://$HOSTNAME.local${NC}"
  404. echo ""
  405. echo "Manage with the 'dw' command:"
  406. echo " dw logs View live logs"
  407. echo " dw restart Restart Dune Weaver"
  408. echo " dw update Pull latest and restart"
  409. echo " dw stop Stop Dune Weaver"
  410. echo " dw status Show status"
  411. echo " dw wifi help WiFi and hotspot management"
  412. echo " dw help Show all commands"
  413. echo ""
  414. if [[ "$SETUP_HOTSPOT" == "true" ]]; then
  415. echo -e "${BLUE}Autohotspot:${NC} If no known WiFi is found on boot,"
  416. echo "a 'Dune Weaver' hotspot will be created automatically."
  417. echo "Connect to it and open the app to configure WiFi."
  418. echo ""
  419. fi
  420. if [[ "$NEEDS_REBOOT" == "true" ]]; then
  421. print_warning "A reboot is required to apply configuration changes"
  422. read -p "Reboot now? (y/N) " -n 1 -r
  423. echo
  424. if [[ $REPLY =~ ^[Yy]$ ]]; then
  425. sudo reboot
  426. fi
  427. fi
  428. }
  429. # Main installation flow
  430. main() {
  431. echo -e "${GREEN}"
  432. echo " ____ __ __ "
  433. echo " | _ \ _ _ _ __ ___\ \ / /__ __ ___ _____ _ __ "
  434. echo " | | | | | | | '_ \ / _ \\ \ /\ / / _ \/ _\` \ \ / / _ \ '__|"
  435. echo " | |_| | |_| | | | | __/ \ V V / __/ (_| |\ V / __/ | "
  436. echo " |____/ \__,_|_| |_|\___| \_/\_/ \___|\__,_| \_/ \___|_| "
  437. echo -e "${NC}"
  438. echo "Raspberry Pi Setup Script"
  439. echo ""
  440. echo "Install directory: $INSTALL_DIR"
  441. echo ""
  442. # Ask connection type upfront (before long-running installs)
  443. configure_uart
  444. # Run setup steps
  445. check_raspberry_pi
  446. install_system_deps
  447. install_nodejs
  448. ensure_repo
  449. update_system
  450. disable_wlan_powersave
  451. if [[ "$FIX_WIFI" == "true" ]]; then
  452. apply_wifi_fix
  453. fi
  454. if [[ "$SETUP_HOTSPOT" == "true" ]]; then
  455. setup_autohotspot
  456. fi
  457. install_lgpio
  458. deploy_native
  459. install_cli
  460. print_final_instructions
  461. }
  462. # Run main
  463. main