mirror of
https://github.com/dmduran12/pymc_console-dist.git
synced 2026-10-31 01:42:00 +01:00
Automated sync from private repository. UI build published as release assets.
715 lines
28 KiB
Bash
Executable File
715 lines
28 KiB
Bash
Executable File
#!/bin/bash
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# waev:outpost - Dashboard Manager
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
#
|
|
# SCOPE: Dashboard-only.
|
|
#
|
|
# This script manages the waev:outpost React dashboard overlay. It does NOT
|
|
# install, upgrade, or uninstall openHop Repeater itself — that is the Repeater
|
|
# repo's job (run openhop_repeater's own manage.sh for Repeater lifecycle).
|
|
#
|
|
# WHAT WE DO:
|
|
# • Download and install the waev:outpost dashboard into /opt/pymc_console
|
|
# • On fresh install, point the Repeater's web.web_path at our dashboard
|
|
# • On upgrade, refresh dashboard assets while preserving web_path
|
|
# • On uninstall, remove /opt/pymc_console and clear web.web_path when it
|
|
# still points at our dashboard (Repeater is otherwise left untouched)
|
|
#
|
|
# WHAT WE DO NOT DO (anymore):
|
|
# • Clone, install, upgrade, or uninstall openHop Repeater
|
|
# • Radio/GPIO configuration
|
|
# • systemd unit management (start/stop/restart/status/logs — use Repeater's
|
|
# manage.sh or systemctl/journalctl directly for the openhop-repeater service)
|
|
# • Any TUI (whiptail/dialog). All prompts are plain terminal I/O.
|
|
#
|
|
# REPEATER REFERENCES:
|
|
# • $INSTALL_DIR is referenced only to detect that Repeater is installed
|
|
# (we refuse to install waev:outpost without it)
|
|
# • The Repeater's config.yaml is patched (web.web_path) on fresh install:
|
|
# $CONFIG_DIR on openHop, or $LEGACY_CONFIG_DIR on a system upstream has
|
|
# not migrated. Detection and patching honour the same two layouts.
|
|
# • $REPEATER_USER:$REPEATER_GROUP is used for ownership of our files
|
|
#
|
|
# NON-INTERACTIVE MODE:
|
|
# Pass --yes (before the verb) or set ASSUME_YES=1 to auto-confirm prompts.
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
set -e
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Configuration
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Repeater paths (read-only for us — used to locate config/user)
|
|
INSTALL_DIR="/opt/openhop_repeater"
|
|
CONFIG_DIR="/etc/openhop_repeater"
|
|
SERVICE_NAME="openhop-repeater"
|
|
REPEATER_USER="repeater"
|
|
REPEATER_GROUP="repeater"
|
|
|
|
# Legacy pyMC_Repeater paths remain detection fallbacks for older systems.
|
|
LEGACY_INSTALL_DIR="/opt/pymc_repeater"
|
|
LEGACY_CONFIG_DIR="/etc/pymc_repeater"
|
|
LEGACY_SERVICE_NAME="pymc-repeater"
|
|
|
|
# Console paths (we own these)
|
|
CONSOLE_DIR="/opt/pymc_console"
|
|
UI_DIR="$CONSOLE_DIR/web/html"
|
|
|
|
# Release artifacts
|
|
UI_REPO="Treehouse-00/pymc_console-dist"
|
|
UI_RELEASE_URL="https://github.com/${UI_REPO}/releases"
|
|
UI_TARBALL="pymc-ui-latest.tar.gz"
|
|
|
|
# Runtime flags (set by CLI parser). Exported so a re-exec preserves them.
|
|
export ASSUME_YES="${ASSUME_YES:-0}"
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Terminal Output
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
# Enable colors only when stdout is a TTY and NO_COLOR is unset.
|
|
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
NC='\033[0m'
|
|
else
|
|
RED=''; GREEN=''; YELLOW=''; CYAN=''; BOLD=''; DIM=''; NC=''
|
|
fi
|
|
|
|
print_step() { echo -e "\n${BOLD}${CYAN}[$1/$2]${NC} ${BOLD}$3${NC}"; }
|
|
print_success() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
print_error() { echo -e " ${RED}✗${NC} ${RED}$1${NC}" >&2; }
|
|
print_info() { echo -e " ${CYAN}➜${NC} $1"; }
|
|
print_warning() { echo -e " ${YELLOW}⚠${NC} $1"; }
|
|
|
|
print_banner() {
|
|
echo ""
|
|
echo -e "${BOLD}${CYAN}waev:outpost${NC}"
|
|
echo -e "${DIM}React Dashboard for openHop Repeater${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Terminal Prompts (no TUI)
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
#
|
|
# prompt_yes_no "question" [default]
|
|
# default: "y" or "n" (default "n"). Honors ASSUME_YES=1.
|
|
# Returns 0 on yes, 1 on no.
|
|
prompt_yes_no() {
|
|
local question="$1"
|
|
local default="${2:-n}"
|
|
local prompt_suffix
|
|
local reply
|
|
|
|
if [[ "$ASSUME_YES" == "1" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$default" == "y" ]]; then
|
|
prompt_suffix="[Y/n]"
|
|
else
|
|
prompt_suffix="[y/N]"
|
|
fi
|
|
|
|
read -r -p "$(echo -e " ${CYAN}?${NC} ${question} ${prompt_suffix} ")" reply
|
|
reply="${reply:-$default}"
|
|
case "$reply" in
|
|
y|Y|yes|YES) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Status Helpers
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
# Tiered Repeater-presence probe. Returns 0 when ANY of the following is true,
|
|
# in order of signal strength:
|
|
# 1. `pip3 show openhop_repeater` succeeds (the source of truth)
|
|
# 2. systemd knows about openhop-repeater.service (unit file present)
|
|
# 3. $INSTALL_DIR/pyproject.toml exists (edge-case layout fallback)
|
|
repeater_installed() {
|
|
if command -v pip3 &>/dev/null && { pip3 show openhop_repeater &>/dev/null || pip3 show pymc-repeater &>/dev/null; }; then
|
|
return 0
|
|
fi
|
|
if command -v systemctl &>/dev/null \
|
|
&& { [[ -n "$(systemctl list-unit-files --no-legend --no-pager "${SERVICE_NAME}.service" 2>/dev/null)" ]] \
|
|
|| [[ -n "$(systemctl list-unit-files --no-legend --no-pager "${LEGACY_SERVICE_NAME}.service" 2>/dev/null)" ]]; }; then
|
|
return 0
|
|
fi
|
|
[[ -d "$INSTALL_DIR" && -f "$INSTALL_DIR/pyproject.toml" ]] || [[ -d "$LEGACY_INSTALL_DIR" && -f "$LEGACY_INSTALL_DIR/pyproject.toml" ]]
|
|
}
|
|
|
|
console_installed() { [[ -d "$UI_DIR" ]]; }
|
|
|
|
pip_version() {
|
|
local pkg="$1"
|
|
command -v pip3 &>/dev/null || return 0
|
|
pip3 show "$pkg" 2>/dev/null | awk '/^Version:/ {print $2; exit}'
|
|
}
|
|
|
|
get_repeater_version() {
|
|
local v
|
|
v="$(pip_version openhop_repeater)"
|
|
[[ -n "$v" ]] || v="$(pip_version pymc-repeater)"
|
|
echo "${v:-unknown}"
|
|
}
|
|
|
|
get_console_version() {
|
|
if [[ -f "$UI_DIR/VERSION" ]]; then
|
|
local v
|
|
v=$(tr -d '[:space:]' < "$UI_DIR/VERSION")
|
|
echo "${v:-unknown}"
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
}
|
|
|
|
# Read-only systemd probes (safe to call as non-root).
|
|
service_unit_exists() {
|
|
command -v systemctl &>/dev/null || return 1
|
|
[[ -n "$(systemctl list-unit-files --no-legend --no-pager "${SERVICE_NAME}.service" 2>/dev/null)" ]] \
|
|
|| [[ -n "$(systemctl list-unit-files --no-legend --no-pager "${LEGACY_SERVICE_NAME}.service" 2>/dev/null)" ]]
|
|
}
|
|
active_service_name() {
|
|
if command -v systemctl &>/dev/null && [[ -n "$(systemctl list-unit-files --no-legend --no-pager "${SERVICE_NAME}.service" 2>/dev/null)" ]]; then
|
|
echo "$SERVICE_NAME"
|
|
else
|
|
echo "$LEGACY_SERVICE_NAME"
|
|
fi
|
|
}
|
|
service_is_active() { command -v systemctl &>/dev/null && systemctl is-active "$(active_service_name)" &>/dev/null; }
|
|
service_is_enabled() { command -v systemctl &>/dev/null && systemctl is-enabled "$(active_service_name)" &>/dev/null; }
|
|
|
|
# Whether systemd knows a given unit by name.
|
|
unit_exists() {
|
|
command -v systemctl &>/dev/null || return 1
|
|
[[ -n "$(systemctl list-unit-files --no-legend --no-pager "${1}.service" 2>/dev/null)" ]]
|
|
}
|
|
|
|
# The Repeater's config file: openHop's path first, then the legacy pyMC path
|
|
# on a system upstream has not migrated. Detection honours both layouts, so
|
|
# the file we patch must too — otherwise an unmigrated system gets a
|
|
# dashboard that is installed but never wired in. Prints the path; returns 1
|
|
# when neither file exists.
|
|
resolve_config_file() {
|
|
if [[ -f "$CONFIG_DIR/config.yaml" ]]; then
|
|
echo "$CONFIG_DIR/config.yaml"
|
|
elif [[ -f "$LEGACY_CONFIG_DIR/config.yaml" ]]; then
|
|
echo "$LEGACY_CONFIG_DIR/config.yaml"
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Where the config file is expected when none exists yet: beside whichever
|
|
# Repeater is actually installed, so a manual hint never names a file the
|
|
# running service would not read. Legacy only when the legacy unit is the
|
|
# one registered, or the legacy install dir is the only one present.
|
|
expected_config_file() {
|
|
local found
|
|
if found=$(resolve_config_file); then
|
|
echo "$found"
|
|
return 0
|
|
fi
|
|
if { unit_exists "$LEGACY_SERVICE_NAME" && ! unit_exists "$SERVICE_NAME"; } \
|
|
|| { [[ ! -d "$INSTALL_DIR" ]] && [[ -d "$LEGACY_INSTALL_DIR" ]]; }; then
|
|
echo "$LEGACY_CONFIG_DIR/config.yaml"
|
|
else
|
|
echo "$CONFIG_DIR/config.yaml"
|
|
fi
|
|
}
|
|
|
|
require_root() {
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
print_error "This command requires root. Run: sudo $0 $1"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Preflight + shared error messaging
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Print a non-mutating "Detected:" block summarising what we see. Returns 1
|
|
# iff Repeater is not installed (callers use this to decide whether to bail).
|
|
preflight_check() {
|
|
local repeater_ok=false
|
|
local config_ok=false
|
|
local yq_ok=false
|
|
local unit_state="not found"
|
|
local config_file
|
|
repeater_installed && repeater_ok=true
|
|
if config_file=$(resolve_config_file); then
|
|
config_ok=true
|
|
else
|
|
config_file=$(expected_config_file)
|
|
fi
|
|
command -v yq &>/dev/null && yq_ok=true
|
|
if service_unit_exists; then
|
|
local enabled="disabled"
|
|
service_is_enabled && enabled="enabled"
|
|
local active="inactive"
|
|
service_is_active && active="active"
|
|
unit_state="${enabled}, ${active}"
|
|
fi
|
|
|
|
local repeater_line
|
|
if [[ "$repeater_ok" == true ]]; then
|
|
repeater_line="${GREEN}found${NC} (v$(get_repeater_version))"
|
|
else
|
|
repeater_line="${RED}not found${NC}"
|
|
fi
|
|
local config_line
|
|
if [[ "$config_ok" == true ]]; then
|
|
config_line="${GREEN}present${NC} ($config_file)"
|
|
else
|
|
config_line="${YELLOW}missing${NC} ($config_file)"
|
|
fi
|
|
local yq_line
|
|
if [[ "$yq_ok" == true ]]; then
|
|
yq_line="${GREEN}present${NC}"
|
|
else
|
|
yq_line="${YELLOW}missing${NC} (web_path patch will be skipped)"
|
|
fi
|
|
local unit_line
|
|
if service_unit_exists; then
|
|
unit_line="${GREEN}${unit_state}${NC}"
|
|
else
|
|
unit_line="${YELLOW}not found${NC}"
|
|
fi
|
|
|
|
echo -e " ${DIM}Preflight:${NC}"
|
|
echo -e " Repeater: ${repeater_line}"
|
|
echo -e " Config file: ${config_line}"
|
|
echo -e " yq: ${yq_line}"
|
|
echo -e " Service unit: ${unit_line}"
|
|
echo ""
|
|
|
|
if [[ "$repeater_ok" != true ]]; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
print_repeater_missing_help() {
|
|
print_error "openHop Repeater is not installed."
|
|
echo ""
|
|
echo " waev:outpost requires openHop Repeater to be installed first."
|
|
echo " Install it using the Repeater repo's manage.sh:"
|
|
echo ""
|
|
echo -e " ${CYAN}git clone https://github.com/openhop-dev/openhop_repeater.git${NC}"
|
|
echo -e " ${CYAN}cd openhop_repeater && sudo bash ./manage.sh install${NC}"
|
|
echo ""
|
|
}
|
|
|
|
print_service_hint() {
|
|
# Non-mutating post-op nudge. No action taken, just observability.
|
|
if ! service_unit_exists; then
|
|
print_warning "${SERVICE_NAME}.service is not registered with systemd."
|
|
echo " Install/repair openHop Repeater to register the service."
|
|
return
|
|
fi
|
|
if service_is_active; then
|
|
print_success "$(active_service_name).service is active."
|
|
else
|
|
print_warning "$(active_service_name).service is not running."
|
|
echo -e " Start it: ${CYAN}sudo systemctl start $(active_service_name)${NC}"
|
|
fi
|
|
if ! service_is_enabled; then
|
|
echo -e " Enable on boot: ${CYAN}sudo systemctl enable $(active_service_name)${NC}"
|
|
fi
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Dashboard Installation (core)
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
install_dashboard() {
|
|
local config_file
|
|
config_file=$(expected_config_file)
|
|
local temp_file="/tmp/pymc-ui-$$.tar.gz"
|
|
local is_fresh_install=true
|
|
|
|
if console_installed; then
|
|
is_fresh_install=false
|
|
fi
|
|
|
|
print_info "Downloading dashboard..."
|
|
if ! curl -fsSL -o "$temp_file" "${UI_RELEASE_URL}/latest/download/${UI_TARBALL}"; then
|
|
print_error "Download failed from ${UI_RELEASE_URL}/latest/download/${UI_TARBALL}"
|
|
rm -f "$temp_file"
|
|
return 1
|
|
fi
|
|
|
|
rm -rf "$UI_DIR"
|
|
mkdir -p "$UI_DIR"
|
|
tar -xzf "$temp_file" -C "$UI_DIR"
|
|
rm -f "$temp_file"
|
|
|
|
chown -R "$REPEATER_USER:$REPEATER_GROUP" "$CONSOLE_DIR" 2>/dev/null || true
|
|
|
|
if [[ -f "$config_file" ]] && command -v yq &>/dev/null; then
|
|
yq -i '.web //= {}' "$config_file" 2>/dev/null || true
|
|
if [[ "$is_fresh_install" == true ]]; then
|
|
yq -i ".web.web_path = \"$UI_DIR\"" "$config_file"
|
|
print_success "Dashboard installed (web_path configured)"
|
|
else
|
|
print_success "Dashboard updated (web_path preserved)"
|
|
fi
|
|
else
|
|
print_warning "Could not configure web_path automatically."
|
|
if [[ ! -f "$config_file" ]]; then
|
|
echo -e " Reason: ${YELLOW}$config_file not found${NC}."
|
|
elif ! command -v yq &>/dev/null; then
|
|
echo -e " Reason: ${YELLOW}yq is not installed${NC}."
|
|
fi
|
|
echo -e " Set it manually with:"
|
|
echo -e " ${CYAN}sudo yq -i '.web.web_path = \"$UI_DIR\"' $config_file${NC}"
|
|
echo -e " Then restart the service:"
|
|
echo -e " ${CYAN}sudo systemctl restart $(active_service_name)${NC}"
|
|
fi
|
|
|
|
local size
|
|
size=$(du -sh "$UI_DIR" 2>/dev/null | cut -f1)
|
|
print_info "Size: $size"
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Install
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
do_install() {
|
|
require_root "install" || return 1
|
|
|
|
print_banner
|
|
echo -e " ${DIM}Mode: Install waev:outpost${NC}"
|
|
echo ""
|
|
if ! preflight_check; then
|
|
print_repeater_missing_help
|
|
return 1
|
|
fi
|
|
|
|
if console_installed; then
|
|
if ! prompt_yes_no "waev:outpost already installed at $UI_DIR — reinstall?" "n"; then
|
|
print_info "Install cancelled."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
print_step 1 1 "Installing dashboard"
|
|
install_dashboard
|
|
|
|
local ip
|
|
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}waev:outpost installed!${NC}"
|
|
echo ""
|
|
echo -e " waev:outpost ${CYAN}v$(get_console_version)${NC}"
|
|
echo ""
|
|
echo -e " Dashboard: ${CYAN}http://${ip:-localhost}:8000/${NC}"
|
|
echo ""
|
|
print_service_hint
|
|
echo ""
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Upgrade
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
do_upgrade() {
|
|
require_root "upgrade" || return 1
|
|
|
|
print_banner
|
|
echo -e " ${DIM}Mode: Upgrade waev:outpost${NC}"
|
|
echo ""
|
|
|
|
# Self-update pymc_console repo and re-exec. This must run before any
|
|
# other checks: preflight_check() and console_installed() below are part
|
|
# of this same script, so if SCRIPT_DIR is stale, their results (and
|
|
# everything else in this run) can't be trusted until we're current.
|
|
# NOTE: this must run in the parent process (no subshell) so that `exec`
|
|
# replaces the running manage.sh instead of just a subshell.
|
|
if [[ -d "$SCRIPT_DIR/.git" ]]; then
|
|
print_info "Checking for waev:outpost updates..."
|
|
git config --global --add safe.directory "$SCRIPT_DIR" 2>/dev/null || true
|
|
|
|
local local_hash remote_hash
|
|
local_hash=$(git -C "$SCRIPT_DIR" rev-parse HEAD 2>/dev/null || echo "")
|
|
git -C "$SCRIPT_DIR" fetch origin 2>/dev/null || true
|
|
remote_hash=$(git -C "$SCRIPT_DIR" rev-parse origin/main 2>/dev/null || echo "")
|
|
|
|
if [[ -n "$remote_hash" && "$local_hash" != "$remote_hash" ]]; then
|
|
if git -C "$SCRIPT_DIR" pull --ff-only 2>/dev/null \
|
|
|| git -C "$SCRIPT_DIR" reset --hard origin/main 2>/dev/null; then
|
|
print_success "waev:outpost updated — restarting..."
|
|
exec "$SCRIPT_DIR/manage.sh" upgrade
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if ! preflight_check; then
|
|
print_repeater_missing_help
|
|
return 1
|
|
fi
|
|
|
|
if ! console_installed; then
|
|
print_error "waev:outpost is not installed. Run: sudo $0 install"
|
|
return 1
|
|
fi
|
|
|
|
local ui_before ui_after
|
|
ui_before=$(get_console_version)
|
|
|
|
print_step 1 1 "Updating dashboard"
|
|
install_dashboard
|
|
|
|
ui_after=$(get_console_version)
|
|
|
|
local ip
|
|
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}Upgrade Complete!${NC}"
|
|
echo ""
|
|
if [[ "$ui_before" != "$ui_after" ]]; then
|
|
echo -e " waev:outpost ${DIM}v$ui_before${NC} → ${CYAN}v$ui_after${NC}"
|
|
else
|
|
echo -e " waev:outpost ${CYAN}v$ui_after${NC}"
|
|
fi
|
|
echo ""
|
|
echo -e " Dashboard: ${CYAN}http://${ip:-localhost}:8000/${NC}"
|
|
echo ""
|
|
print_service_hint
|
|
echo ""
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Uninstall
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
# After the dashboard is removed, a web_path that still points at it would
|
|
# leave the Repeater serving nothing. Clear it only when it is ours: a value
|
|
# someone set by hand is theirs to keep.
|
|
release_web_path() {
|
|
local config_file current
|
|
if ! config_file=$(resolve_config_file); then
|
|
print_info "No Repeater config file found; nothing to clear."
|
|
return 0
|
|
fi
|
|
if ! command -v yq &>/dev/null; then
|
|
print_warning "yq is not installed; could not check web.web_path in $config_file."
|
|
echo -e " If it still points at $UI_DIR, clear it with:"
|
|
echo -e " ${CYAN}sudo yq -i 'del(.web.web_path)' $config_file${NC}"
|
|
echo -e " Then restart the service:"
|
|
echo -e " ${CYAN}sudo systemctl restart $(active_service_name)${NC}"
|
|
return 0
|
|
fi
|
|
current=$(yq '.web.web_path // ""' "$config_file" 2>/dev/null || echo "")
|
|
if [[ "$current" == "$UI_DIR" ]]; then
|
|
yq -i 'del(.web.web_path)' "$config_file"
|
|
print_success "Cleared web.web_path in $config_file (Repeater falls back to its own dashboard)"
|
|
echo -e " Restart the service to apply:"
|
|
echo -e " ${CYAN}sudo systemctl restart $(active_service_name)${NC}"
|
|
elif [[ -z "$current" ]]; then
|
|
print_info "web.web_path is not set in $config_file; nothing to clear."
|
|
else
|
|
print_warning "web.web_path in $config_file is '$current', not ours; left unchanged."
|
|
fi
|
|
}
|
|
|
|
do_uninstall() {
|
|
require_root "uninstall" || return 1
|
|
|
|
local has_console=false
|
|
local has_repeater=false
|
|
console_installed && has_console=true
|
|
repeater_installed && has_repeater=true
|
|
|
|
print_banner
|
|
echo -e " ${DIM}Detected:${NC}"
|
|
local repeater_state
|
|
if [[ "$has_repeater" == true ]]; then
|
|
repeater_state="${DIM}present (v$(get_repeater_version)) — will NOT be touched${NC}"
|
|
else
|
|
repeater_state="${DIM}not found${NC}"
|
|
fi
|
|
echo -e " Repeater: ${repeater_state}"
|
|
echo -e " Console: $([[ "$has_console" == true ]] && echo "${GREEN}found${NC} ($CONSOLE_DIR)" || echo "${DIM}not found${NC}")"
|
|
echo -e " This repo: ${GREEN}$SCRIPT_DIR${NC}"
|
|
echo ""
|
|
|
|
if [[ "$has_console" == false ]]; then
|
|
print_info "Console is not installed; nothing to remove under $CONSOLE_DIR."
|
|
fi
|
|
|
|
local will_remove=""
|
|
[[ "$has_console" == true ]] && will_remove+=" • Console dashboard ($CONSOLE_DIR)\n"
|
|
will_remove+=" • web.web_path in the Repeater config, only if it still points at the dashboard\n"
|
|
will_remove+=" • waev:outpost checkout ($SCRIPT_DIR)"
|
|
|
|
echo -e " Will remove:\n${will_remove}"
|
|
echo ""
|
|
|
|
if ! prompt_yes_no "Continue with uninstall?" "n"; then
|
|
print_info "Uninstall cancelled."
|
|
return 0
|
|
fi
|
|
|
|
local step=1
|
|
local total=2
|
|
[[ "$has_console" == true ]] && ((total++))
|
|
|
|
if [[ "$has_console" == true ]]; then
|
|
print_step $step $total "Removing waev:outpost"
|
|
rm -rf "$CONSOLE_DIR"
|
|
print_success "Removed $CONSOLE_DIR"
|
|
((step++))
|
|
fi
|
|
|
|
print_step $step $total "Releasing web.web_path"
|
|
release_web_path
|
|
((step++))
|
|
|
|
print_step $step $total "Scheduling checkout removal"
|
|
# Sanity guard: only self-delete if the path is non-empty and looks like ours
|
|
if [[ -z "$SCRIPT_DIR" || "$SCRIPT_DIR" == "/" ]]; then
|
|
print_warning "Refusing to self-delete: SCRIPT_DIR is unsafe ($SCRIPT_DIR)"
|
|
elif [[ "$(basename "$SCRIPT_DIR")" != *pymc_console* ]]; then
|
|
print_warning "Refusing to self-delete: $SCRIPT_DIR does not look like a pymc_console checkout"
|
|
else
|
|
echo -e " ${YELLOW}Will remove $SCRIPT_DIR after script exits${NC}"
|
|
# SC2064: intentional expand-now — we want the current SCRIPT_DIR captured.
|
|
# shellcheck disable=SC2064
|
|
trap "rm -rf '$SCRIPT_DIR'" EXIT
|
|
print_success "Scheduled for removal"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}Uninstall Complete${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Help / CLI
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
waev:outpost — Dashboard Manager
|
|
|
|
Usage: $0 [--yes] <command>
|
|
|
|
Commands:
|
|
install Install waev:outpost (requires openHop Repeater)
|
|
upgrade Refresh waev:outpost assets (preserves web_path)
|
|
uninstall Remove waev:outpost and this checkout
|
|
-h, --help Show this help
|
|
|
|
Flags:
|
|
--yes, -y Auto-confirm all prompts (also: ASSUME_YES=1)
|
|
|
|
Notes:
|
|
• This script manages waev:outpost only. openHop Repeater itself
|
|
(install, upgrade, uninstall, service control, logs, radio/GPIO) must
|
|
be managed using the Repeater repo's manage.sh:
|
|
https://github.com/openhop-dev/openhop_repeater
|
|
EOF
|
|
}
|
|
|
|
print_deprecated_subcommand() {
|
|
local cmd="$1"
|
|
local arg="$2"
|
|
print_error "\`$cmd $arg\` has been deprecated."
|
|
echo " The Full Stack / Console-only distinction no longer exists."
|
|
echo " This script now manages waev:outpost only."
|
|
echo " To install or manage openHop Repeater, use the Repeater repo's manage.sh."
|
|
echo ""
|
|
show_help
|
|
}
|
|
|
|
main() {
|
|
# Parse global flags (--yes / -y / --no-color) anywhere in the argument list.
|
|
local _args=()
|
|
local arg
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--yes|-y) ASSUME_YES=1 ;;
|
|
--no-color) ;; # already handled via NO_COLOR env if set; accept for symmetry
|
|
*) _args+=("$arg") ;;
|
|
esac
|
|
done
|
|
set -- "${_args[@]}"
|
|
|
|
case "${1:-}" in
|
|
-h|--help|"")
|
|
show_help
|
|
;;
|
|
install)
|
|
case "${2:-}" in
|
|
full|console)
|
|
print_deprecated_subcommand "install" "$2"
|
|
exit 1
|
|
;;
|
|
"")
|
|
do_install
|
|
;;
|
|
*)
|
|
print_error "Unknown argument: install $2"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
upgrade)
|
|
case "${2:-}" in
|
|
full|console)
|
|
print_deprecated_subcommand "upgrade" "$2"
|
|
exit 1
|
|
;;
|
|
"")
|
|
do_upgrade
|
|
;;
|
|
*)
|
|
print_error "Unknown argument: upgrade $2"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
uninstall) do_uninstall ;;
|
|
start|stop|restart|status|logs)
|
|
print_error "\`$1\` is not managed by the waev:outpost installer."
|
|
echo " Service control, status, and logs belong to openHop Repeater."
|
|
echo " Use the Repeater repo's manage.sh, or run systemctl/journalctl directly:"
|
|
echo ""
|
|
if [[ "$1" == "logs" ]]; then
|
|
echo -e " ${CYAN}sudo journalctl -u $(active_service_name) -f${NC}"
|
|
else
|
|
echo -e " ${CYAN}sudo systemctl $1 $(active_service_name)${NC}"
|
|
fi
|
|
echo ""
|
|
exit 1
|
|
;;
|
|
*)
|
|
print_error "Unknown command: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Run only when executed. Sourced (as the test harness does), the file defines
|
|
# its functions and variables and does nothing else.
|
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
main "$@"
|
|
fi
|