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