setup-pi.sh 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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. # Check if running on Raspberry Pi
  128. check_raspberry_pi() {
  129. print_step "Checking system compatibility..."
  130. if [[ ! -f /proc/device-tree/model ]]; then
  131. print_warning "Could not detect Raspberry Pi model. Continuing anyway..."
  132. return
  133. fi
  134. MODEL=$(tr -d '\0' < /proc/device-tree/model)
  135. echo "Detected: $MODEL"
  136. # Check for 64-bit OS
  137. ARCH=$(uname -m)
  138. if [[ "$ARCH" != "aarch64" && "$ARCH" != "arm64" ]]; then
  139. print_error "64-bit OS required. Detected: $ARCH"
  140. echo "Please reinstall Raspberry Pi OS (64-bit) using Raspberry Pi Imager"
  141. exit 1
  142. fi
  143. print_success "64-bit OS detected ($ARCH)"
  144. }
  145. # Disable WLAN power save
  146. disable_wlan_powersave() {
  147. print_step "Disabling WLAN power save for better stability..."
  148. # Check if already disabled
  149. if iwconfig wlan0 2>/dev/null | grep -q "Power Management:off"; then
  150. echo "WLAN power save already disabled"
  151. return
  152. fi
  153. # Create config to persist across reboots
  154. sudo tee /etc/NetworkManager/conf.d/wifi-powersave-off.conf > /dev/null << 'EOF'
  155. [connection]
  156. wifi.powersave = 2
  157. EOF
  158. # Also try immediate disable
  159. sudo iwconfig wlan0 power off 2>/dev/null || true
  160. print_success "WLAN power save disabled"
  161. }
  162. # Apply WiFi stability fix
  163. apply_wifi_fix() {
  164. print_step "Applying WiFi stability fix..."
  165. CMDLINE_FILE="/boot/firmware/cmdline.txt"
  166. if [[ ! -f "$CMDLINE_FILE" ]]; then
  167. CMDLINE_FILE="/boot/cmdline.txt"
  168. fi
  169. if [[ ! -f "$CMDLINE_FILE" ]]; then
  170. print_warning "Could not find cmdline.txt, skipping WiFi fix"
  171. return
  172. fi
  173. # Check if fix already applied
  174. if grep -q "brcmfmac.feature_disable=0x82000" "$CMDLINE_FILE"; then
  175. echo "WiFi fix already applied"
  176. return
  177. fi
  178. # Backup and apply fix
  179. sudo cp "$CMDLINE_FILE" "${CMDLINE_FILE}.backup"
  180. sudo sed -i 's/$/ brcmfmac.feature_disable=0x82000/' "$CMDLINE_FILE"
  181. print_success "WiFi fix applied. A reboot is recommended after setup."
  182. NEEDS_REBOOT=true
  183. }
  184. # Verify we're in the dune-weaver directory
  185. ensure_repo() {
  186. print_step "Setting up dune-weaver repository..."
  187. # Check if we're already in the dune-weaver directory
  188. if [[ -f "main.py" ]] && [[ -f "requirements.txt" ]]; then
  189. INSTALL_DIR="$(pwd)"
  190. print_success "Using existing repo at $INSTALL_DIR"
  191. return
  192. fi
  193. # Check if repo exists in home directory
  194. if [[ -d "$INSTALL_DIR" ]] && [[ -f "$INSTALL_DIR/main.py" ]]; then
  195. print_success "Found existing repo at $INSTALL_DIR"
  196. cd "$INSTALL_DIR"
  197. echo "Pulling latest changes..."
  198. git pull
  199. return
  200. fi
  201. # Clone the repository
  202. print_step "Cloning dune-weaver repository..."
  203. git clone "$REPO_URL" --single-branch "$INSTALL_DIR"
  204. cd "$INSTALL_DIR"
  205. print_success "Cloned to $INSTALL_DIR"
  206. }
  207. # Deploy native (venv + systemd + nginx)
  208. deploy_native() {
  209. print_step "Setting up Python virtual environment..."
  210. cd "$INSTALL_DIR"
  211. # Create venv
  212. python3 -m venv .venv
  213. source .venv/bin/activate
  214. # Install dependencies
  215. print_step "Installing Python packages..."
  216. pip install --upgrade pip
  217. pip install -r requirements.txt
  218. # Ensure nginx (www-data) can traverse to static files
  219. # chmod o+x grants traversal only, not directory listing
  220. local dir="$INSTALL_DIR"
  221. while [[ "$dir" != "/" ]]; do
  222. sudo chmod o+x "$dir"
  223. dir=$(dirname "$dir")
  224. done
  225. # Configure nginx
  226. print_step "Configuring nginx..."
  227. sudo cp "$INSTALL_DIR/nginx/dune-weaver.conf" /etc/nginx/sites-available/dune-weaver.conf
  228. sudo sed -i "s|INSTALL_DIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/nginx/sites-available/dune-weaver.conf
  229. sudo ln -sf /etc/nginx/sites-available/dune-weaver.conf /etc/nginx/sites-enabled/dune-weaver.conf
  230. sudo rm -f /etc/nginx/sites-enabled/default
  231. sudo nginx -t
  232. sudo systemctl restart nginx
  233. sudo systemctl enable nginx
  234. # Create systemd service
  235. print_step "Creating systemd service..."
  236. sudo cp "$INSTALL_DIR/dune-weaver.service" /etc/systemd/system/dune-weaver.service
  237. sudo sed -i "s|USER_PLACEHOLDER|$USER|g" /etc/systemd/system/dune-weaver.service
  238. sudo sed -i "s|INSTALL_DIR_PLACEHOLDER|$INSTALL_DIR|g" /etc/systemd/system/dune-weaver.service
  239. # Enable and start service
  240. sudo systemctl daemon-reload
  241. sudo systemctl enable dune-weaver
  242. sudo systemctl start dune-weaver
  243. # Create sudoers entry for passwordless systemctl commands
  244. print_step "Configuring sudo permissions..."
  245. sudo tee /etc/sudoers.d/dune-weaver > /dev/null << EOF
  246. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart dune-weaver
  247. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop dune-weaver
  248. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl start dune-weaver
  249. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl poweroff
  250. $USER ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
  251. EOF
  252. sudo chmod 0440 /etc/sudoers.d/dune-weaver
  253. print_success "Native deployment complete!"
  254. }
  255. # Install dw CLI command
  256. install_cli() {
  257. print_step "Installing 'dw' command..."
  258. # Copy dw script to /usr/local/bin
  259. sudo cp "$INSTALL_DIR/dw" /usr/local/bin/dw
  260. sudo chmod +x /usr/local/bin/dw
  261. print_success "'dw' command installed"
  262. }
  263. # Setup autohotspot
  264. setup_autohotspot() {
  265. print_step "Setting up autohotspot..."
  266. if [[ ! -f "$INSTALL_DIR/wifi/setup-wifi.sh" ]]; then
  267. print_warning "wifi/setup-wifi.sh not found, skipping autohotspot setup"
  268. return
  269. fi
  270. bash "$INSTALL_DIR/wifi/setup-wifi.sh"
  271. print_success "Autohotspot setup complete"
  272. }
  273. # Configure UART for GPIO pin connection to DLC32/ESP32
  274. configure_uart() {
  275. local CONFIG_FILE="/boot/firmware/config.txt"
  276. if [[ ! -f "$CONFIG_FILE" ]]; then
  277. CONFIG_FILE="/boot/config.txt"
  278. fi
  279. echo ""
  280. echo -e "${GREEN}How is your Raspberry Pi connected to the sand table controller (DLC32/ESP32)?${NC}"
  281. echo ""
  282. echo " 1) USB cable"
  283. echo " 2) UART over GPIO pins (TX/RX wired to header pins)"
  284. echo ""
  285. echo -e " ${YELLOW}Note: USB is not reliable on Pi 3B+. Use UART for Pi 3B+.${NC}"
  286. echo ""
  287. read -p "Enter choice [1/2] (default: 1): " -n 1 -r uart_choice
  288. echo ""
  289. if [[ "$uart_choice" == "2" ]]; then
  290. echo ""
  291. echo -e "${YELLOW}============================================${NC}"
  292. echo -e "${YELLOW} UART Setup — raspi-config will run next${NC}"
  293. echo -e "${YELLOW}============================================${NC}"
  294. echo ""
  295. echo -e " When prompted, select:"
  296. echo -e " Login shell over serial? → ${GREEN}No${NC}"
  297. echo -e " Serial port hardware? → ${GREEN}Yes${NC}"
  298. echo ""
  299. read -p "Press Enter to continue..." -r
  300. echo ""
  301. # Disable serial console, enable serial hardware
  302. if command -v raspi-config &> /dev/null; then
  303. sudo raspi-config nonint do_serial 2
  304. echo "Serial console disabled, serial hardware enabled"
  305. else
  306. print_warning "raspi-config not found, please run 'sudo raspi-config' manually"
  307. print_warning "Go to: 3 Interface Options > I6 Serial Port > No (console) > Yes (hardware)"
  308. fi
  309. print_step "Configuring UART overlays..."
  310. # Add UART overlays to config.txt if not already present
  311. local needs_change=false
  312. for overlay in "dtoverlay=pi3-miniuart-bt" "dtoverlay=miniuart-bt" "enable_uart=1"; do
  313. if ! grep -q "^${overlay}$" "$CONFIG_FILE" 2>/dev/null; then
  314. echo "$overlay" | sudo tee -a "$CONFIG_FILE" > /dev/null
  315. needs_change=true
  316. fi
  317. done
  318. if [[ "$needs_change" == "true" ]]; then
  319. echo "Added UART overlays to $CONFIG_FILE"
  320. else
  321. echo "UART overlays already present in $CONFIG_FILE"
  322. fi
  323. NEEDS_REBOOT=true
  324. print_success "UART configured. A reboot is required for changes to take effect."
  325. else
  326. # USB mode — check if UART config exists and offer to clean it up
  327. local has_uart=false
  328. for overlay in "dtoverlay=pi3-miniuart-bt" "dtoverlay=miniuart-bt" "enable_uart=1"; do
  329. if grep -q "^${overlay}$" "$CONFIG_FILE" 2>/dev/null; then
  330. has_uart=true
  331. break
  332. fi
  333. done
  334. if [[ "$has_uart" == "true" ]]; then
  335. echo -e "${YELLOW}UART overlays found in $CONFIG_FILE from a previous setup.${NC}"
  336. read -p "Remove them? (y/N): " -n 1 -r remove_uart
  337. echo ""
  338. if [[ "$remove_uart" =~ ^[Yy]$ ]]; then
  339. sudo sed -i '/^dtoverlay=pi3-miniuart-bt$/d' "$CONFIG_FILE"
  340. sudo sed -i '/^dtoverlay=miniuart-bt$/d' "$CONFIG_FILE"
  341. sudo sed -i '/^enable_uart=1$/d' "$CONFIG_FILE"
  342. NEEDS_REBOOT=true
  343. print_success "UART overlays removed. A reboot is recommended."
  344. fi
  345. else
  346. echo "USB connection selected, no UART changes needed."
  347. fi
  348. fi
  349. }
  350. # Remove software that is no longer needed on the Pi
  351. cleanup_unused() {
  352. print_step "Cleaning up unused software..."
  353. # Remove Node.js / npm if present from a prior install
  354. if dpkg -l nodejs 2>/dev/null | grep -q '^ii'; then
  355. echo "Removing Node.js (no longer needed — frontend is pre-built)..."
  356. sudo apt purge -y nodejs || true
  357. sudo rm -f /etc/apt/sources.list.d/nodesource.list \
  358. /etc/apt/keyrings/nodesource.gpg 2>/dev/null || true
  359. sudo apt autoremove -y
  360. print_success "Node.js removed"
  361. fi
  362. # Remove Docker if present from old Docker-based deployment
  363. if dpkg -l docker-ce 2>/dev/null | grep -q '^ii'; then
  364. echo "Removing Docker (old deployment method)..."
  365. # Stop running containers
  366. sudo docker stop $(sudo docker ps -aq) 2>/dev/null || true
  367. sudo apt purge -y docker-ce docker-ce-cli containerd.io docker-compose-plugin 2>/dev/null || true
  368. sudo rm -rf /var/lib/docker
  369. sudo apt autoremove -y
  370. print_success "Docker removed"
  371. fi
  372. }
  373. # Get IP address
  374. get_ip_address() {
  375. # Try multiple methods to get IP
  376. IP=$(hostname -I 2>/dev/null | awk '{print $1}')
  377. if [[ -z "$IP" ]]; then
  378. IP=$(ip route get 1 2>/dev/null | awk '{print $7}' | head -1)
  379. fi
  380. if [[ -z "$IP" ]]; then
  381. IP="<your-pi-ip>"
  382. fi
  383. echo "$IP"
  384. }
  385. # Print final instructions
  386. print_final_instructions() {
  387. IP=$(get_ip_address)
  388. HOSTNAME=$(hostname)
  389. echo ""
  390. echo -e "${GREEN}============================================${NC}"
  391. echo -e "${GREEN} Dune Weaver Setup Complete!${NC}"
  392. echo -e "${GREEN}============================================${NC}"
  393. echo ""
  394. echo -e "Access the web interface at:"
  395. echo -e " ${BLUE}http://$IP${NC}"
  396. echo -e " ${BLUE}http://$HOSTNAME.local${NC}"
  397. echo ""
  398. echo "Manage with the 'dw' command:"
  399. echo " dw logs View live logs"
  400. echo " dw restart Restart Dune Weaver"
  401. echo " dw update Pull latest and restart"
  402. echo " dw stop Stop Dune Weaver"
  403. echo " dw status Show status"
  404. echo " dw wifi help WiFi and hotspot management"
  405. echo " dw help Show all commands"
  406. echo ""
  407. if [[ "$SETUP_HOTSPOT" == "true" ]]; then
  408. echo -e "${BLUE}Autohotspot:${NC} If no known WiFi is found on boot,"
  409. echo "a 'Dune Weaver' hotspot will be created automatically."
  410. echo "Connect to it and open the app to configure WiFi."
  411. echo ""
  412. fi
  413. if [[ "$NEEDS_REBOOT" == "true" ]]; then
  414. print_warning "A reboot is required to apply configuration changes"
  415. read -p "Reboot now? (y/N) " -n 1 -r
  416. echo
  417. if [[ $REPLY =~ ^[Yy]$ ]]; then
  418. sudo reboot
  419. fi
  420. fi
  421. }
  422. # Main installation flow
  423. main() {
  424. echo -e "${GREEN}"
  425. echo " ____ __ __ "
  426. echo " | _ \ _ _ _ __ ___\ \ / /__ __ ___ _____ _ __ "
  427. echo " | | | | | | | '_ \ / _ \\ \ /\ / / _ \/ _\` \ \ / / _ \ '__|"
  428. echo " | |_| | |_| | | | | __/ \ V V / __/ (_| |\ V / __/ | "
  429. echo " |____/ \__,_|_| |_|\___| \_/\_/ \___|\__,_| \_/ \___|_| "
  430. echo -e "${NC}"
  431. echo "Raspberry Pi Setup Script"
  432. echo ""
  433. echo "Install directory: $INSTALL_DIR"
  434. echo ""
  435. # Ask connection type upfront (before long-running installs)
  436. configure_uart
  437. # Run setup steps
  438. check_raspberry_pi
  439. install_system_deps
  440. ensure_repo
  441. disable_wlan_powersave
  442. if [[ "$FIX_WIFI" == "true" ]]; then
  443. apply_wifi_fix
  444. fi
  445. if [[ "$SETUP_HOTSPOT" == "true" ]]; then
  446. setup_autohotspot
  447. fi
  448. install_lgpio
  449. deploy_native
  450. install_cli
  451. cleanup_unused
  452. print_final_instructions
  453. }
  454. # Run main
  455. main