Pulse/scripts/toggle-mock.sh
rcourtman f136b1ef59
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
fix(mock): backfill the metrics store in mock mode so reports get real history
Mock mode seeded rich in-memory chart history but passed a nil store to the
seeder and the live tick, so the sqlite metrics store held almost nothing:
vmware/truenas/docker resources got one row per poll from the platform
ingestion paths and mock Proxmox guests got nothing at all (the unified sync
skips every resource in mock mode). Performance reports rendered
"Data Points: 0" for PVE guests and "Samples: 1" elsewhere, with no charts.

Re-enable store seeding behind an explicit PULSE_MOCK_SEED_METRICS_STORE
opt-in that scripts/hot-dev.sh and scripts/toggle-mock.sh export exactly
where they point PULSE_DATA_DIR at the isolated tmp/mock-data dir. Without
the opt-in (a production install flipping PULSE_MOCK_MODE on its real data
dir) the store stays untouched, which is what the old nil guard protected.

Replace the dormant dense seeding policy (every in-memory timestamp written
to both hourly and daily tiers, ~11M rows at current fixture scale) with
tier-correct backfill driven by the deterministic mock.SampleMetric runtime:
daily 30d at 4h spacing, hourly 7d at 2h, minute 24h at 15m, raw left to the
live tick (now also store-connected under the opt-in). Timestamps sit on the
spacing grid and a new Store.MaxTimestampsForTier coverage query fills only
the gap since the previous boot, so restarts neither duplicate rows nor
re-pay the seed: a fresh seed wrote 264,960 rows in 8.4s (71MB) and a
restart wrote only the gap plus fixture series whose mock IDs are not
boot-stable (k8s pod names, ceph FSID, a pre-existing generator defect).

Verified live in mock mode: 30-day PDF reports for a mock PVE VM
(checkout-web-01), a vSphere VM, the TrueNAS host, and a Docker container
now show 1260/1260/1260/540 data points with rendered time-series charts.
2026-06-10 21:25:02 +01:00

882 lines
24 KiB
Bash
Executable file

#!/usr/bin/env bash
# Toggle script for Pulse mock mode.
#
# Goals:
# - `./scripts/toggle-mock.sh` toggles mode (run again toggles back)
# - Rebuild frontend + backend every switch
# - Stop and restart the active runtime (systemd, hot-dev, or standalone)
# - Work on macOS and Linux
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
HOT_DEV_BG_PATH="${HOT_DEV_BG_PATH:-${ROOT_DIR}/scripts/hot-dev-bg.sh}"
DEV_DATA_DIR="${ROOT_DIR}/tmp/dev-config"
MOCK_DATA_DIR="${ROOT_DIR}/tmp/mock-data"
DEV_KEY_FILE="${DEV_DATA_DIR}/.encryption.key"
MOCK_ENV_FILE="${MOCK_ENV_FILE:-${DEV_DATA_DIR}/.env}"
RUNTIME_MOCK_ENV_FILE="${RUNTIME_MOCK_ENV_FILE:-${MOCK_DATA_DIR}/.env}"
LEGACY_ROOT_MOCK_ENV_FILE="${LEGACY_ROOT_MOCK_ENV_FILE:-${ROOT_DIR}/mock.env}"
LEGACY_DEV_MOCK_ENV_FILE="${LEGACY_DEV_MOCK_ENV_FILE:-${DEV_DATA_DIR}/mock.env}"
LEGACY_RUNTIME_MOCK_ENV_FILE="${LEGACY_RUNTIME_MOCK_ENV_FILE:-${MOCK_DATA_DIR}/mock.env}"
HOT_DEV_LOG="/tmp/pulse-hot-dev.log"
STANDALONE_LOG="/tmp/pulse-standalone.log"
DEFAULT_PORT=7655
LOCK_DIR="/tmp/pulse-toggle-mock.lock"
LOCK_PID_FILE="${LOCK_DIR}/pid"
log_info() {
echo -e "${BLUE}[toggle-mock]${NC} $*"
}
log_warn() {
echo -e "${YELLOW}[toggle-mock] WARNING:${NC} $*"
}
log_error() {
echo -e "${RED}[toggle-mock] ERROR:${NC} $*"
}
log_success() {
echo -e "${GREEN}[toggle-mock]${NC} $*"
}
acquire_lock() {
if mkdir "${LOCK_DIR}" 2>/dev/null; then
echo "$$" > "${LOCK_PID_FILE}"
trap 'release_lock' EXIT
return
fi
if [[ -f "${LOCK_PID_FILE}" ]]; then
local owner_pid
owner_pid="$(cat "${LOCK_PID_FILE}" 2>/dev/null || true)"
if [[ -n "${owner_pid}" ]] && kill -0 "${owner_pid}" 2>/dev/null; then
log_error "Another toggle run is in progress (pid: ${owner_pid}). Try again in a few seconds."
exit 1
fi
fi
rm -rf "${LOCK_DIR}" 2>/dev/null || true
if mkdir "${LOCK_DIR}" 2>/dev/null; then
echo "$$" > "${LOCK_PID_FILE}"
trap 'release_lock' EXIT
return
fi
log_error "Failed to acquire toggle lock at ${LOCK_DIR}"
exit 1
}
release_lock() {
rm -rf "${LOCK_DIR}" 2>/dev/null || true
}
launch_detached() {
local log_file="$1"
shift
if command -v python3 >/dev/null 2>&1; then
python3 - "$log_file" "$@" <<'PY'
import subprocess
import sys
log_path = sys.argv[1]
cmd = sys.argv[2:]
with open(log_path, "ab", buffering=0) as log:
proc = subprocess.Popen(
cmd,
stdin=subprocess.DEVNULL,
stdout=log,
stderr=subprocess.STDOUT,
start_new_session=True,
)
print(proc.pid)
PY
return
fi
nohup "$@" >"${log_file}" 2>&1 &
echo $!
}
is_port_listening() {
local port="$1"
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
}
wait_for_port() {
local port="$1"
local timeout_seconds="${2:-20}"
local checks=$((timeout_seconds * 2))
while (( checks > 0 )); do
if is_port_listening "$port"; then
return 0
fi
sleep 0.5
checks=$((checks - 1))
done
return 1
}
run_hot_dev_bg() {
"${HOT_DEV_BG_PATH}" "$@"
}
hot_dev_bg_status_output() {
run_hot_dev_bg status 2>/dev/null || true
}
hot_dev_bg_is_running() {
local output
output="$(hot_dev_bg_status_output)"
[[ "${output}" == *"[hot-dev-bg] Running"* ]]
}
prefer_hot_dev_runtime() {
local prefer="${PULSE_TOGGLE_PREFER_HOT_DEV:-auto}"
case "${prefer}" in
true) return 0 ;;
false) return 1 ;;
auto)
if [[ "$(uname -s)" == "Darwin" ]]; then
return 0
fi
return 1
;;
*)
log_warn "Unknown PULSE_TOGGLE_PREFER_HOT_DEV=${prefer}; using auto"
if [[ "$(uname -s)" == "Darwin" ]]; then
return 0
fi
return 1
;;
esac
}
use_isolated_mock_data_dir() {
local value="${PULSE_TOGGLE_ISOLATE_MOCK_DATA:-false}"
case "${value}" in
true|TRUE|1|yes|YES|on|ON) return 0 ;;
*) return 1 ;;
esac
}
verify_mock_state_via_frontend_proxy() {
local target_mode="$1"
local expected="false"
local url="${PULSE_TOGGLE_FRONTEND_VERIFY_URL:-http://127.0.0.1:5173/api/system/mock-mode}"
local retries=40
if [[ "${target_mode}" == "true" ]]; then
expected="true"
fi
while (( retries > 0 )); do
local resp
resp="$(curl -fsS --max-time 2 "${url}" 2>/dev/null || true)"
if [[ -n "${resp}" ]] && echo "${resp}" | grep -q "\"enabled\":${expected}"; then
return 0
fi
sleep 0.5
retries=$((retries - 1))
done
return 1
}
managed_hot_dev_reports_mock_state() {
local target_mode="$1"
hot_dev_bg_is_running || return 1
verify_mock_state_via_frontend_proxy "$target_mode"
}
sed_inplace() {
local expr="$1"
local file="$2"
if [[ "$(uname -s)" == "Darwin" ]]; then
sed -i '' "$expr" "$file"
else
sed -i "$expr" "$file"
fi
}
load_env_file() {
local env_file="$1"
if [[ -f "$env_file" ]]; then
set +u
set -a
# shellcheck disable=SC1090
source "$env_file"
set +a
set -u
fi
}
mock_default_entries() {
cat <<'ENVEOF'
PULSE_MOCK_MODE=false
PULSE_MOCK_NODES=5
PULSE_MOCK_VMS_PER_NODE=6
PULSE_MOCK_LXCS_PER_NODE=8
PULSE_MOCK_DOCKER_HOSTS=5
PULSE_MOCK_DOCKER_CONTAINERS=14
PULSE_MOCK_GENERIC_HOSTS=4
PULSE_MOCK_K8S_CLUSTERS=3
PULSE_MOCK_K8S_NODES=5
PULSE_MOCK_K8S_PODS=40
PULSE_MOCK_K8S_DEPLOYMENTS=14
PULSE_MOCK_RANDOM_METRICS=true
PULSE_MOCK_STOPPED_PERCENT=6
ENVEOF
}
ensure_parent_dir() {
mkdir -p "$(dirname "$1")"
}
ensure_env_file_exists() {
local file="$1"
ensure_parent_dir "$file"
if [[ ! -f "$file" ]]; then
: > "$file"
fi
}
env_has_key() {
local file="$1"
local key="$2"
[[ -f "$file" ]] && grep -Eq "^[[:space:]]*${key}=" "$file"
}
read_env_value() {
local file="$1"
local key="$2"
if [[ ! -f "$file" ]]; then
return 1
fi
awk -F= -v lookup="$key" '
$1 ~ "^[[:space:]]*" lookup "$" {
value = substr($0, index($0, "=") + 1)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
print value
}
' "$file" | tail -n 1
}
set_env_value() {
local file="$1"
local key="$2"
local value="$3"
ensure_env_file_exists "$file"
if env_has_key "$file" "$key"; then
sed_inplace "s|^[[:space:]]*${key}=.*|${key}=${value}|" "$file"
else
printf '\n%s=%s\n' "$key" "$value" >> "$file"
fi
}
ensure_env_value() {
local file="$1"
local key="$2"
local value="$3"
ensure_env_file_exists "$file"
if ! env_has_key "$file" "$key"; then
printf '%s=%s\n' "$key" "$value" >> "$file"
fi
}
sync_mock_entries_to_file() {
local file="$1"
local line key value
ensure_env_file_exists "$file"
while IFS='=' read -r key value; do
[[ -n "${key}" ]] || continue
value="$(read_env_value "$MOCK_ENV_FILE" "$key" || printf '%s' "$value")"
set_env_value "$file" "$key" "$value"
done < <(mock_default_entries)
}
ensure_mock_env_file() {
local legacy_file key value
local created=false
if [[ ! -f "$MOCK_ENV_FILE" ]]; then
ensure_env_file_exists "$MOCK_ENV_FILE"
created=true
fi
if ! env_has_key "$MOCK_ENV_FILE" "PULSE_MOCK_MODE"; then
for legacy_file in \
"$LEGACY_DEV_MOCK_ENV_FILE" \
"$LEGACY_ROOT_MOCK_ENV_FILE" \
"$LEGACY_RUNTIME_MOCK_ENV_FILE"
do
[[ -f "$legacy_file" ]] || continue
while IFS='=' read -r key value; do
[[ "${key}" == PULSE_MOCK_* ]] || continue
set_env_value "$MOCK_ENV_FILE" "$key" "$value"
done < <(grep -E '^[[:space:]]*PULSE_MOCK_[A-Z0-9_]*=' "$legacy_file" || true)
log_info "Migrated legacy mock settings from ${legacy_file} to ${MOCK_ENV_FILE}"
break
done
fi
while IFS='=' read -r key value; do
ensure_env_value "$MOCK_ENV_FILE" "$key" "$value"
done < <(mock_default_entries)
if [[ "$created" == "true" ]]; then
log_info "Created ${MOCK_ENV_FILE}"
fi
}
get_mock_mode() {
ensure_mock_env_file
if [[ "$(read_env_value "$MOCK_ENV_FILE" "PULSE_MOCK_MODE" || true)" == "true" ]]; then
echo "true"
else
echo "false"
fi
}
set_mock_mode() {
local enabled="$1"
ensure_mock_env_file
set_env_value "$MOCK_ENV_FILE" "PULSE_MOCK_MODE" "$enabled"
sync_mock_entries_to_file "$DEV_DATA_DIR/.env"
sync_mock_entries_to_file "$RUNTIME_MOCK_ENV_FILE"
touch "$MOCK_ENV_FILE"
}
detect_runtime_mode() {
if command -v systemctl >/dev/null 2>&1; then
if systemctl is-active --quiet pulse-hot-dev 2>/dev/null; then
echo "systemd-hot-dev"
return
fi
if systemctl is-active --quiet pulse 2>/dev/null; then
echo "systemd-pulse"
return
fi
fi
if hot_dev_bg_is_running; then
echo "managed-hot-dev"
return
fi
if pgrep -f "hot-dev.sh" >/dev/null 2>&1; then
echo "hot-dev"
return
fi
if pgrep -f "${ROOT_DIR}/pulse|(^|[[:space:]])\./pulse([[:space:]]|$)" >/dev/null 2>&1; then
echo "standalone"
return
fi
if pgrep -x pulse >/dev/null 2>&1; then
echo "pulse-name"
return
fi
echo "none"
}
kill_with_grace() {
local pattern="$1"
local signal="${2:-TERM}"
if ! pgrep -f "$pattern" >/dev/null 2>&1; then
return
fi
pkill -"$signal" -f "$pattern" 2>/dev/null || true
}
wait_for_exit() {
local pattern="$1"
local retries=20
while (( retries > 0 )); do
if ! pgrep -f "$pattern" >/dev/null 2>&1; then
return
fi
sleep 0.25
retries=$((retries - 1))
done
pkill -9 -f "$pattern" 2>/dev/null || true
}
stop_hot_dev_runtime() {
local runtime_mode="${1:-hot-dev}"
local hot_dev_pattern="${ROOT_DIR}/scripts/hot-dev.sh|scripts/hot-dev.sh|hot-dev.sh"
local pulse_pattern="${ROOT_DIR}/pulse|(^|[[:space:]])\\./pulse([[:space:]]|$)"
local vite_pattern="${ROOT_DIR}/frontend-modern.*vite|vite.*${ROOT_DIR}/frontend-modern|vite --config vite.config.ts"
if [[ "${runtime_mode}" == "managed-hot-dev" ]] && hot_dev_bg_is_running; then
log_info "Stopping managed hot-dev runtime..."
run_hot_dev_bg stop >/dev/null
return
fi
log_info "Stopping legacy hot-dev runtime..."
kill_with_grace "$hot_dev_pattern" TERM
wait_for_exit "$hot_dev_pattern"
kill_with_grace "$pulse_pattern" TERM
wait_for_exit "$pulse_pattern"
kill_with_grace "$vite_pattern" TERM
wait_for_exit "$vite_pattern"
}
stop_standalone_runtime() {
local allow_name_kill="${1:-false}"
local pulse_pattern="${ROOT_DIR}/pulse|(^|[[:space:]])\\./pulse([[:space:]]|$)"
log_info "Stopping standalone Pulse runtime..."
kill_with_grace "$pulse_pattern" TERM
wait_for_exit "$pulse_pattern"
if [[ "$allow_name_kill" == "true" ]] && pgrep -x pulse >/dev/null 2>&1; then
log_warn "Found remaining 'pulse' process by name; stopping it"
pkill -TERM -x pulse 2>/dev/null || true
sleep 1
pkill -9 -x pulse 2>/dev/null || true
fi
}
stop_systemd_runtime() {
local mode="$1"
case "$mode" in
systemd-hot-dev)
log_info "Stopping systemd service: pulse-hot-dev"
sudo systemctl stop pulse-hot-dev
;;
systemd-pulse)
log_info "Stopping systemd service: pulse"
sudo systemctl stop pulse
;;
esac
}
has_frontend_artifacts() {
[[ -f "${ROOT_DIR}/frontend-modern/dist/index.html" ]] && [[ -f "${ROOT_DIR}/internal/api/frontend-modern/dist/index.html" ]]
}
has_backend_artifact() {
[[ -x "${ROOT_DIR}/pulse" ]]
}
rebuild_frontend_backend() {
# Rebuild strategy:
# - auto (default): only rebuild when required artifacts are missing
# - always: force full frontend+backend rebuild
# - never: skip rebuild completely
local rebuild_mode="${PULSE_TOGGLE_REBUILD:-auto}"
local do_frontend=false
local do_backend=false
case "$rebuild_mode" in
always)
do_frontend=true
do_backend=true
;;
never)
log_info "Skipping rebuild (PULSE_TOGGLE_REBUILD=never)"
return
;;
auto)
if ! has_frontend_artifacts; then
do_frontend=true
# Backend embeds frontend assets, so rebuild backend after frontend build.
do_backend=true
fi
if ! has_backend_artifact; then
do_backend=true
fi
;;
*)
log_warn "Unknown PULSE_TOGGLE_REBUILD=${rebuild_mode}; using auto"
if ! has_frontend_artifacts; then
do_frontend=true
do_backend=true
fi
if ! has_backend_artifact; then
do_backend=true
fi
;;
esac
if [[ "$do_frontend" == "false" ]] && [[ "$do_backend" == "false" ]]; then
log_info "Build artifacts already present; skipping rebuild"
return
fi
(
cd "$ROOT_DIR"
if [[ "$do_frontend" == "true" ]]; then
log_info "Building frontend..."
make frontend
fi
if [[ "$do_backend" == "true" ]]; then
log_info "Building backend..."
make backend
fi
)
log_success "Build complete"
}
ensure_dev_key() {
mkdir -p "$DEV_DATA_DIR"
if [[ ! -f "$DEV_KEY_FILE" ]]; then
openssl rand -base64 32 > "$DEV_KEY_FILE"
chmod 600 "$DEV_KEY_FILE"
log_info "Generated dev encryption key at ${DEV_KEY_FILE}"
fi
}
sync_production_config() {
if [[ -x "${ROOT_DIR}/scripts/sync-production-config.sh" ]]; then
log_info "Syncing production configuration into dev config"
DEV_DIR="$DEV_DATA_DIR" "${ROOT_DIR}/scripts/sync-production-config.sh"
else
log_warn "sync-production-config.sh not found or not executable; skipping sync"
fi
}
start_hot_dev_runtime() {
log_info "Starting managed hot-dev runtime..."
if ! run_hot_dev_bg start --takeover; then
log_warn "Managed hot-dev start command did not complete cleanly"
return 1
fi
log_success "Managed hot-dev runtime is running"
}
start_standalone_runtime() {
local target_mock_mode="$1"
local pid
local startup_timeout="${PULSE_TOGGLE_STANDALONE_START_TIMEOUT:-20}"
local data_dir="$DEV_DATA_DIR"
log_info "Starting standalone Pulse"
cd "$ROOT_DIR"
load_env_file "${ROOT_DIR}/.env"
load_env_file "${ROOT_DIR}/.env.local"
load_env_file "${ROOT_DIR}/.env.dev"
if [[ "$target_mock_mode" == "true" ]] && use_isolated_mock_data_dir; then
data_dir="$MOCK_DATA_DIR"
fi
export PULSE_DATA_DIR="$data_dir"
mkdir -p "$PULSE_DATA_DIR"
load_env_file "${PULSE_DATA_DIR}/.env"
if [[ "$PULSE_DATA_DIR" == "$MOCK_DATA_DIR" ]]; then
# The isolated mock data dir holds no real history, so the backend may
# safely backfill mock metrics history into its metrics.db (powers
# reports and long-range charts in mock mode).
export PULSE_MOCK_SEED_METRICS_STORE=true
log_warn "Using isolated mock data dir (${MOCK_DATA_DIR}); production metrics history will pause while mock mode is on"
else
ensure_dev_key
if [[ -z "${PULSE_ENCRYPTION_KEY:-}" ]]; then
export PULSE_ENCRYPTION_KEY="$(<"$DEV_KEY_FILE")"
fi
fi
# Keep audit persistence inside the selected runtime data directory so
# local macOS runs don't attempt to write /var/lib/pulse.
export PULSE_AUDIT_DIR="${PULSE_AUDIT_DIR:-$PULSE_DATA_DIR}"
export PORT="${PORT:-$DEFAULT_PORT}"
export FRONTEND_PORT="${FRONTEND_PORT:-$PORT}"
pid="$(launch_detached "${STANDALONE_LOG}" "${ROOT_DIR}/pulse")"
sleep 2
if kill -0 "$pid" 2>/dev/null; then
if ! wait_for_port "${PORT}" "${startup_timeout}"; then
log_error "Pulse process started but port ${PORT} did not become ready within ${startup_timeout}s"
return 1
fi
log_success "Pulse started (pid: ${pid}, port: ${PORT}, log: ${STANDALONE_LOG})"
else
log_error "Pulse failed to start. Check ${STANDALONE_LOG}"
return 1
fi
}
start_systemd_runtime() {
local mode="$1"
case "$mode" in
systemd-hot-dev)
log_info "Starting systemd service: pulse-hot-dev"
sudo systemctl start pulse-hot-dev
;;
systemd-pulse)
log_info "Starting systemd service: pulse"
sudo systemctl start pulse
;;
esac
}
apply_mode() {
local target_mode="$1"
local runtime_mode
local start_mode
local allow_fallback="${PULSE_TOGGLE_ALLOW_STANDALONE_FALLBACK:-false}"
runtime_mode="$(detect_runtime_mode)"
start_mode="${runtime_mode}"
log_info "Detected runtime mode: ${runtime_mode}"
if [[ "$target_mode" == "true" ]]; then
log_info "Switching to mock mode"
if use_isolated_mock_data_dir; then
mkdir -p "$MOCK_DATA_DIR"
fi
else
log_info "Switching to production-node mode"
sync_production_config
fi
set_mock_mode "$target_mode"
case "$runtime_mode" in
systemd-hot-dev|systemd-pulse)
stop_systemd_runtime "$runtime_mode"
;;
managed-hot-dev|hot-dev)
stop_hot_dev_runtime "$runtime_mode"
;;
standalone)
stop_standalone_runtime "false"
;;
pulse-name)
stop_standalone_runtime "true"
;;
none)
stop_standalone_runtime "false"
;;
esac
# macOS default: keep development on hot-dev so frontend changes at 127.0.0.1:5173
# are always reflected immediately after toggling.
if [[ "${runtime_mode}" == "none" ]] || [[ "${runtime_mode}" == "standalone" ]] || [[ "${runtime_mode}" == "pulse-name" ]]; then
if prefer_hot_dev_runtime; then
start_mode="managed-hot-dev"
fi
fi
# hot-dev already handles build/reload on startup; avoid duplicate builds here for fast toggles.
if [[ "$start_mode" == "managed-hot-dev" ]] || [[ "$start_mode" == "hot-dev" ]] || [[ "$start_mode" == "systemd-hot-dev" ]]; then
log_info "Skipping pre-rebuild for hot-dev runtime (managed by hot-dev startup)"
else
rebuild_frontend_backend
fi
case "$start_mode" in
systemd-hot-dev|systemd-pulse)
start_systemd_runtime "$start_mode"
;;
managed-hot-dev|hot-dev)
if ! start_hot_dev_runtime; then
if managed_hot_dev_reports_mock_state "$target_mode"; then
log_warn "Managed hot-dev reported a restart problem, but the browser entrypoint is serving the requested mock state; continuing"
elif [[ "${allow_fallback}" == "true" ]]; then
log_warn "hot-dev restart failed or is unhealthy; falling back to standalone backend on port ${DEFAULT_PORT}"
stop_hot_dev_runtime "$start_mode" || true
start_standalone_runtime "$target_mode"
start_mode="standalone"
else
log_error "hot-dev restart failed; refusing to fallback so frontend dev state stays explicit"
return 1
fi
fi
;;
standalone|pulse-name|none)
start_standalone_runtime "$target_mode"
;;
esac
if [[ "$start_mode" == "managed-hot-dev" ]] || [[ "$start_mode" == "hot-dev" ]]; then
if ! verify_mock_state_via_frontend_proxy "$target_mode"; then
log_error "Frontend proxy at 127.0.0.1:5173 did not report expected mock state (${target_mode})"
return 1
fi
fi
if [[ "$target_mode" == "true" ]]; then
log_success "Mock mode is now enabled"
else
log_success "Mock mode is now disabled (real nodes mode)"
fi
}
show_status() {
local runtime_mode
runtime_mode="$(detect_runtime_mode)"
ensure_mock_env_file
set +u
# shellcheck disable=SC1090
source "$MOCK_ENV_FILE"
set -u
echo "Mock Data Mode Control for Pulse"
echo ""
local status_data_dir="$DEV_DATA_DIR"
if [[ "${PULSE_MOCK_MODE:-false}" == "true" ]]; then
# hot-dev runtimes isolate mock data into MOCK_DATA_DIR unconditionally
# (see scripts/hot-dev.sh); the standalone runtime only isolates when
# PULSE_TOGGLE_ISOLATE_MOCK_DATA is set.
case "${runtime_mode}" in
managed-hot-dev | hot-dev | systemd-hot-dev)
status_data_dir="$MOCK_DATA_DIR"
;;
*)
use_isolated_mock_data_dir && status_data_dir="$MOCK_DATA_DIR"
;;
esac
fi
if [[ "${PULSE_MOCK_MODE:-false}" == "true" ]]; then
echo -e "${GREEN}Mock Mode: ENABLED${NC}"
echo " Runtime: ${runtime_mode}"
echo " Nodes: ${PULSE_MOCK_NODES:-0}"
echo " VMs per node: ${PULSE_MOCK_VMS_PER_NODE:-0}"
echo " LXCs per node: ${PULSE_MOCK_LXCS_PER_NODE:-0}"
echo " Generic hosts: ${PULSE_MOCK_GENERIC_HOSTS:-0}"
echo " Docker hosts: ${PULSE_MOCK_DOCKER_HOSTS:-0}"
echo " Docker containers/host: ${PULSE_MOCK_DOCKER_CONTAINERS:-0}"
echo " K8s clusters: ${PULSE_MOCK_K8S_CLUSTERS:-0}"
echo " Data dir: ${status_data_dir}"
else
echo -e "${BLUE}Mock Mode: DISABLED${NC}"
echo " Runtime: ${runtime_mode}"
echo " Data dir: ${status_data_dir}"
fi
}
edit_config() {
ensure_mock_env_file
log_info "Opening ${MOCK_ENV_FILE}"
${EDITOR:-nano} "$MOCK_ENV_FILE"
}
sync_config_only() {
sync_production_config
if [[ "$(detect_runtime_mode)" != "none" ]]; then
log_info "Runtime detected; restarting current mode to apply synced config"
apply_mode "$(get_mock_mode)"
else
log_info "No active runtime detected; sync complete"
fi
}
usage() {
echo "Mock Data Mode Control for Pulse"
echo ""
echo "Usage: $0 [toggle|on|off|status|edit|sync]"
echo ""
echo " toggle - Toggle between mock mode and production-node mode (default)"
echo " on - Enable mock mode and restart/rebuild stack"
echo " off - Disable mock mode, sync real nodes config, restart/rebuild stack"
echo " status - Show current mock mode + runtime status"
echo " edit - Edit the runtime .env mock settings"
echo " sync - Sync production config to dev config and restart if running"
}
main() {
local cmd="${1:-toggle}"
local needs_lock=false
case "$cmd" in
on|enable)
needs_lock=true
;;
off|disable)
needs_lock=true
;;
toggle)
needs_lock=true
;;
sync)
needs_lock=true
;;
esac
if [[ "$needs_lock" == "true" ]]; then
acquire_lock
fi
case "$cmd" in
on|enable)
apply_mode "true"
;;
off|disable)
apply_mode "false"
;;
toggle)
if [[ "$(get_mock_mode)" == "true" ]]; then
apply_mode "false"
else
apply_mode "true"
fi
;;
status)
show_status
;;
edit)
edit_config
;;
sync)
sync_config_only
;;
-h|--help|help)
usage
;;
*)
usage
echo ""
log_error "Unknown command: $cmd"
exit 1
;;
esac
}
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@"
fi