Implements full boot-time auto-start for the SaltyBot ROS2 stack on Jetson Orin. Everything comes up automatically after power-on with correct dependency ordering and restart-on-failure for each service. New systemd services: saltybot-ros2.service full_stack.launch.py (perception + SLAM + Nav2) saltybot-esp32-serial.service ESP32-S3 BALANCE UART bridge (bd-wim1, PR #727) saltybot-here4.service Here4 DroneCAN GPS bridge (bd-p47c, PR #728) saltybot-dashboard.service Web dashboard on port 8080 Updated: saltybot.target now Wants all four new services with boot-order comments can-bringup.service bitrate 500 kbps → 1 Mbps (DroneCAN for Here4) 70-canable.rules remove bitrate from udev RUN+=; let service own the bitrate, add TAG+=systemd for device unit install_systemd.sh installs all services + udev rules, colcon build, enables mosquitto, usermod dialout full_stack.launch.py resolve 8 merge conflict markers (ESP32-S3 rename) and fix missing indent on enable_mission_logging_arg — file was un-launchable with SyntaxError New: scripts/ros2-launch.sh sources ROS2 Humble + workspace overlay, then exec ros2 launch — used by all ROS2 service units via ExecStart= udev/80-esp32.rules /dev/esp32-balance (CH343) and /dev/esp32-io (ESP32-S3 native USB CDC) Resolves bd-1hyn Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
195 lines
7.1 KiB
Bash
195 lines
7.1 KiB
Bash
#!/usr/bin/env bash
|
|
# install_systemd.sh — Deploy and enable saltybot systemd services on Orin
|
|
#
|
|
# Run as root: sudo ./systemd/install_systemd.sh
|
|
#
|
|
# What this does:
|
|
# 1. Deploy repo to /opt/saltybot/jetson
|
|
# 2. Build ROS2 workspace (colcon)
|
|
# 3. Install systemd unit files
|
|
# 4. Install udev rules (CAN, ESP32)
|
|
# 5. Enable and optionally start all services
|
|
#
|
|
# Services installed (start order):
|
|
# can-bringup.service CANable2 @ 1 Mbps DroneCAN (Here4 GPS)
|
|
# saltybot-esp32-serial.service ESP32-S3 BALANCE UART bridge (bd-wim1)
|
|
# saltybot-here4.service Here4 GPS DroneCAN bridge (bd-p47c)
|
|
# saltybot-ros2.service Full ROS2 stack (perception + nav)
|
|
# saltybot-dashboard.service Web dashboard on port 8080
|
|
# saltybot-social.service Social-bot stack (speech + LLM + face)
|
|
# tailscale-vpn.service Tailscale VPN for remote access
|
|
#
|
|
# Prerequisites:
|
|
# apt install ros-humble-desktop python3-colcon-common-extensions
|
|
# pip install dronecan (for Here4 GPS node)
|
|
# usermod -aG dialout orin (for serial port access)
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_DIR="$(dirname "${SCRIPT_DIR}")"
|
|
SYSTEMD_DIR="/etc/systemd/system"
|
|
DEPLOY_DIR="/opt/saltybot/jetson"
|
|
SCRIPTS_DIR="${DEPLOY_DIR}/scripts"
|
|
UDEV_DIR="/etc/udev/rules.d"
|
|
BRINGUP_SYSTEMD="${REPO_DIR}/ros2_ws/src/saltybot_bringup/systemd"
|
|
BRINGUP_UDEV="${REPO_DIR}/ros2_ws/src/saltybot_bringup/udev"
|
|
WS_SRC="${REPO_DIR}/ros2_ws/src"
|
|
WS_BUILD="${DEPLOY_DIR}/ros2_ws"
|
|
|
|
ROS_DISTRO="${ROS_DISTRO:-humble}"
|
|
ROS_SETUP="/opt/ros/${ROS_DISTRO}/setup.bash"
|
|
ORIN_USER="${SALTYBOT_USER:-orin}"
|
|
|
|
log() { echo "[install_systemd] $*"; }
|
|
warn() { echo "[install_systemd] WARN: $*" >&2; }
|
|
die() { echo "[install_systemd] ERROR: $*" >&2; exit 1; }
|
|
|
|
[[ "$(id -u)" == "0" ]] || die "Run as root (sudo $0)"
|
|
[[ -f "${ROS_SETUP}" ]] || die "ROS2 ${ROS_DISTRO} not found at ${ROS_SETUP}"
|
|
|
|
# ── 1. Deploy repo ─────────────────────────────────────────────────────────────
|
|
log "Deploying repo to ${DEPLOY_DIR}..."
|
|
mkdir -p "${DEPLOY_DIR}"
|
|
rsync -a \
|
|
--exclude='.git' \
|
|
--exclude='__pycache__' \
|
|
--exclude='*.pyc' \
|
|
--exclude='.pytest_cache' \
|
|
--exclude='build/' \
|
|
--exclude='install/' \
|
|
--exclude='log/' \
|
|
"${REPO_DIR}/" "${DEPLOY_DIR}/"
|
|
|
|
# Install launch wrapper script
|
|
log "Installing ros2-launch.sh..."
|
|
install -m 755 "${SCRIPT_DIR}/../scripts/ros2-launch.sh" "/opt/saltybot/scripts/ros2-launch.sh"
|
|
|
|
# ── 2. Build ROS2 workspace ────────────────────────────────────────────────────
|
|
if command -v colcon &>/dev/null; then
|
|
log "Building ROS2 workspace (colcon)..."
|
|
# shellcheck disable=SC1090
|
|
source "${ROS_SETUP}"
|
|
(
|
|
cd "${WS_BUILD}"
|
|
colcon build \
|
|
--symlink-install \
|
|
--cmake-args -DCMAKE_BUILD_TYPE=Release \
|
|
2>&1 | tee /tmp/colcon-build.log
|
|
)
|
|
log "Workspace build complete."
|
|
else
|
|
warn "colcon not found — skipping workspace build."
|
|
warn "Run manually: cd ${WS_BUILD} && colcon build --symlink-install"
|
|
fi
|
|
|
|
# ── 3. Install systemd units ───────────────────────────────────────────────────
|
|
log "Installing systemd units..."
|
|
|
|
# Units from jetson/systemd/
|
|
for unit in \
|
|
saltybot.target \
|
|
saltybot-ros2.service \
|
|
saltybot-esp32-serial.service \
|
|
saltybot-here4.service \
|
|
saltybot-dashboard.service \
|
|
saltybot-social.service \
|
|
tailscale-vpn.service
|
|
do
|
|
if [[ -f "${SCRIPT_DIR}/${unit}" ]]; then
|
|
cp "${SCRIPT_DIR}/${unit}" "${SYSTEMD_DIR}/"
|
|
log " Installed ${unit}"
|
|
else
|
|
warn " ${unit} not found in ${SCRIPT_DIR}/ — skipping"
|
|
fi
|
|
done
|
|
|
|
# Units from saltybot_bringup/systemd/
|
|
for unit in \
|
|
can-bringup.service \
|
|
chromium-kiosk.service \
|
|
magedok-display.service \
|
|
salty-face-server.service
|
|
do
|
|
if [[ -f "${BRINGUP_SYSTEMD}/${unit}" ]]; then
|
|
cp "${BRINGUP_SYSTEMD}/${unit}" "${SYSTEMD_DIR}/"
|
|
log " Installed ${unit} (from bringup)"
|
|
else
|
|
warn " ${unit} not found in bringup systemd/ — skipping"
|
|
fi
|
|
done
|
|
|
|
# ── 4. Install udev rules ──────────────────────────────────────────────────────
|
|
log "Installing udev rules..."
|
|
mkdir -p "${UDEV_DIR}"
|
|
|
|
for rule in \
|
|
70-canable.rules \
|
|
80-esp32.rules \
|
|
90-magedok-touch.rules
|
|
do
|
|
if [[ -f "${BRINGUP_UDEV}/${rule}" ]]; then
|
|
cp "${BRINGUP_UDEV}/${rule}" "${UDEV_DIR}/"
|
|
log " Installed ${rule}"
|
|
else
|
|
warn " ${rule} not found — skipping"
|
|
fi
|
|
done
|
|
|
|
udevadm control --reload
|
|
udevadm trigger --subsystem-match=net --action=add
|
|
udevadm trigger --subsystem-match=tty --action=add
|
|
log "udev rules reloaded."
|
|
|
|
# ── 5. Set permissions ─────────────────────────────────────────────────────────
|
|
log "Ensuring '${ORIN_USER}' is in dialout group (serial port access)..."
|
|
if id "${ORIN_USER}" &>/dev/null; then
|
|
usermod -aG dialout "${ORIN_USER}" || warn "usermod failed — add ${ORIN_USER} to dialout manually"
|
|
else
|
|
warn "User '${ORIN_USER}' not found — skip usermod"
|
|
fi
|
|
|
|
# ── 6. Reload systemd and enable services ─────────────────────────────────────
|
|
log "Reloading systemd daemon..."
|
|
systemctl daemon-reload
|
|
|
|
log "Enabling services..."
|
|
systemctl enable \
|
|
saltybot.target \
|
|
can-bringup.service \
|
|
saltybot-esp32-serial.service \
|
|
saltybot-here4.service \
|
|
saltybot-ros2.service \
|
|
saltybot-dashboard.service \
|
|
saltybot-social.service \
|
|
tailscale-vpn.service
|
|
|
|
# Enable mosquitto (MQTT broker) if installed
|
|
if systemctl list-unit-files mosquitto.service &>/dev/null; then
|
|
systemctl enable mosquitto.service
|
|
log " Enabled mosquitto.service"
|
|
fi
|
|
|
|
# ── 7. Summary ────────────────────────────────────────────────────────────────
|
|
log ""
|
|
log "Installation complete."
|
|
log ""
|
|
log "Services will start automatically on next reboot."
|
|
log "To start now without rebooting:"
|
|
log " sudo systemctl start saltybot.target"
|
|
log ""
|
|
log "Check status:"
|
|
log " systemctl status can-bringup saltybot-esp32-serial saltybot-here4 saltybot-ros2 saltybot-dashboard"
|
|
log ""
|
|
log "Live logs:"
|
|
log " journalctl -fu can-bringup"
|
|
log " journalctl -fu saltybot-esp32-serial"
|
|
log " journalctl -fu saltybot-here4"
|
|
log " journalctl -fu saltybot-ros2"
|
|
log " journalctl -fu saltybot-dashboard"
|
|
log ""
|
|
log "Dashboard: http://<orin-ip>:8080"
|
|
log "rosbridge: ws://<orin-ip>:9090"
|
|
log ""
|
|
log "Note: saltybot-esp32-serial and saltybot-here4 require packages"
|
|
log " from bd-wim1 (PR #727) and bd-p47c (PR #728) to be merged."
|