209 lines
5.9 KiB
Bash
209 lines
5.9 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
timestamp() {
|
|
date '+%Y-%m-%d %H:%M:%S'
|
|
}
|
|
|
|
log() {
|
|
printf '[%s] %s\n' "$(timestamp)" "$*"
|
|
}
|
|
|
|
fail() {
|
|
log "ERROR: $*"
|
|
exit 1
|
|
}
|
|
|
|
report_err() {
|
|
local exit_code=$?
|
|
local line_no=${1:-unknown}
|
|
local cmd=${2:-unknown}
|
|
log "ERROR: command failed at line ${line_no}: ${cmd} (exit=${exit_code})"
|
|
exit "$exit_code"
|
|
}
|
|
|
|
trap 'report_err "${LINENO}" "${BASH_COMMAND}"' ERR
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DEFAULT_APP_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
|
|
RUN_USER="${RUN_USER:-$(id -un)}"
|
|
APP_DIR="${APP_DIR:-${DEFAULT_APP_DIR}}"
|
|
LOG_DIR="${LOG_DIR:-${APP_DIR}/logs}"
|
|
HOTSPOT_SSID="${HOTSPOT_SSID:-}"
|
|
HOTSPOT_PASSWORD="${HOTSPOT_PASSWORD:-}"
|
|
SERVER_IP="${SERVER_IP:-10.0.0.5}"
|
|
SERVER_PORT="${SERVER_PORT:-9000}"
|
|
ROS_SETUP="${ROS_SETUP:-/home/${RUN_USER}/ros2ws/install/setup.bash}"
|
|
PYTHON_BIN="${PYTHON_BIN:-python3}"
|
|
WIFI_RETRY_INTERVAL="${WIFI_RETRY_INTERVAL:-5}"
|
|
PING_RETRY_INTERVAL="${PING_RETRY_INTERVAL:-5}"
|
|
VENV_ACTIVATE="${APP_DIR}/.venv/bin/activate"
|
|
LOG_RETENTION_COUNT="${LOG_RETENTION_COUNT:-20}"
|
|
|
|
mkdir -p "${LOG_DIR}"
|
|
LOG_FILE="${LOG_DIR}/monitor_sender_$(date '+%Y%m%d_%H%M%S').log"
|
|
touch "${LOG_FILE}"
|
|
exec > >(tee -a "${LOG_FILE}") 2>&1
|
|
|
|
require_command() {
|
|
local command_name=$1
|
|
command -v "${command_name}" >/dev/null 2>&1 || fail "missing required command: ${command_name}"
|
|
}
|
|
|
|
prune_old_logs() {
|
|
local prune_from
|
|
local old_logs=()
|
|
|
|
[[ "${LOG_RETENTION_COUNT}" =~ ^[0-9]+$ ]] || fail "LOG_RETENTION_COUNT must be a positive integer"
|
|
(( LOG_RETENTION_COUNT >= 1 )) || fail "LOG_RETENTION_COUNT must be at least 1"
|
|
|
|
prune_from=$((LOG_RETENTION_COUNT + 1))
|
|
mapfile -t old_logs < <(ls -1dt "${LOG_DIR}"/monitor_sender_*.log 2>/dev/null | tail -n +"${prune_from}")
|
|
|
|
if ((${#old_logs[@]} > 0)); then
|
|
rm -f -- "${old_logs[@]}"
|
|
log "Pruned ${#old_logs[@]} old log file(s); keeping latest ${LOG_RETENTION_COUNT}"
|
|
fi
|
|
}
|
|
|
|
source_with_relaxed_mode() {
|
|
local target_file=$1
|
|
local shell_flags=$-
|
|
local err_trap
|
|
local source_status=0
|
|
|
|
err_trap="$(trap -p ERR || true)"
|
|
trap - ERR
|
|
set +eu
|
|
|
|
# shellcheck disable=SC1090,SC1091
|
|
source "${target_file}"
|
|
source_status=$?
|
|
|
|
[[ "${shell_flags}" == *e* ]] && set -e || set +e
|
|
[[ "${shell_flags}" == *u* ]] && set -u || set +u
|
|
|
|
if [[ -n "${err_trap}" ]]; then
|
|
eval "${err_trap}"
|
|
fi
|
|
|
|
return "${source_status}"
|
|
}
|
|
|
|
get_wifi_device() {
|
|
nmcli -t -f DEVICE,TYPE device status | awk -F: '$2 == "wifi" { print $1; exit }'
|
|
}
|
|
|
|
get_active_connection_name() {
|
|
local wifi_device=$1
|
|
nmcli -g GENERAL.CONNECTION device show "${wifi_device}" 2>/dev/null | head -n1 | tr -d '\r'
|
|
}
|
|
|
|
get_active_ssid() {
|
|
local wifi_device=$1
|
|
local active_connection
|
|
|
|
active_connection="$(get_active_connection_name "${wifi_device}" || true)"
|
|
if [[ -z "${active_connection}" || "${active_connection}" == "--" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
nmcli -g 802-11-wireless.ssid connection show "${active_connection}" 2>/dev/null | head -n1 | tr -d '\r'
|
|
}
|
|
|
|
is_hotspot_connected() {
|
|
local wifi_device=$1
|
|
local active_ssid
|
|
|
|
active_ssid="$(get_active_ssid "${wifi_device}" || true)"
|
|
[[ -n "${active_ssid}" && "${active_ssid}" == "${HOTSPOT_SSID}" ]]
|
|
}
|
|
|
|
connect_hotspot() {
|
|
local wifi_device=$1
|
|
|
|
while true; do
|
|
if is_hotspot_connected "${wifi_device}"; then
|
|
log "Wi-Fi already connected to hotspot '${HOTSPOT_SSID}'"
|
|
return 0
|
|
fi
|
|
|
|
log "Trying hotspot '${HOTSPOT_SSID}' on Wi-Fi device '${wifi_device}'"
|
|
nmcli device wifi rescan ifname "${wifi_device}" >/dev/null 2>&1 || true
|
|
|
|
if nmcli connection up "${HOTSPOT_SSID}" ifname "${wifi_device}" >/dev/null 2>&1; then
|
|
sleep 2
|
|
if is_hotspot_connected "${wifi_device}"; then
|
|
log "Connected to hotspot '${HOTSPOT_SSID}' via saved NetworkManager profile"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${HOTSPOT_PASSWORD}" ]]; then
|
|
if nmcli device wifi connect "${HOTSPOT_SSID}" password "${HOTSPOT_PASSWORD}" ifname "${wifi_device}" >/dev/null 2>&1; then
|
|
sleep 2
|
|
if is_hotspot_connected "${wifi_device}"; then
|
|
log "Connected to hotspot '${HOTSPOT_SSID}' via direct nmcli connect"
|
|
return 0
|
|
fi
|
|
fi
|
|
else
|
|
log "HOTSPOT_PASSWORD is empty; skipping direct nmcli connect and relying on saved profile"
|
|
fi
|
|
|
|
log "Hotspot '${HOTSPOT_SSID}' is not ready yet; retrying in ${WIFI_RETRY_INTERVAL}s"
|
|
sleep "${WIFI_RETRY_INTERVAL}"
|
|
done
|
|
}
|
|
|
|
wait_for_server() {
|
|
while true; do
|
|
if ping -c 1 -W 2 "${SERVER_IP}" >/dev/null 2>&1; then
|
|
log "Server '${SERVER_IP}' is reachable"
|
|
return 0
|
|
fi
|
|
|
|
log "Server '${SERVER_IP}' is not reachable yet; retrying in ${PING_RETRY_INTERVAL}s"
|
|
sleep "${PING_RETRY_INTERVAL}"
|
|
done
|
|
}
|
|
|
|
log "Starting xMonitor sender bootstrap"
|
|
log "Using APP_DIR='${APP_DIR}'"
|
|
log "Log file: ${LOG_FILE}"
|
|
prune_old_logs
|
|
|
|
[[ -n "${HOTSPOT_SSID}" ]] || fail "HOTSPOT_SSID must be set in /etc/xmonitor/xmonitor.env"
|
|
[[ -d "${APP_DIR}" ]] || fail "APP_DIR does not exist: ${APP_DIR}"
|
|
[[ -f "${APP_DIR}/monitor_sender.py" ]] || fail "monitor_sender.py not found in ${APP_DIR}"
|
|
[[ -f "${ROS_SETUP}" ]] || fail "ROS setup file not found: ${ROS_SETUP}"
|
|
[[ -f "${VENV_ACTIVATE}" ]] || fail "Python virtualenv activate script not found: ${VENV_ACTIVATE}"
|
|
|
|
require_command nmcli
|
|
require_command ping
|
|
require_command tee
|
|
require_command "${PYTHON_BIN}"
|
|
|
|
if [[ "$(id -un)" != "${RUN_USER}" ]]; then
|
|
log "Warning: script is running as '$(id -un)' but RUN_USER is '${RUN_USER}'"
|
|
fi
|
|
|
|
WIFI_DEVICE="$(get_wifi_device)"
|
|
[[ -n "${WIFI_DEVICE}" ]] || fail "No Wi-Fi device was found by NetworkManager"
|
|
|
|
log "Detected Wi-Fi device '${WIFI_DEVICE}'"
|
|
connect_hotspot "${WIFI_DEVICE}"
|
|
wait_for_server
|
|
|
|
log "Sourcing ROS environment '${ROS_SETUP}'"
|
|
source_with_relaxed_mode "${ROS_SETUP}"
|
|
|
|
log "Activating Python virtualenv '${VENV_ACTIVATE}'"
|
|
source_with_relaxed_mode "${VENV_ACTIVATE}"
|
|
|
|
cd "${APP_DIR}"
|
|
log "Launching monitor_sender.py --ip ${SERVER_IP} --port ${SERVER_PORT}"
|
|
exec "${PYTHON_BIN}" monitor_sender.py --ip "${SERVER_IP}" --port "${SERVER_PORT}"
|