#!/usr/bin/env bash set -euo pipefail # POS: system dashboard — Web dashboard: start/stop/status/config/url # POS_FLAGS: --help # POS_SUBCMDS: start stop status port config url # POS_CONFIG: dashboard | dashboard.env | DASHBOARD_PORT=num:Port the dashboard listens on (default 8080) | DASHBOARD_HOST=:Bind address (default 0.0.0.0) source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh" # ── Config & constants ────────────────────────────────────────── CONFIG_DIR="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}" CONFIG_FILE="$CONFIG_DIR/dashboard.env" SERVICE="pos-dashboard.service" USER_SYSTEMD_DIR="${USER_SYSTEMD_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user}" DASHBOARD_DIR="/usr/local/share/linux_post_install/dashboard" DASHBOARD_SCRIPT="$DASHBOARD_DIR/app.py" # ── load config ───────────────────────────────────────────────── load_config() { [ -f "$CONFIG_FILE" ] || return 0 local k v while IFS='=' read -r k v; do [ -n "$k" ] || continue case "$k" in \#*) continue ;; esac v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}" if [ -z "${!k:-}" ]; then export "$k"="$v" fi done < <(grep -E '^[A-Z_]+=' "$CONFIG_FILE" || true) } # ── defaults ──────────────────────────────────────────────────── load_config DASHBOARD_PORT="${DASHBOARD_PORT:-8080}" DASHBOARD_HOST="${DASHBOARD_HOST:-0.0.0.0}" # ── usage ─────────────────────────────────────────────────────── usage() { cat < Web dashboard for the pos toolkit — browser-based UI for managing entertainment, Telegram, Docker, and system health. Commands: start Install and start the systemd user service stop Stop, disable, and remove the service status Show service state and the dashboard URL port Change the dashboard port (restarts the service) config Edit DASHBOARD_PORT, DASHBOARD_HOST url Print the dashboard URL Config: $CONFIG_FILE (DASHBOARD_PORT, DASHBOARD_HOST) Service: $SERVICE (systemd user unit) Dir: $DASHBOARD_DIR The dashboard binds to DASHBOARD_HOST:DASHBOARD_PORT (default 0.0.0.0:8080). Access it over Tailscale at: http://:/ Examples: pos dashboard start pos dashboard port 9090 pos dashboard status pos dashboard url pos dashboard config EOF exit 0 } # ── deps guard (before --help, per convention) ────────────────── command -v python3 &>/dev/null || err "python3 not found — install with: sudo apt install python3" # ── help ──────────────────────────────────────────────────────── case "${1:-}" in -h|--help) usage ;; esac # ── start ─────────────────────────────────────────────────────── cmd_start() { # Check flask is available if ! python3 -c "import flask" 2>/dev/null; then err "python3-flask not installed — install with: sudo apt install python3-flask" fi # Check dashboard directory exists if [ ! -d "$DASHBOARD_DIR" ]; then err "Dashboard directory not found: $DASHBOARD_DIR — run install.sh first" fi if [ ! -f "$DASHBOARD_SCRIPT" ]; then err "Dashboard app not found: $DASHBOARD_SCRIPT — run install.sh first" fi # Ensure config exists if [ ! -f "$CONFIG_FILE" ]; then mkdir -p "$CONFIG_DIR" cp "${BASH_SOURCE[0]%/bin/pos-dashboard}/../config/dashboard.env" "$CONFIG_FILE" 2>/dev/null \ || warn "No config template found — create $CONFIG_FILE manually" chmod 600 "$CONFIG_FILE" 2>/dev/null || true fi # Install systemd user service mkdir -p "$USER_SYSTEMD_DIR" local runner if [ -x /usr/local/bin/pos-dashboard ]; then runner=/usr/local/bin/pos-dashboard else runner="$(cd "$(dirname "$0")/.." && pwd)/bin/pos-dashboard" warn "using repo path $runner — re-run 'install.sh' so the service survives a deleted repo" fi cat >"$USER_SYSTEMD_DIR/$SERVICE" </dev/null 2>&1; then if ! loginctl show-user "$(id -un)" 2>/dev/null | grep -q '^Linger=yes'; then warn "enable linger so the dashboard survives logout: sudo loginctl enable-linger $(id -un)" fi fi } # ── stop ──────────────────────────────────────────────────────── cmd_stop() { if [ ! -f "$USER_SYSTEMD_DIR/$SERVICE" ]; then warn "no dashboard service installed ($SERVICE)" return 0 fi systemctl --user disable --now "$SERVICE" 2>/dev/null || true rm -f "$USER_SYSTEMD_DIR/$SERVICE" systemctl --user daemon-reload log "Dashboard service disabled" } # ── status ────────────────────────────────────────────────────── cmd_status() { load_config DASHBOARD_PORT="${DASHBOARD_PORT:-8080}" DASHBOARD_HOST="${DASHBOARD_HOST:-0.0.0.0}" if systemctl --user is-active --quiet "$SERVICE" 2>/dev/null; then echo "service: running" else echo "service: not running" fi if systemctl --user is-enabled "$SERVICE" >/dev/null 2>&1; then echo "autostart: enabled (starts on login)" else echo "autostart: disabled" fi echo "config: $CONFIG_FILE" echo "dir: $DASHBOARD_DIR" cmd_url } # ── port ──────────────────────────────────────────────────────── cmd_port() { local new_port="${1:-}" if [ -z "$new_port" ]; then err "Usage: pos dashboard port " fi # Validate port is a number between 1 and 65535 if ! [[ "$new_port" =~ ^[0-9]+$ ]] || [ "$new_port" -lt 1 ] || [ "$new_port" -gt 65535 ]; then err "Invalid port: $new_port — must be a number between 1 and 65535" fi # Ensure config file exists if [ ! -f "$CONFIG_FILE" ]; then mkdir -p "$CONFIG_DIR" cp "${BASH_SOURCE[0]%/bin/pos-dashboard}/../config/dashboard.env" "$CONFIG_FILE" 2>/dev/null \ || echo -e "DASHBOARD_PORT=8080\nDASHBOARD_HOST=0.0.0.0" > "$CONFIG_FILE" chmod 600 "$CONFIG_FILE" fi # Update port in config file if grep -q '^DASHBOARD_PORT=' "$CONFIG_FILE"; then sed -i "s/^DASHBOARD_PORT=.*/DASHBOARD_PORT=$new_port/" "$CONFIG_FILE" else echo "DASHBOARD_PORT=$new_port" >> "$CONFIG_FILE" fi log "Port changed to $new_port in $CONFIG_FILE" # Update the running variable so cmd_url shows the new port DASHBOARD_PORT="$new_port" # Restart service if running if systemctl --user is-active --quiet "$SERVICE" 2>/dev/null; then systemctl --user restart "$SERVICE" log "Dashboard service restarted on port $new_port" else warn "Dashboard is not running — start it with: pos dashboard start" fi cmd_url } # ── config ────────────────────────────────────────────────────── cmd_config() { # If pos config-ui.sh is available, use the interactive editor local cfg_ui_script for p in \ "$(dirname "$0")/../lib/config-ui.sh" \ "$(dirname "$0")/config-ui.sh" \ "/usr/local/bin/config-ui.sh"; do if [ -f "$p" ]; then cfg_ui_script="$p" break fi done if [ -n "${cfg_ui_script:-}" ]; then source "$cfg_ui_script" 2>/dev/null cfg_ui "dashboard" return fi # Fallback: reload and show current values load_config DASHBOARD_PORT="${DASHBOARD_PORT:-8080}" DASHBOARD_HOST="${DASHBOARD_HOST:-0.0.0.0}" echo "Dashboard config: $CONFIG_FILE" echo printf ' DASHBOARD_PORT=%s\n' "$DASHBOARD_PORT" printf ' DASHBOARD_HOST=%s\n' "$DASHBOARD_HOST" echo echo "Edit with: pos config dashboard" } # ── url ───────────────────────────────────────────────────────── cmd_url() { local ip="" # Prefer Tailscale IP if command -v tailscale &>/dev/null; then ip="$(tailscale ip -4 2>/dev/null || true)" fi # Fallback to first non-loopback IP if [ -z "$ip" ]; then ip="$(hostname -I 2>/dev/null | awk '{print $1}')" fi if [ -z "$ip" ]; then ip="localhost" fi echo "http://${ip}:${DASHBOARD_PORT}/" } # ── dispatch ──────────────────────────────────────────────────── case "${1:-}" in start) cmd_start ;; stop) cmd_stop ;; status) cmd_status ;; port) cmd_port "${2:-}" ;; config) cmd_config ;; url) cmd_url ;; "") usage ;; *) err "Unknown subcommand '$1' (see --help)" ;; esac