Skip to content

Commit d5dfb07

Browse files
Add unified setup.sh and fix 10.71.1.x subnet config
- Add setup.sh: single script covering system packages (can-utils, python3-gi, GStreamer), MCP2517FD dtoverlay (20 MHz crystal), CAN kernel modules, can0 service, uv install, Python venv with system-site-packages, car-telemetry service install, static IP on eth0, Tailscale, and NoMachine - Fix setup-static-ip.sh: update subnet from 192.168.1.x to 10.71.1.x - Fix car-telemetry.service: update User, paths, and REMOTE_IP for car user on 10.71.1.x
1 parent c2827a4 commit d5dfb07

3 files changed

Lines changed: 269 additions & 10 deletions

File tree

universal-telemetry-software/deploy/car-telemetry.service

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ After=network.target
1919

2020
[Service]
2121
Type=simple
22-
User=base
23-
WorkingDirectory=/home/base/data-acquisition/universal-telemetry-software
22+
User=car
23+
WorkingDirectory=/home/car/data-acquisition/universal-telemetry-software
2424

2525
# Hardcoded car environment — edit here if IPs change
2626
Environment=ROLE=car
@@ -39,13 +39,13 @@ Environment=ENABLE_TIMESCALE_LOGGING=false
3939
# Set OFF_THE_SHELF=false when using our custom PoE injection board + status LEDs
4040
Environment=OFF_THE_SHELF=true
4141
# Set at install time: sed -i "s/GIT_HASH=unknown/GIT_HASH=$(git rev-parse --short HEAD)/" car-telemetry.service
42-
Environment=GIT_HASH=unknown
42+
Environment=GIT_HASH=c2827a4
4343
# REDIS_URL points to localhost — Redis is optional on the car.
4444
# Without it the WS bridge uses the direct in-process queue instead.
4545

4646
# uv run handles virtualenv creation and dependency sync automatically.
4747
# Run `uv sync` once during setup (requires internet) to pre-build the venv.
48-
ExecStart=/home/base/.local/bin/uv run python main.py
48+
ExecStart=/home/car/.local/bin/uv run python main.py
4949

5050
# Always restart — handles CAN bus glitches, Redis not ready yet, etc.
5151
Restart=always

universal-telemetry-software/setup-static-ip.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ fi
1919
echo -e "${YELLOW}Static IP setup for eth0 (Ubiquiti radio link)${NC}"
2020
echo ""
2121
echo "Which RPi is this?"
22-
echo " 1) Car — 192.168.1.10"
23-
echo " 2) Base — 192.168.1.20"
22+
echo " 1) Car — 10.71.1.10"
23+
echo " 2) Base — 10.71.1.20"
2424
echo ""
2525
read -rp "Enter 1 or 2: " choice
2626

2727
case "$choice" in
28-
1) IP="192.168.1.10"; ROLE="car"; REMOTE_IP="192.168.1.20" ;;
29-
2) IP="192.168.1.20"; ROLE="base"; REMOTE_IP="192.168.1.10" ;;
28+
1) IP="10.71.1.10"; ROLE="car"; REMOTE_IP="10.71.1.20" ;;
29+
2) IP="10.71.1.20"; ROLE="base"; REMOTE_IP="10.71.1.10" ;;
3030
*) echo -e "${RED}Invalid choice. Enter 1 or 2.${NC}"; exit 1 ;;
3131
esac
3232

@@ -77,9 +77,9 @@ echo -e "${GREEN}=========================================${NC}"
7777
echo -e "${GREEN} Static IP set — next steps:${NC}"
7878
echo -e "${GREEN}=========================================${NC}"
7979
if [[ "$ROLE" == "car" ]]; then
80-
echo -e " Ping base RPi: ping -c 4 192.168.1.20"
80+
echo -e " Ping base RPi: ping -c 4 10.71.1.20"
8181
else
82-
echo -e " Ping car RPi: ping -c 4 192.168.1.10"
82+
echo -e " Ping car RPi: ping -c 4 10.71.1.10"
8383
fi
8484
echo -e " IP and deploy/.env persist across reboots and git resets."
8585
echo -e "${GREEN}=========================================${NC}"
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
#!/usr/bin/env bash
2+
# setup.sh — Full first-time setup for the car/base RPi
3+
#
4+
# Covers: system packages, GStreamer/gi bindings, CAN kernel modules,
5+
# MCP2517FD device tree overlay (20 MHz crystal), can0 boot service,
6+
# uv install, Python venv, car-telemetry systemd service,
7+
# static IP on eth0, WiFi routing priority, Tailscale, NoMachine.
8+
#
9+
# Usage: sudo ./setup.sh [--car | --base]
10+
# --car non-interactive car setup (10.71.1.10, remote 10.71.1.20)
11+
# --base non-interactive base setup (10.71.1.20, remote 10.71.1.10)
12+
# (omit flag for interactive role prompt)
13+
14+
set -euo pipefail
15+
16+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; NC='\033[0m'
17+
18+
ok() { echo -e "${GREEN}$*${NC}"; }
19+
warn() { echo -e "${YELLOW} $*${NC}"; }
20+
die() { echo -e "${RED}$*${NC}"; exit 1; }
21+
hdr() { echo -e "\n${BOLD}── $* ──${NC}"; }
22+
23+
if [[ $EUID -ne 0 ]]; then
24+
die "Run as root: sudo $0 $*"
25+
fi
26+
27+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28+
29+
# ── Role selection ────────────────────────────────────────────────────────────
30+
ROLE=""
31+
for arg in "$@"; do
32+
case "$arg" in
33+
--car) ROLE=car ;;
34+
--base) ROLE=base ;;
35+
esac
36+
done
37+
38+
if [[ -z "$ROLE" ]]; then
39+
echo -e "${YELLOW}Which RPi is this?${NC}"
40+
echo " 1) Car — 10.71.1.10 (remote: 10.71.1.20)"
41+
echo " 2) Base — 10.71.1.20 (remote: 10.71.1.10)"
42+
echo ""
43+
read -rp "Enter 1 or 2: " choice
44+
case "$choice" in
45+
1) ROLE=car ;;
46+
2) ROLE=base ;;
47+
*) die "Invalid choice." ;;
48+
esac
49+
fi
50+
51+
if [[ "$ROLE" == "car" ]]; then
52+
LOCAL_IP="10.71.1.10"
53+
REMOTE_IP="10.71.1.20"
54+
else
55+
LOCAL_IP="10.71.1.20"
56+
REMOTE_IP="10.71.1.10"
57+
fi
58+
59+
# Derive the real user who will run the service (the one who called sudo)
60+
SERVICE_USER="${SUDO_USER:-$(logname 2>/dev/null || echo car)}"
61+
SERVICE_HOME="$(getent passwd "$SERVICE_USER" | cut -d: -f6)"
62+
UV="$SERVICE_HOME/.local/bin/uv"
63+
64+
echo ""
65+
echo -e "${BOLD}Role:${NC} $ROLE"
66+
echo -e "${BOLD}Local IP:${NC} $LOCAL_IP"
67+
echo -e "${BOLD}Remote IP:${NC} $REMOTE_IP"
68+
echo -e "${BOLD}Service user:${NC} $SERVICE_USER ($SERVICE_HOME)"
69+
echo ""
70+
71+
# ── 1. System packages ────────────────────────────────────────────────────────
72+
hdr "System packages"
73+
apt-get update -qq
74+
apt-get install -y \
75+
can-utils \
76+
python3-gi \
77+
python3-gst-1.0 \
78+
gstreamer1.0-tools \
79+
gstreamer1.0-plugins-base \
80+
gstreamer1.0-plugins-good \
81+
curl
82+
ok "System packages installed"
83+
84+
# ── 2. CAN kernel modules ─────────────────────────────────────────────────────
85+
hdr "CAN kernel modules"
86+
for mod in can can_raw mcp251xfd; do
87+
modprobe "$mod"
88+
if ! grep -qx "$mod" /etc/modules 2>/dev/null; then
89+
echo "$mod" >> /etc/modules
90+
fi
91+
done
92+
ok "Modules loaded and persisted in /etc/modules"
93+
94+
# ── 3. MCP2517FD device tree overlay (20 MHz crystal) ────────────────────────
95+
hdr "Boot config — MCP2517FD overlay"
96+
CONFIG=/boot/firmware/config.txt
97+
for line in \
98+
"dtoverlay=mcp251xfd,oscillator=20000000,interrupt=25" \
99+
"dtoverlay=spi-bcm2835"
100+
do
101+
if grep -qF "$line" "$CONFIG" 2>/dev/null; then
102+
warn "Already present: $line"
103+
else
104+
echo "$line" >> "$CONFIG"
105+
ok "Added: $line"
106+
fi
107+
done
108+
109+
# ── 4. can0 boot service ──────────────────────────────────────────────────────
110+
hdr "can0 systemd service"
111+
cat > /etc/systemd/system/can0.service <<'EOF'
112+
[Unit]
113+
Description=CAN bus interface can0
114+
After=network.target
115+
116+
[Service]
117+
Type=oneshot
118+
ExecStart=/sbin/ip link set can0 up type can bitrate 500000
119+
RemainAfterExit=yes
120+
121+
[Install]
122+
WantedBy=multi-user.target
123+
EOF
124+
systemctl daemon-reload
125+
systemctl enable can0
126+
ok "can0.service installed and enabled"
127+
128+
if ip link set can0 up type can bitrate 500000 2>/dev/null; then
129+
ok "can0 is UP at 500 kbps"
130+
else
131+
warn "can0 not yet available — overlay activates after reboot"
132+
fi
133+
134+
# ── 5. Install uv ─────────────────────────────────────────────────────────────
135+
hdr "uv"
136+
if [[ -x "$UV" ]]; then
137+
ok "uv already installed at $UV"
138+
else
139+
sudo -u "$SERVICE_USER" env HOME="$SERVICE_HOME" \
140+
sh -c 'curl -LsSf https://astral.sh/uv/install.sh | sh'
141+
ok "uv installed at $UV"
142+
fi
143+
144+
# ── 6. Python venv + dependencies ────────────────────────────────────────────
145+
hdr "Python venv (system-site-packages for gi/GStreamer)"
146+
cd "$SCRIPT_DIR"
147+
sudo -u "$SERVICE_USER" env HOME="$SERVICE_HOME" \
148+
"$UV" venv --system-site-packages --clear .venv
149+
sudo -u "$SERVICE_USER" env HOME="$SERVICE_HOME" \
150+
"$UV" sync
151+
ok "venv ready at $SCRIPT_DIR/.venv"
152+
153+
# ── 7. car-telemetry systemd service ─────────────────────────────────────────
154+
hdr "car-telemetry systemd service"
155+
SERVICE_SRC="$SCRIPT_DIR/deploy/car-telemetry.service"
156+
GIT_HASH=$(git -C "$SCRIPT_DIR" rev-parse --short HEAD 2>/dev/null || echo unknown)
157+
158+
sed \
159+
-e "s|User=.*|User=$SERVICE_USER|" \
160+
-e "s|WorkingDirectory=.*|WorkingDirectory=$SCRIPT_DIR|" \
161+
-e "s|ExecStart=.*uv run|ExecStart=$UV run|" \
162+
-e "s|REMOTE_IP=.*|REMOTE_IP=$REMOTE_IP|" \
163+
-e "s|GIT_HASH=.*|GIT_HASH=$GIT_HASH|" \
164+
"$SERVICE_SRC" > /etc/systemd/system/car-telemetry.service
165+
166+
systemctl daemon-reload
167+
systemctl enable car-telemetry
168+
ok "car-telemetry.service installed and enabled (hash=$GIT_HASH, remote=$REMOTE_IP)"
169+
170+
if [[ "$ROLE" == "car" ]]; then
171+
systemctl restart car-telemetry
172+
ok "car-telemetry started"
173+
else
174+
warn "Base role — skipping car-telemetry start"
175+
fi
176+
177+
# ── 8. Static IP on eth0 + WiFi routing priority ─────────────────────────────
178+
# eth0 gets a high metric (low priority for default route) so WiFi remains the
179+
# default gateway for internet traffic even when the radio link is connected.
180+
# eth0 is still reachable at 10.71.1.x for the telemetry link.
181+
hdr "Static IP — eth0 (metric 200, WiFi stays default route)"
182+
NETPLAN_FILE=/etc/netplan/10-eth0-static.yaml
183+
cat > "$NETPLAN_FILE" <<EOF
184+
network:
185+
version: 2
186+
ethernets:
187+
eth0:
188+
addresses:
189+
- ${LOCAL_IP}/24
190+
routes:
191+
- to: 10.71.1.0/24
192+
via: 0.0.0.0
193+
metric: 200
194+
on-link: true
195+
dhcp4: false
196+
dhcp6: false
197+
EOF
198+
chmod 600 "$NETPLAN_FILE"
199+
ok "Written $NETPLAN_FILE"
200+
201+
# WiFi remains the default route automatically — eth0's netplan has no gateway,
202+
# so it only adds a host route to 10.71.1.0/24 and never competes for default.
203+
204+
echo -e "${YELLOW}Applying netplan...${NC}"
205+
netplan apply
206+
ok "netplan applied"
207+
208+
ASSIGNED=$(ip addr show eth0 2>/dev/null | grep -oP "inet \K[0-9.]+" || echo "")
209+
if [[ "$ASSIGNED" == "$LOCAL_IP" ]]; then
210+
ok "eth0 is now ${LOCAL_IP}/24"
211+
else
212+
warn "eth0 shows '${ASSIGNED:-none}' — expected '${LOCAL_IP}' (cable may not be plugged in yet)"
213+
fi
214+
215+
# ── 9. Tailscale ─────────────────────────────────────────────────────────────
216+
hdr "Tailscale"
217+
if command -v tailscale &>/dev/null; then
218+
ok "Tailscale already installed ($(tailscale version | head -1))"
219+
else
220+
curl -fsSL https://tailscale.com/install.sh | sh
221+
ok "Tailscale installed"
222+
fi
223+
systemctl enable --now tailscaled
224+
warn "Run 'sudo tailscale up' to authenticate this node"
225+
226+
# ── 10. NoMachine ─────────────────────────────────────────────────────────────
227+
hdr "NoMachine"
228+
if command -v nxserver &>/dev/null || dpkg -l nomachine &>/dev/null 2>&1; then
229+
ok "NoMachine already installed"
230+
else
231+
NX_URL="https://web9001.nomachine.com/download/9.4/Raspberry/nomachine_9.4.14_1_arm64.deb"
232+
NX_DEB="/tmp/nomachine.deb"
233+
echo " Downloading NoMachine for $ARCH..."
234+
curl -L "$NX_URL" -o "$NX_DEB"
235+
dpkg -i "$NX_DEB"
236+
rm -f "$NX_DEB"
237+
ok "NoMachine installed"
238+
fi
239+
240+
# ── Done ──────────────────────────────────────────────────────────────────────
241+
echo ""
242+
echo -e "${GREEN}${BOLD}=========================================${NC}"
243+
echo -e "${GREEN}${BOLD} Setup complete${NC}"
244+
echo -e "${GREEN}${BOLD}=========================================${NC}"
245+
echo -e " Role: $ROLE"
246+
echo -e " This Pi: ${LOCAL_IP}/24 (eth0)"
247+
echo -e " Remote: $REMOTE_IP"
248+
echo -e " WiFi: default route (eth0 has no gateway)"
249+
echo -e " Git hash: $GIT_HASH"
250+
echo ""
251+
echo -e " ${YELLOW}Next steps:${NC}"
252+
echo -e " sudo tailscale up # authenticate Tailscale"
253+
echo -e " sudo reboot # activates MCP2517FD overlay"
254+
echo ""
255+
echo -e " After reboot:"
256+
echo -e " ip link show can0"
257+
echo -e " systemctl status car-telemetry"
258+
echo -e " journalctl -u car-telemetry -f"
259+
echo -e "${GREEN}${BOLD}=========================================${NC}"

0 commit comments

Comments
 (0)