Files
Pulse/scripts/hot-dev.sh
2025-11-02 22:47:55 +00:00

324 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
load_env_file() {
local env_file=$1
if [[ -f ${env_file} ]]; then
printf "[hot-dev] Loading %s\n" "${env_file}"
set +u
set -a
# shellcheck disable=SC1090
source "${env_file}"
set +a
set -u
fi
}
load_env_file "${ROOT_DIR}/.env"
load_env_file "${ROOT_DIR}/.env.local"
load_env_file "${ROOT_DIR}/.env.dev"
FRONTEND_PORT=${FRONTEND_PORT:-${PORT:-7655}}
PORT=${PORT:-${FRONTEND_PORT}}
FRONTEND_DEV_HOST=${FRONTEND_DEV_HOST:-0.0.0.0}
FRONTEND_DEV_PORT=${FRONTEND_DEV_PORT:-${FRONTEND_PORT}}
PULSE_DEV_API_HOST=${PULSE_DEV_API_HOST:-127.0.0.1}
PULSE_DEV_API_PORT=${PULSE_DEV_API_PORT:-7656}
if [[ -z ${PULSE_DEV_API_URL:-} ]]; then
PULSE_DEV_API_URL="http://${PULSE_DEV_API_HOST}:${PULSE_DEV_API_PORT}"
fi
if [[ -z ${PULSE_DEV_WS_URL:-} ]]; then
if [[ ${PULSE_DEV_API_URL} == http://* ]]; then
PULSE_DEV_WS_URL="ws://${PULSE_DEV_API_URL#http://}"
elif [[ ${PULSE_DEV_API_URL} == https://* ]]; then
PULSE_DEV_WS_URL="wss://${PULSE_DEV_API_URL#https://}"
else
PULSE_DEV_WS_URL=${PULSE_DEV_API_URL}
fi
fi
export FRONTEND_PORT PORT
export FRONTEND_DEV_HOST FRONTEND_DEV_PORT
export PULSE_DEV_API_HOST PULSE_DEV_API_PORT PULSE_DEV_API_URL PULSE_DEV_WS_URL
# Auto-detect pulse-sensor-proxy socket if available
HOST_PROXY_SOCKET="/mnt/pulse-proxy/pulse-sensor-proxy.sock"
CONTAINER_PROXY_SOCKET="/run/pulse-sensor-proxy/pulse-sensor-proxy.sock"
if [[ -z ${PULSE_SENSOR_PROXY_SOCKET:-} ]]; then
if [[ -S "${HOST_PROXY_SOCKET}" ]]; then
export PULSE_SENSOR_PROXY_SOCKET="${HOST_PROXY_SOCKET}"
printf "[hot-dev] Detected pulse-sensor-proxy socket at %s\n" "${PULSE_SENSOR_PROXY_SOCKET}"
elif [[ -S "${CONTAINER_PROXY_SOCKET}" ]]; then
export PULSE_SENSOR_PROXY_SOCKET="${CONTAINER_PROXY_SOCKET}"
printf "[hot-dev] WARNING: Using container-local pulse-sensor-proxy socket at %s\n" "${PULSE_SENSOR_PROXY_SOCKET}"
printf "[hot-dev] WARNING: Host proxy is missing; temperatures will not reach Pulse until it is reinstalled.\n"
else
printf "[hot-dev] WARNING: No pulse-sensor-proxy socket detected. Temperatures will be unavailable.\n"
fi
else
if [[ ! -S "${PULSE_SENSOR_PROXY_SOCKET}" ]]; then
printf "[hot-dev] WARNING: Configured pulse-sensor-proxy socket not found at %s\n" "${PULSE_SENSOR_PROXY_SOCKET}"
elif [[ "${PULSE_SENSOR_PROXY_SOCKET}" == "${CONTAINER_PROXY_SOCKET}" && ! -S "${HOST_PROXY_SOCKET}" ]]; then
printf "[hot-dev] WARNING: Using container-local proxy socket; reinstall host pulse-sensor-proxy for real telemetry.\n"
fi
fi
EXTRA_CLEANUP_PORT=$((PULSE_DEV_API_PORT + 1))
cat <<BANNER
=========================================
Starting HOT-RELOAD development mode
=========================================
Frontend: http://${FRONTEND_DEV_HOST}:${FRONTEND_DEV_PORT} (with hot-reload)
Backend API: ${PULSE_DEV_API_URL}
Mock Mode: ${PULSE_MOCK_MODE:-false}
Toggle mock mode: npm run mock:on / npm run mock:off
Mock config: npm run mock:edit
Frontend: Edit files and see changes instantly!
Backend: Auto-rebuilds when .go files change!
Press Ctrl+C to stop
=========================================
BANNER
kill_port() {
local port=$1
printf "[hot-dev] Cleaning up port %s...\n" "${port}"
lsof -i :"${port}" 2>/dev/null | awk 'NR>1 {print $2}' | xargs -r kill -9 2>/dev/null || true
}
printf "[hot-dev] Cleaning up existing processes...\n"
# Don't stop ourselves if we're running under systemd
if [[ -z "${INVOCATION_ID:-}" ]]; then
# Not running under systemd, safe to stop the service
sudo systemctl stop pulse-hot-dev 2>/dev/null || true
fi
sudo systemctl stop pulse-backend 2>/dev/null || true
sudo systemctl stop pulse 2>/dev/null || true
sudo systemctl stop pulse-frontend 2>/dev/null || true
pkill -f "backend-watch.sh" 2>/dev/null || true
pkill -f vite 2>/dev/null || true
pkill -f "npm run dev" 2>/dev/null || true
pkill -f "npm exec" 2>/dev/null || true
pkill -x "pulse" 2>/dev/null || true
sleep 1
pkill -9 -x "pulse" 2>/dev/null || true
kill_port "${FRONTEND_DEV_PORT}"
kill_port "${PULSE_DEV_API_PORT}"
kill_port "${EXTRA_CLEANUP_PORT}"
sleep 3
# Temporarily disable pipefail for port checks (lsof returns 1 when port is free)
set +o pipefail
if lsof -i :"${FRONTEND_DEV_PORT}" 2>/dev/null | grep -q LISTEN; then
echo "ERROR: Port ${FRONTEND_DEV_PORT} is still in use after cleanup!"
kill_port "${FRONTEND_DEV_PORT}"
sleep 2
if lsof -i :"${FRONTEND_DEV_PORT}" 2>/dev/null | grep -q LISTEN; then
echo "FATAL: Cannot free port ${FRONTEND_DEV_PORT}. Please manually kill the process:"
lsof -i :"${FRONTEND_DEV_PORT}"
exit 1
fi
fi
if lsof -i :"${PULSE_DEV_API_PORT}" 2>/dev/null | grep -q LISTEN; then
echo "ERROR: Port ${PULSE_DEV_API_PORT} is still in use after cleanup!"
kill_port "${PULSE_DEV_API_PORT}"
sleep 2
if lsof -i :"${PULSE_DEV_API_PORT}" 2>/dev/null | grep -q LISTEN; then
echo "FATAL: Cannot free port ${PULSE_DEV_API_PORT}. Please manually kill the process:"
lsof -i :"${PULSE_DEV_API_PORT}"
exit 1
fi
fi
# Re-enable pipefail
set -o pipefail
echo "Ports are clean!"
if [[ -f "${ROOT_DIR}/mock.env" ]]; then
load_env_file "${ROOT_DIR}/mock.env"
# Load local overrides if they exist
if [[ -f "${ROOT_DIR}/mock.env.local" ]]; then
load_env_file "${ROOT_DIR}/mock.env.local"
echo "[hot-dev] Loaded mock.env.local overrides"
fi
if [[ ${PULSE_MOCK_MODE:-false} == "true" ]]; then
TOTAL_GUESTS=$((PULSE_MOCK_NODES * (PULSE_MOCK_VMS_PER_NODE + PULSE_MOCK_LXCS_PER_NODE)))
echo "Mock mode ENABLED with ${PULSE_MOCK_NODES} nodes (${TOTAL_GUESTS} total guests)"
else
# Sync production config when not in mock mode
echo "Syncing production configuration..."
"${ROOT_DIR}/scripts/sync-production-config.sh"
fi
fi
if [[ -f /etc/pulse/.env ]] && [[ -r /etc/pulse/.env ]]; then
set +u
# shellcheck disable=SC1091
source /etc/pulse/.env 2>/dev/null || true
set -u
echo "Auth configuration loaded from /etc/pulse/.env"
fi
printf "[hot-dev] Starting backend on port %s...\n" "${PULSE_DEV_API_PORT}"
cd "${ROOT_DIR}"
go build -o pulse ./cmd/pulse
# CRITICAL: Export all required environment variables for the backend
# Mock variables already exported via load_env_file (set -a)
# But we must explicitly export PULSE_DATA_DIR to ensure dev mode uses correct config
FRONTEND_PORT=${PULSE_DEV_API_PORT}
PORT=${PULSE_DEV_API_PORT}
export FRONTEND_PORT PULSE_DEV_API_PORT PORT
# Set data directory strategy for the backend
if [[ ${PULSE_MOCK_MODE:-false} == "true" ]]; then
export PULSE_DATA_DIR=/opt/pulse/tmp/mock-data
mkdir -p "$PULSE_DATA_DIR"
echo "[hot-dev] Mock mode: Using isolated data directory: ${PULSE_DATA_DIR}"
else
if [[ -n ${PULSE_DATA_DIR:-} ]]; then
echo "[hot-dev] Using preconfigured data directory: ${PULSE_DATA_DIR}"
elif [[ ${HOT_DEV_USE_PROD_DATA:-false} == "true" ]]; then
export PULSE_DATA_DIR=/etc/pulse
echo "[hot-dev] HOT_DEV_USE_PROD_DATA=true using production data directory: ${PULSE_DATA_DIR}"
else
DEV_CONFIG_DIR="${ROOT_DIR}/tmp/dev-config"
mkdir -p "$DEV_CONFIG_DIR"
export PULSE_DATA_DIR="${DEV_CONFIG_DIR}"
echo "[hot-dev] Production mode: Using dev config directory: ${PULSE_DATA_DIR}"
fi
# Attempt to load encryption key automatically when not explicitly provided
if [[ -z ${PULSE_ENCRYPTION_KEY:-} ]]; then
if [[ -f "${PULSE_DATA_DIR}/.encryption.key" ]]; then
export PULSE_ENCRYPTION_KEY="$(<"${PULSE_DATA_DIR}/.encryption.key")"
echo "[hot-dev] Loaded encryption key from ${PULSE_DATA_DIR}/.encryption.key"
elif [[ ${PULSE_DATA_DIR} == "${ROOT_DIR}/tmp/dev-config" ]]; then
DEV_KEY_FILE="${PULSE_DATA_DIR}/.encryption.key"
if [[ ! -f "${DEV_KEY_FILE}" ]]; then
openssl rand -hex 32 > "${DEV_KEY_FILE}"
chmod 600 "${DEV_KEY_FILE}"
echo "[hot-dev] Generated dev encryption key at ${DEV_KEY_FILE}"
fi
export PULSE_ENCRYPTION_KEY="$(<"${DEV_KEY_FILE}")"
else
echo "[hot-dev] WARNING: No encryption key found for ${PULSE_DATA_DIR}. Encrypted config may fail to load."
fi
fi
fi
./pulse &
BACKEND_PID=$!
sleep 2
if ! kill -0 "${BACKEND_PID}" 2>/dev/null; then
echo "ERROR: Backend failed to start!"
exit 1
fi
# Start backend file watcher in background
printf "[hot-dev] Starting backend file watcher...\n"
(
cd "${ROOT_DIR}"
while true; do
# Watch for changes to .go files (excluding vendor and node_modules)
inotifywait -r -e modify,create,delete,move \
--exclude '(vendor/|node_modules/|\.git/|\.swp$|\.tmp$|~$)' \
--format '%e %w%f' \
"${ROOT_DIR}/cmd" "${ROOT_DIR}/internal" "${ROOT_DIR}/pkg" 2>/dev/null | \
while read -r event changed_file; do
# Rebuild on any .go file change OR any create/delete event (catches new files)
if [[ "$changed_file" == *.go ]] || [[ "$event" =~ CREATE|DELETE|MOVED ]]; then
echo ""
echo "[hot-dev] 🔄 Change detected: $event $(basename "$changed_file")"
echo "[hot-dev] Rebuilding backend..."
# Rebuild the binary
if go build -o pulse ./cmd/pulse 2>&1 | grep -v "^#"; then
echo "[hot-dev] ✓ Build successful, restarting backend..."
# Find and kill old backend
OLD_PID=$(pgrep -f "^\./pulse$" || true)
if [[ -n "$OLD_PID" ]]; then
kill "$OLD_PID" 2>/dev/null || true
sleep 1
if kill -0 "$OLD_PID" 2>/dev/null; then
kill -9 "$OLD_PID" 2>/dev/null || true
fi
fi
# Start new backend with same environment
FRONTEND_PORT=${PULSE_DEV_API_PORT} PORT=${PULSE_DEV_API_PORT} PULSE_DATA_DIR=${PULSE_DATA_DIR} ./pulse &
NEW_PID=$!
sleep 1
if kill -0 "$NEW_PID" 2>/dev/null; then
echo "[hot-dev] ✓ Backend restarted (PID: $NEW_PID)"
else
echo "[hot-dev] ✗ Backend failed to start!"
fi
else
echo "[hot-dev] ✗ Build failed!"
fi
echo "[hot-dev] Watching for changes..."
fi
done
done
) &
WATCHER_PID=$!
cleanup() {
echo ""
echo "Stopping services..."
if [[ -n ${WATCHER_PID:-} ]] && kill -0 "${WATCHER_PID}" 2>/dev/null; then
kill "${WATCHER_PID}" 2>/dev/null || true
fi
if [[ -n ${BACKEND_PID:-} ]] && kill -0 "${BACKEND_PID}" 2>/dev/null; then
kill "${BACKEND_PID}" 2>/dev/null || true
sleep 1
if kill -0 "${BACKEND_PID}" 2>/dev/null; then
echo "Backend not responding to SIGTERM, force killing..."
kill -9 "${BACKEND_PID}" 2>/dev/null || true
fi
fi
pkill -f vite 2>/dev/null || true
pkill -f "npm run dev" 2>/dev/null || true
pkill -9 -x "pulse" 2>/dev/null || true
pkill -f "inotifywait.*pulse" 2>/dev/null || true
echo "Hot-dev stopped. To restart normal service, run: sudo systemctl start pulse"
echo "(Legacy installs may use: sudo systemctl start pulse-backend)"
}
trap cleanup INT TERM EXIT
printf "[hot-dev] Starting frontend with hot-reload on port %s...\n" "${FRONTEND_DEV_PORT}"
echo "If this fails, port ${FRONTEND_DEV_PORT} is still in use!"
cd "${ROOT_DIR}/frontend-modern"
npx vite --config vite.config.ts --host "${FRONTEND_DEV_HOST}" --port "${FRONTEND_DEV_PORT}" --clearScreen false
echo "ERROR: Vite exited unexpectedly!"
echo "Dev mode will auto-restart in 5 seconds via systemd..."
cleanup