204 lines
8.5 KiB
Bash
Executable File
204 lines
8.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# deploy.sh — sync this project to the LattePanda and build/run it there.
|
|
# The LattePanda is the machine wired to the gimbal + camera, so the actual
|
|
# cmake build (and the live run) happen remotely over ssh. We edit locally and
|
|
# mirror. Mirrors ../firmware/deploy.sh, but for the CMake/C++ side.
|
|
#
|
|
# Usage:
|
|
# ./deploy.sh rsync + remote configure + build
|
|
# ./deploy.sh --run ... then run the app over ssh (RUN_ARGS, ctrl-c to stop)
|
|
# ./deploy.sh --clean wipe the remote build dir first (fresh configure)
|
|
# ./deploy.sh --check-deps only check the remote build dependencies
|
|
# ./deploy.sh --run --force skip the placeholder-config guard before running
|
|
#
|
|
# Flags combine, e.g. ./deploy.sh --clean --run
|
|
#
|
|
# Config comes from .deploy.env (see .deploy.env.example).
|
|
#
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
ENV_FILE=".deploy.env"
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
echo "error: $ENV_FILE not found (copy .deploy.env.example and edit it)" >&2
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
|
|
: "${REMOTE_HOST:?set REMOTE_HOST in .deploy.env}"
|
|
: "${REMOTE_DIR:?set REMOTE_DIR in .deploy.env}"
|
|
CMAKE_ARGS="${CMAKE_ARGS:--DWITH_MQTT=ON -DWITH_VIMBA=ON}"
|
|
BUILD_JOBS="${BUILD_JOBS:-}"
|
|
RUN_ARGS="${RUN_ARGS:---start}"
|
|
|
|
CLEAN=0
|
|
RUN=0
|
|
CHECK_DEPS=0
|
|
FORCE=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--clean) CLEAN=1 ;;
|
|
--run|--start) RUN=1 ;;
|
|
--check-deps) CHECK_DEPS=1 ;;
|
|
--force) FORCE=1 ;;
|
|
*) echo "unknown option: $arg" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# Locate cmake on the remote. Non-interactive ssh shells don't source .bashrc,
|
|
# so allow an explicit REMOTE_CMAKE override in .deploy.env; otherwise search
|
|
# the usual locations.
|
|
RESOLVE_CMAKE="
|
|
CMAKE='${REMOTE_CMAKE:-}'
|
|
if [ -z \"\$CMAKE\" ]; then
|
|
for c in \"\$(command -v cmake 2>/dev/null)\" /usr/bin/cmake /usr/local/bin/cmake; do
|
|
if [ -x \"\$c\" ]; then CMAKE=\"\$c\"; break; fi
|
|
done
|
|
fi
|
|
if [ -z \"\$CMAKE\" ]; then
|
|
echo 'error: cmake not found on remote; set REMOTE_CMAKE in .deploy.env' >&2
|
|
exit 127
|
|
fi
|
|
"
|
|
|
|
# Vimba X needs the GenTL transport-layer dir on GENICAM_GENTL64_PATH at runtime,
|
|
# or VmbStartup() fails with "Could not start Vimba X API". The SDK installs that
|
|
# via /etc/profile.d, which non-interactive ssh shells don't source — so set it
|
|
# ourselves before launching. Optional VIMBA_CTI_PATH override in .deploy.env.
|
|
RESOLVE_VIMBA_ENV="
|
|
for s in /etc/profile.d/*Vimba*GenTL*.sh; do
|
|
[ -f \"\$s\" ] && . \"\$s\"
|
|
done
|
|
if [ -z \"\${GENICAM_GENTL64_PATH:-}\" ]; then
|
|
for d in '${VIMBA_CTI_PATH:-}' /opt/VimbaX/cti /opt/VimbaX_*/cti; do
|
|
if [ -d \"\$d\" ]; then export GENICAM_GENTL64_PATH=\"\$d\"; break; fi
|
|
done
|
|
fi
|
|
"
|
|
|
|
# Preflight: the two heavyweight deps that aren't auto-fetched (Paho is). OpenCV
|
|
# and Boost.program_options must be present or the configure step fails opaquely.
|
|
CHECK_DEPS_CMD="
|
|
miss=''
|
|
pkg-config --exists opencv4 2>/dev/null || miss=\"\$miss opencv4\"
|
|
pkg-config --exists libjxl libjxl_threads 2>/dev/null || miss=\"\$miss libjxl\"
|
|
[ -f /usr/include/boost/version.hpp ] || ls /usr/include/boost*/boost/version.hpp >/dev/null 2>&1 || miss=\"\$miss boost\"
|
|
if [ -n \"\$miss\" ]; then
|
|
echo \"error: missing remote build deps:\$miss\" >&2
|
|
echo 'install on the LattePanda (Ubuntu):' >&2
|
|
echo ' sudo apt-get install -y build-essential cmake git libopencv-dev libboost-program-options-dev libjxl-dev' >&2
|
|
exit 1
|
|
fi
|
|
echo 'remote deps OK (OpenCV, Boost, libjxl; Vimba via cmake/FindVmb; Paho fetched)'
|
|
"
|
|
|
|
if [[ "$CHECK_DEPS" == "1" ]]; then
|
|
echo ">> checking remote build deps on $REMOTE_HOST"
|
|
# shellcheck disable=SC2029
|
|
ssh "$REMOTE_HOST" "$CHECK_DEPS_CMD"
|
|
exit 0
|
|
fi
|
|
|
|
# .git is excluded from rsync, so capture build identity locally and pass it on.
|
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
|
GIT_REV="$(git describe --always --dirty 2>/dev/null || echo nogit)"
|
|
else
|
|
GIT_REV="nogit"
|
|
fi
|
|
echo ">> build id: $GIT_REV"
|
|
|
|
echo ">> ensuring remote dir exists"
|
|
ssh "$REMOTE_HOST" "mkdir -p '$REMOTE_DIR'"
|
|
|
|
# config.ini and images/ are device-local (runtime config + captured output) and
|
|
# are deliberately NOT mirrored, so a deploy never clobbers them. build/ is
|
|
# machine-specific and regenerated remotely.
|
|
echo ">> syncing to $REMOTE_HOST:$REMOTE_DIR"
|
|
rsync -avz --delete \
|
|
--exclude 'build/' \
|
|
--exclude '.git' \
|
|
--exclude '.venv' \
|
|
--exclude '.vscode' \
|
|
--exclude '__pycache__' \
|
|
--exclude '.deploy.env' \
|
|
--exclude 'config.ini' \
|
|
--exclude 'images/' \
|
|
./ "$REMOTE_HOST:$REMOTE_DIR/"
|
|
|
|
# Seed a config.ini on the device from the example if it doesn't have one yet,
|
|
# so the first run finds a config. Never overwrites an existing device config.
|
|
echo ">> ensuring remote config.ini exists"
|
|
ssh "$REMOTE_HOST" "cd '$REMOTE_DIR' && \
|
|
if [ ! -f config.ini ]; then \
|
|
cp config/config.example.ini config.ini && \
|
|
echo ' seeded config.ini from config/config.example.ini — edit it on the device'; \
|
|
else echo ' keeping existing device config.ini'; fi"
|
|
|
|
if [[ "$CLEAN" == "1" ]]; then
|
|
echo ">> wiping remote build dir"
|
|
ssh "$REMOTE_HOST" "rm -rf '$REMOTE_DIR/build'"
|
|
fi
|
|
|
|
echo ">> remote preflight + configure + build ($CMAKE_ARGS)"
|
|
JOBS_FLAG=""
|
|
[[ -n "$BUILD_JOBS" ]] && JOBS_FLAG="-j$BUILD_JOBS" || JOBS_FLAG="-j\$(nproc)"
|
|
# shellcheck disable=SC2029
|
|
ssh "$REMOTE_HOST" "$RESOLVE_CMAKE $CHECK_DEPS_CMD cd '$REMOTE_DIR' && \
|
|
GIT_REV='$GIT_REV' \"\$CMAKE\" -S . -B build $CMAKE_ARGS && \
|
|
\"\$CMAKE\" --build build $JOBS_FLAG"
|
|
echo ">> build complete: $REMOTE_DIR/build/fire_gimbal_control"
|
|
|
|
if [[ "$RUN" == "1" ]]; then
|
|
# Guard: a freshly seeded config.ini is the example template, with placeholders
|
|
# that connect nowhere and silently drop to degraded mode. Refuse to launch on
|
|
# placeholders (unless --force) so it's an explicit failure, not a confusing
|
|
# half-run. Only enforce placeholders for subsystems this run actually uses,
|
|
# mirroring Application.cpp's resolution: MQTT off if --no-mqtt, a WITH_MQTT=OFF
|
|
# build, OR [Features] enable_mqtt is falsey; camera mocked if --mock-camera OR
|
|
# [Features] mock_camera true / enable_camera false. CLI/build signals are
|
|
# computed here; the config's own flags are parsed remotely where the file is.
|
|
if [[ "$FORCE" != "1" ]]; then
|
|
MQTT_OFF_CLI=0; CAM_MOCK_CLI=0
|
|
[[ "$RUN_ARGS" == *--no-mqtt* || "$CMAKE_ARGS" == *WITH_MQTT=OFF* ]] && MQTT_OFF_CLI=1
|
|
[[ "$RUN_ARGS" == *--mock-camera* ]] && CAM_MOCK_CLI=1
|
|
echo ">> checking device config.ini for unedited placeholders"
|
|
# shellcheck disable=SC2029
|
|
if ! ssh "$REMOTE_HOST" "
|
|
cfg='$REMOTE_DIR/config.ini'
|
|
[ -f \"\$cfg\" ] || { echo \"error: \$cfg missing on device\" >&2; exit 2; }
|
|
# Read an INI value (last assignment wins), strip inline ';' comment + ws, lowercase.
|
|
ini_val() { grep -iE \"^[[:space:]]*\$1[[:space:]]*=\" \"\$cfg\" | tail -1 \
|
|
| sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]*(;.*)?\$//' | tr 'A-Z' 'a-z'; }
|
|
is_false() { case \"\$(ini_val \"\$1\")\" in 0|false|no|off) return 0;; *) return 1;; esac; }
|
|
is_true() { case \"\$(ini_val \"\$1\")\" in 1|true|yes|on) return 0;; *) return 1;; esac; }
|
|
|
|
mqtt_on=1; [ '$MQTT_OFF_CLI' = 1 ] && mqtt_on=0; is_false enable_mqtt && mqtt_on=0
|
|
cam_real=1; [ '$CAM_MOCK_CLI' = 1 ] && cam_real=0
|
|
is_true mock_camera && cam_real=0; is_false enable_camera && cam_real=0
|
|
|
|
pat='ExampleTower' # tower identity: always
|
|
[ \"\$mqtt_on\" = 1 ] && pat=\"\$pat|CHANGE_ME\" # MQTT creds: only if MQTT on
|
|
[ \"\$cam_real\" = 1 ] && pat=\"\$pat|DEV_0000000000\" # camera id: only if real cam
|
|
|
|
if grep -Eq \"\$pat\" \"\$cfg\"; then
|
|
echo 'error: device config.ini still has example placeholders:' >&2
|
|
grep -En \"\$pat\" \"\$cfg\" | sed 's/^/ /' >&2
|
|
echo 'edit them on the device, or pass --force to run anyway.' >&2
|
|
exit 3
|
|
fi
|
|
"; then
|
|
echo ">> aborting run; fix the config or re-run with --force" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo ">> running on $REMOTE_HOST (ctrl-c to stop)"
|
|
# -t for a real tty so the capture loop's logs stream and ctrl-c propagates.
|
|
# cwd = REMOTE_DIR so ./config.ini and the output dir resolve.
|
|
# shellcheck disable=SC2029
|
|
ssh -t "$REMOTE_HOST" "$RESOLVE_VIMBA_ENV cd '$REMOTE_DIR' && exec ./build/fire_gimbal_control $RUN_ARGS"
|
|
fi
|