Initial import: bare-metal stacks, Server UI, GPU fan, tutorials.
Infrastructure configs for GMKtec K11 (Docker, vLLM, LocalAI, ComfyUI, control-plane, gpu-fan agent, Server UI with CLI/file explorer/GPU fan curve). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-shot deploy: gpu-fan agent :18090 + server-ui proxy. Run: sudo scripts/deploy-gpu-fan-fix.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STACKS_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Run as root: sudo ${SCRIPT_DIR}/deploy-gpu-fan-fix.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== GPU Fan integration deploy ==="
|
||||
|
||||
pkill -f "stacks/server-ui/.venv/bin/python app.py" 2>/dev/null || true
|
||||
pkill -f "SERVER_UI_PORT=18091" 2>/dev/null || true
|
||||
pkill -f "SERVER_UI_PORT=8092" 2>/dev/null || true
|
||||
|
||||
"${STACKS_DIR}/gpu-fan/scripts/install.sh"
|
||||
"${SCRIPT_DIR}/install.sh"
|
||||
|
||||
echo ""
|
||||
echo "=== Verification ==="
|
||||
UI_KEY="$(grep '^API_KEY=' /opt/control-plane/.env | cut -d= -f2-)"
|
||||
curl -sf "http://127.0.0.1:8091/api/gpu-fan/health" -H "X-API-Key: ${UI_KEY}" | head -c 200
|
||||
echo ""
|
||||
echo ""
|
||||
echo "Panel: http://$(hostname -I | awk '{print $1}'):8091/#gpu-fan"
|
||||
echo "API key: grep ^API_KEY= /opt/control-plane/.env"
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bash
|
||||
# Unified installer: gpu-fan (native only) + Server UI (native or Docker).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STACK_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
GPU_FAN_DIR="$(cd "${STACK_DIR}/../gpu-fan" && pwd)"
|
||||
|
||||
GPU_FAN_CHOICE=""
|
||||
SERVER_UI_CHOICE=""
|
||||
NON_INTERACTIVE=false
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: install-control-plane.sh [options]
|
||||
|
||||
Options:
|
||||
--gpu-fan=yes|no|skip Install gpu-fan host agent (native only; no Docker)
|
||||
--server-ui=native|docker|skip
|
||||
-y, --non-interactive Use defaults: gpu-fan=yes, server-ui=native
|
||||
-h, --help
|
||||
|
||||
Recommended: run without flags for interactive menu.
|
||||
|
||||
gpu-fan cannot run in Docker (NVML requires root on host). See ADR-001.
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--gpu-fan=*) GPU_FAN_CHOICE="${1#*=}"; NON_INTERACTIVE=true; shift ;;
|
||||
--server-ui=*) SERVER_UI_CHOICE="${1#*=}"; NON_INTERACTIVE=true; shift ;;
|
||||
-y|--non-interactive) NON_INTERACTIVE=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown option: $1"; usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Run as root: sudo ${SCRIPT_DIR}/install-control-plane.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
prompt_gpu_fan() {
|
||||
if [[ -n "${GPU_FAN_CHOICE}" ]]; then
|
||||
return
|
||||
fi
|
||||
echo ""
|
||||
echo "=== 1) gpu-fan (agent NVML na hoście) ==="
|
||||
echo " Sterowanie wentylatorami GPU wymaga root + NVML na hoście."
|
||||
echo " Docker NIE jest obsługiwany (ADR-001)."
|
||||
echo ""
|
||||
read -r -p "Zainstalować gpu-fan native? [Y/n]: " ans
|
||||
ans="${ans:-Y}"
|
||||
case "${ans^^}" in
|
||||
Y|YES|TAK) GPU_FAN_CHOICE="yes" ;;
|
||||
*) GPU_FAN_CHOICE="no" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
prompt_server_ui() {
|
||||
if [[ -n "${SERVER_UI_CHOICE}" ]]; then
|
||||
return
|
||||
fi
|
||||
echo ""
|
||||
echo "=== 2) Server UI (panel stacków Docker + GPU Fan) ==="
|
||||
echo " [1] Native — systemd na hoście (zalecane)"
|
||||
echo " [2] Docker — kontener + docker.sock"
|
||||
echo " [3] Pomiń"
|
||||
echo ""
|
||||
read -r -p "Wybór [1]: " ans
|
||||
ans="${ans:-1}"
|
||||
case "${ans}" in
|
||||
1) SERVER_UI_CHOICE="native" ;;
|
||||
2) SERVER_UI_CHOICE="docker" ;;
|
||||
3) SERVER_UI_CHOICE="skip" ;;
|
||||
*) echo "Nieprawidłowy wybór"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "${NON_INTERACTIVE}" == true && -z "${GPU_FAN_CHOICE}" ]]; then
|
||||
GPU_FAN_CHOICE="yes"
|
||||
fi
|
||||
if [[ "${NON_INTERACTIVE}" == true && -z "${SERVER_UI_CHOICE}" ]]; then
|
||||
SERVER_UI_CHOICE="native"
|
||||
fi
|
||||
|
||||
echo "=== GMKtec Control Plane — instalacja ==="
|
||||
bash "${SCRIPT_DIR}/setup-control-plane-env.sh"
|
||||
prompt_gpu_fan
|
||||
prompt_server_ui
|
||||
|
||||
echo ""
|
||||
echo "Plan:"
|
||||
echo " gpu-fan: ${GPU_FAN_CHOICE}"
|
||||
echo " server-ui: ${SERVER_UI_CHOICE}"
|
||||
echo ""
|
||||
|
||||
case "${GPU_FAN_CHOICE,,}" in
|
||||
yes|y|true|1)
|
||||
if [[ ! -x "${GPU_FAN_DIR}/scripts/install.sh" ]]; then
|
||||
echo "ERROR: ${GPU_FAN_DIR}/scripts/install.sh not found"
|
||||
exit 1
|
||||
fi
|
||||
echo "--- Installing gpu-fan (native) ---"
|
||||
bash "${GPU_FAN_DIR}/scripts/install.sh"
|
||||
;;
|
||||
skip|no|n|false|0)
|
||||
echo "Skipping gpu-fan."
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: invalid --gpu-fan value: ${GPU_FAN_CHOICE}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${SERVER_UI_CHOICE,,}" in
|
||||
native|systemd|1)
|
||||
echo "--- Installing Server UI (native / systemd) ---"
|
||||
# Stop Docker deployment if running
|
||||
if [[ -f "${STACK_DIR}/docker-compose.yml" ]]; then
|
||||
(cd "${STACK_DIR}" && docker compose --profile server-ui down 2>/dev/null) || true
|
||||
fi
|
||||
bash "${SCRIPT_DIR}/install.sh"
|
||||
;;
|
||||
docker|container|2)
|
||||
echo "--- Installing Server UI (Docker) ---"
|
||||
bash "${SCRIPT_DIR}/install-docker.sh"
|
||||
;;
|
||||
skip|no|3)
|
||||
echo "Skipping Server UI."
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: invalid --server-ui value: ${SERVER_UI_CHOICE}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
LAN_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
UI_PORT=8091
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo " Instalacja zakończona"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "Server UI i gpu-fan NIE pojawią się w 'docker ps' gdy działają jako systemd."
|
||||
echo "W docker ps widać tylko workloady (ComfyUI, LocalAI, NPMPlus, …)."
|
||||
echo ""
|
||||
echo "Sprawdzenie:"
|
||||
echo " systemctl status gpu-fan server-ui # native"
|
||||
echo " docker compose --profile server-ui ps # docker"
|
||||
echo " ss -tln | grep -E '8091|18090'"
|
||||
echo ""
|
||||
|
||||
if [[ "${SERVER_UI_CHOICE,,}" != "skip" ]]; then
|
||||
# shellcheck source=print-api-key-instructions.sh
|
||||
source "${SCRIPT_DIR}/print-api-key-instructions.sh"
|
||||
print_api_key_instructions "/opt/control-plane/.env" "${UI_PORT}"
|
||||
fi
|
||||
|
||||
echo "Tutorial: manual-tutorial/08-server-ui-install.md"
|
||||
echo "Klucz API (szczegóły): manual-tutorial/04a-api-key.md"
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install Server UI as Docker container (profile server-ui).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STACK_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
CONTROL_PLANE_STACK="$(cd "${STACK_DIR}/../control-plane" && pwd)"
|
||||
SERVICE_NAME="server-ui.service"
|
||||
UI_PORT="${SERVER_UI_PORT:-8091}"
|
||||
ENV_FILE="${CONTROL_PLANE_STACK}/.env"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Run as root: sudo ${SCRIPT_DIR}/install-docker.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set_env_var() {
|
||||
local file="$1" key="$2" val="$3"
|
||||
if grep -q "^${key}=" "${file}"; then
|
||||
sed -i "s|^${key}=.*|${key}=${val}|" "${file}"
|
||||
else
|
||||
echo "${key}=${val}" >> "${file}"
|
||||
fi
|
||||
}
|
||||
|
||||
detect_repo_root() {
|
||||
if [[ -n "${REPO_ROOT:-}" ]]; then
|
||||
echo "${REPO_ROOT}"
|
||||
return
|
||||
fi
|
||||
local candidate
|
||||
candidate="$(cd "${STACK_DIR}/../.." && pwd)"
|
||||
if [[ -d "${candidate}/stacks/localai" ]]; then
|
||||
echo "${candidate}"
|
||||
return
|
||||
fi
|
||||
echo "${candidate}"
|
||||
}
|
||||
|
||||
echo "=== Server UI — install (Docker) ==="
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "ERROR: docker not found. Install Docker CE first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash "${SCRIPT_DIR}/setup-control-plane-env.sh"
|
||||
|
||||
REPO_ROOT_VAL="$(detect_repo_root)"
|
||||
DOCKER_GID="$(getent group docker | cut -d: -f3 || echo 999)"
|
||||
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
cp "${CONTROL_PLANE_STACK}/.env.example" "${ENV_FILE}"
|
||||
fi
|
||||
|
||||
set_env_var "${ENV_FILE}" "REPO_ROOT" "${REPO_ROOT_VAL}"
|
||||
set_env_var "${ENV_FILE}" "SERVER_UI_HOST" "0.0.0.0"
|
||||
set_env_var "${ENV_FILE}" "SERVER_UI_PORT" "${UI_PORT}"
|
||||
set_env_var "${ENV_FILE}" "GPU_FAN_AGENT_URL" "http://host.docker.internal:18090"
|
||||
set_env_var "${ENV_FILE}" "DOCKER_GID" "${DOCKER_GID}"
|
||||
|
||||
# Disable native systemd if present
|
||||
if systemctl is-enabled "${SERVICE_NAME}" &>/dev/null; then
|
||||
echo "Disabling native ${SERVICE_NAME}..."
|
||||
systemctl disable --now "${SERVICE_NAME}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
cd "${STACK_DIR}"
|
||||
export REPO_ROOT="${REPO_ROOT_VAL}"
|
||||
export DOCKER_GID
|
||||
|
||||
echo "Building server-ui image..."
|
||||
docker compose --profile server-ui build
|
||||
|
||||
echo "Starting server-ui container..."
|
||||
docker compose --profile server-ui up -d
|
||||
|
||||
sleep 2
|
||||
|
||||
LAN_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
API_KEY_VAL="$(grep '^API_KEY=' "${ENV_FILE}" | cut -d= -f2- || true)"
|
||||
|
||||
echo ""
|
||||
echo "Runtime: Docker (profile server-ui)"
|
||||
echo "Repo mount: ${REPO_ROOT_VAL} → /repo"
|
||||
echo "Env: ${ENV_FILE}"
|
||||
echo "Container: $(docker compose --profile server-ui ps --format '{{.Name}} {{.Status}}' 2>/dev/null || docker ps --filter name=server-ui --format '{{.Names}} {{.Status}}')"
|
||||
echo ""
|
||||
echo "Web UI (LAN):"
|
||||
echo " http://${LAN_IP:-<server-ip>}:${UI_PORT}/"
|
||||
echo ""
|
||||
echo "API key:"
|
||||
echo " grep ^API_KEY= ${ENV_FILE}"
|
||||
echo ""
|
||||
|
||||
if [[ -n "${API_KEY_VAL}" ]]; then
|
||||
HEALTH="$(curl -sf "http://127.0.0.1:${UI_PORT}/api/health" 2>/dev/null || echo '{"ok":false}')"
|
||||
echo "Health: ${HEALTH}"
|
||||
GF_HEALTH="$(curl -sf "http://127.0.0.1:${UI_PORT}/api/gpu-fan/health" -H "X-API-Key: ${API_KEY_VAL}" 2>/dev/null || echo '{"ok":false}')"
|
||||
echo "GPU Fan proxy: ${GF_HEALTH}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Logs:"
|
||||
echo " docker compose --profile server-ui logs -f"
|
||||
echo "Stop:"
|
||||
echo " docker compose --profile server-ui down"
|
||||
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STACK_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
INSTALL_DIR="/opt/server-ui"
|
||||
SERVICE_NAME="server-ui.service"
|
||||
UI_PORT=8091
|
||||
CONTROL_PLANE_ENV="/opt/control-plane/.env"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Run as root: sudo ${SCRIPT_DIR}/install.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
free_ui_port() {
|
||||
pkill -f "stacks/server-ui/.venv/bin/python app.py" 2>/dev/null || true
|
||||
pkill -f "SERVER_UI_PORT=18091" 2>/dev/null || true
|
||||
local pids
|
||||
pids="$(ss -tlnp "sport = :${UI_PORT}" 2>/dev/null | grep -oP 'pid=\K[0-9]+' | sort -u || true)"
|
||||
for pid in ${pids}; do
|
||||
local cmd
|
||||
cmd="$(ps -p "${pid}" -o cmd= 2>/dev/null || true)"
|
||||
if [[ "${cmd}" == *"stacks/server-ui"* ]]; then
|
||||
echo "Stopping stale dev server-ui on :${UI_PORT} (pid ${pid})"
|
||||
kill "${pid}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# Disable Docker deployment if switching to native
|
||||
if [[ -f "${STACK_DIR}/docker-compose.yml" ]]; then
|
||||
(cd "${STACK_DIR}" && docker compose --profile server-ui down 2>/dev/null) || true
|
||||
fi
|
||||
|
||||
echo "=== Server UI — install (native / systemd) ==="
|
||||
|
||||
bash "${SCRIPT_DIR}/setup-control-plane-env.sh"
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y python3-venv python3-pip
|
||||
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
|
||||
rsync -a --delete \
|
||||
--exclude '.venv' \
|
||||
--exclude '.env' \
|
||||
--exclude '__pycache__' \
|
||||
"${STACK_DIR}/" "${INSTALL_DIR}/"
|
||||
|
||||
python3 -m venv "${INSTALL_DIR}/.venv"
|
||||
"${INSTALL_DIR}/.venv/bin/pip" install --upgrade pip -q
|
||||
"${INSTALL_DIR}/.venv/bin/pip" install -r "${INSTALL_DIR}/requirements.txt" -q
|
||||
|
||||
install -m 644 "${INSTALL_DIR}/server-ui.service" "/etc/systemd/system/${SERVICE_NAME}"
|
||||
systemctl daemon-reload
|
||||
systemctl enable "${SERVICE_NAME}"
|
||||
|
||||
free_ui_port
|
||||
systemctl restart "${SERVICE_NAME}"
|
||||
sleep 2
|
||||
|
||||
LAN_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
API_KEY_VAL="$(grep '^API_KEY=' "${CONTROL_PLANE_ENV}" | cut -d= -f2- || true)"
|
||||
|
||||
echo ""
|
||||
echo "Installed to ${INSTALL_DIR}"
|
||||
echo "Service: $(systemctl is-active "${SERVICE_NAME}" 2>/dev/null || echo unknown)"
|
||||
echo "Env: ${CONTROL_PLANE_ENV}"
|
||||
|
||||
# shellcheck source=print-api-key-instructions.sh
|
||||
source "${SCRIPT_DIR}/print-api-key-instructions.sh"
|
||||
print_api_key_instructions "${CONTROL_PLANE_ENV}" "${UI_PORT}"
|
||||
|
||||
if [[ -n "${API_KEY_VAL}" ]]; then
|
||||
HEALTH="$(curl -sf "http://127.0.0.1:${UI_PORT}/api/gpu-fan/health" -H "X-API-Key: ${API_KEY_VAL}" 2>/dev/null || echo '{"ok":false}')"
|
||||
echo "GPU Fan proxy health: ${HEALTH}"
|
||||
if ! echo "${HEALTH}" | grep -q '"ok":true'; then
|
||||
echo "WARN: gpu-fan agent may be down — run: sudo ${STACK_DIR%/server-ui}/gpu-fan/scripts/install.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Logs:"
|
||||
echo " journalctl -u ${SERVICE_NAME} -f"
|
||||
echo ""
|
||||
echo "Unified installer (gpu-fan + server-ui):"
|
||||
echo " sudo ${SCRIPT_DIR}/install-control-plane.sh"
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Print API key + browser instructions (shared by install scripts and show-api-key.sh).
|
||||
set -euo pipefail
|
||||
|
||||
print_api_key_instructions() {
|
||||
local env_file="${1:-/opt/control-plane/.env}"
|
||||
local ui_port="${2:-8091}"
|
||||
local api_key=""
|
||||
local lan_ip=""
|
||||
|
||||
if [[ -r "${env_file}" ]]; then
|
||||
api_key="$(grep '^API_KEY=' "${env_file}" 2>/dev/null | cut -d= -f2- || true)"
|
||||
elif [[ -f "${env_file}" ]]; then
|
||||
api_key="$(sudo grep '^API_KEY=' "${env_file}" 2>/dev/null | cut -d= -f2- || true)"
|
||||
fi
|
||||
|
||||
lan_ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
[[ -n "${lan_ip}" ]] || lan_ip="<IP-serwera>"
|
||||
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════════"
|
||||
echo " SERVER UI — KLUCZ API"
|
||||
echo "══════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
if [[ -z "${api_key}" ]]; then
|
||||
echo "Brak API_KEY w ${env_file}"
|
||||
echo "Uruchom: sudo bash stacks/server-ui/scripts/setup-control-plane-env.sh"
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "1. Twój klucz API (skopiuj):"
|
||||
echo ""
|
||||
echo " ${api_key}"
|
||||
echo ""
|
||||
echo "2. Otwórz panel (klucz zapisze się w przeglądarce):"
|
||||
echo ""
|
||||
echo " http://${lan_ip}:${ui_port}/?api_key=${api_key}"
|
||||
echo ""
|
||||
echo "3. Albo ręcznie:"
|
||||
echo " a) Wejdź na http://${lan_ip}:${ui_port}/"
|
||||
echo " b) Wklej klucz w pole „API Key”"
|
||||
echo " c) Kliknij „Zapisz”"
|
||||
echo " d) Kliknij „Sprawdź klucz” (powinno być: Klucz poprawny)"
|
||||
echo " e) Dopiero potem Start/Stop, CLI, Pliki, GPU Fan"
|
||||
echo ""
|
||||
echo "Plik klucza (na przyszłość):"
|
||||
echo " sudo grep ^API_KEY= ${env_file}"
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════════"
|
||||
echo ""
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
print_api_key_instructions "${1:-/opt/control-plane/.env}" "${2:-8091}"
|
||||
fi
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Restart gpu-fan + server-ui after code sync. Requires: sudo scripts/restart-stack.sh
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Run: sudo ${SCRIPT_DIR}/restart-stack.sh"
|
||||
exit 1
|
||||
fi
|
||||
systemctl restart gpu-fan
|
||||
sleep 2
|
||||
systemctl restart server-ui
|
||||
sleep 2
|
||||
echo "gpu-fan: $(systemctl is-active gpu-fan)"
|
||||
echo "server-ui: $(systemctl is-active server-ui)"
|
||||
ss -tlnp | grep -E '18090|8091' || true
|
||||
UI_KEY="$(grep '^API_KEY=' /opt/control-plane/.env | cut -d= -f2-)"
|
||||
curl -sf "http://127.0.0.1:8091/api/gpu-fan/health" -H "X-API-Key: ${UI_KEY}" || echo "health check failed"
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create /opt/control-plane/.env and migrate from legacy per-service .env files.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STACK_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
REPO_ROOT="$(cd "${STACK_DIR}/../.." && pwd)"
|
||||
CONTROL_PLANE_DIR="/opt/control-plane"
|
||||
ENV_FILE="${CONTROL_PLANE_DIR}/.env"
|
||||
EXAMPLE="${REPO_ROOT}/stacks/control-plane/.env.example"
|
||||
LEGACY_SUI="/opt/server-ui/.env"
|
||||
LEGACY_GF="/opt/gpu-fan/.env"
|
||||
LEGACY_REPO_SUI="${REPO_ROOT}/stacks/server-ui/.env"
|
||||
DEV_ENV="${REPO_ROOT}/stacks/control-plane/.env"
|
||||
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Run as root: sudo ${SCRIPT_DIR}/setup-control-plane-env.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set_env_var() {
|
||||
local file="$1" key="$2" val="$3"
|
||||
if grep -q "^${key}=" "${file}"; then
|
||||
sed -i "s|^${key}=.*|${key}=${val}|" "${file}"
|
||||
else
|
||||
echo "${key}=${val}" >> "${file}"
|
||||
fi
|
||||
}
|
||||
|
||||
get_env_var() {
|
||||
local file="$1" key="$2"
|
||||
grep "^${key}=" "${file}" 2>/dev/null | cut -d= -f2- || true
|
||||
}
|
||||
|
||||
remove_deprecated_keys() {
|
||||
local file="$1"
|
||||
[[ -f "${file}" ]] || return 0
|
||||
cp -a "${file}" "${file}.bak.${TIMESTAMP}"
|
||||
grep -v -E '^(API_KEY|GPU_FAN_AGENT_KEY)=' "${file}" > "${file}.tmp.${TIMESTAMP}" || true
|
||||
{
|
||||
echo "# API_KEY: use /opt/control-plane/.env (prod) or stacks/control-plane/.env (dev)"
|
||||
echo "# Run: sudo bash stacks/server-ui/scripts/setup-control-plane-env.sh"
|
||||
cat "${file}.tmp.${TIMESTAMP}"
|
||||
} > "${file}"
|
||||
rm -f "${file}.tmp.${TIMESTAMP}"
|
||||
echo "Removed API_KEY / GPU_FAN_AGENT_KEY from ${file} (backup: ${file}.bak.${TIMESTAMP})"
|
||||
}
|
||||
|
||||
sync_dev_env_from_prod() {
|
||||
local prod_key repo_root_val
|
||||
prod_key="$(get_env_var "${ENV_FILE}" API_KEY)"
|
||||
repo_root_val="$(get_env_var "${ENV_FILE}" REPO_ROOT)"
|
||||
[[ -n "${repo_root_val}" ]] || repo_root_val="${REPO_ROOT}"
|
||||
|
||||
if [[ ! -f "${DEV_ENV}" && -f "${EXAMPLE}" ]]; then
|
||||
cp "${EXAMPLE}" "${DEV_ENV}"
|
||||
echo "Created dev env: ${DEV_ENV}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${DEV_ENV}" ]]; then
|
||||
echo "WARN: dev env missing: ${DEV_ENV}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${prod_key}" ]]; then
|
||||
set_env_var "${DEV_ENV}" "API_KEY" "${prod_key}"
|
||||
fi
|
||||
set_env_var "${DEV_ENV}" "REPO_ROOT" "${repo_root_val}"
|
||||
local ui_port
|
||||
ui_port="$(get_env_var "${ENV_FILE}" SERVER_UI_PORT)"
|
||||
[[ -n "${ui_port}" ]] && set_env_var "${DEV_ENV}" "SERVER_UI_PORT" "${ui_port}"
|
||||
echo "Synced dev env: ${DEV_ENV} (API_KEY matches ${ENV_FILE})"
|
||||
}
|
||||
|
||||
merge_env_file() {
|
||||
local src="$1"
|
||||
[[ -f "${src}" ]] || return 0
|
||||
while IFS= read -r line || [[ -n "${line}" ]]; do
|
||||
line="${line%%#*}"
|
||||
line="$(echo "${line}" | xargs)"
|
||||
[[ -n "${line}" && "${line}" == *=* ]] || continue
|
||||
local key="${line%%=*}"
|
||||
local val="${line#*=}"
|
||||
key="$(echo "${key}" | xargs)"
|
||||
[[ -n "${val}" ]] || continue
|
||||
# Skip deprecated duplicate key
|
||||
[[ "${key}" == "GPU_FAN_AGENT_KEY" ]] && continue
|
||||
set_env_var "${ENV_FILE}" "${key}" "${val}"
|
||||
done < "${src}"
|
||||
}
|
||||
|
||||
echo "=== Control plane env setup ==="
|
||||
|
||||
mkdir -p "${CONTROL_PLANE_DIR}"
|
||||
|
||||
# Deploy env_loader for production Python imports
|
||||
if [[ -f "${REPO_ROOT}/stacks/control-plane/env_loader.py" ]]; then
|
||||
install -m 644 "${REPO_ROOT}/stacks/control-plane/env_loader.py" "${CONTROL_PLANE_DIR}/env_loader.py"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
if [[ -f "${EXAMPLE}" ]]; then
|
||||
cp "${EXAMPLE}" "${ENV_FILE}"
|
||||
echo "Created ${ENV_FILE} from example"
|
||||
else
|
||||
touch "${ENV_FILE}"
|
||||
echo "Created empty ${ENV_FILE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Migrate legacy files (non-destructive: backup first)
|
||||
SUI_KEY=""
|
||||
GF_KEY=""
|
||||
[[ -f "${LEGACY_SUI}" ]] && SUI_KEY="$(get_env_var "${LEGACY_SUI}" API_KEY)"
|
||||
[[ -f "${LEGACY_GF}" ]] && GF_KEY="$(get_env_var "${LEGACY_GF}" API_KEY)"
|
||||
|
||||
for legacy in "${LEGACY_SUI}" "${LEGACY_GF}"; do
|
||||
if [[ -f "${legacy}" ]]; then
|
||||
cp -a "${legacy}" "${legacy}.bak.${TIMESTAMP}"
|
||||
echo "Backed up ${legacy} → ${legacy}.bak.${TIMESTAMP}"
|
||||
merge_env_file "${legacy}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "${SUI_KEY}" && -n "${GF_KEY}" && "${SUI_KEY}" != "${GF_KEY}" ]]; then
|
||||
echo "WARN: API_KEY differed in legacy files — merged value: $(get_env_var "${ENV_FILE}" API_KEY)"
|
||||
echo " After install, use one key everywhere: grep ^API_KEY= ${ENV_FILE}"
|
||||
fi
|
||||
|
||||
# Defaults
|
||||
set_env_var "${ENV_FILE}" "GPU_FAN_AGENT_URL" "$(get_env_var "${ENV_FILE}" GPU_FAN_AGENT_URL || echo http://127.0.0.1:18090)"
|
||||
[[ -n "$(get_env_var "${ENV_FILE}" GPU_FAN_AGENT_URL)" ]] || set_env_var "${ENV_FILE}" "GPU_FAN_AGENT_URL" "http://127.0.0.1:18090"
|
||||
set_env_var "${ENV_FILE}" "GPU_FAN_API_HOST" "$(get_env_var "${ENV_FILE}" GPU_FAN_API_HOST || echo 127.0.0.1)"
|
||||
[[ -n "$(get_env_var "${ENV_FILE}" GPU_FAN_API_HOST)" ]] || set_env_var "${ENV_FILE}" "GPU_FAN_API_HOST" "127.0.0.1"
|
||||
set_env_var "${ENV_FILE}" "GPU_FAN_API_PORT" "$(get_env_var "${ENV_FILE}" GPU_FAN_API_PORT || echo 18090)"
|
||||
[[ -n "$(get_env_var "${ENV_FILE}" GPU_FAN_API_PORT)" ]] || set_env_var "${ENV_FILE}" "GPU_FAN_API_PORT" "18090"
|
||||
set_env_var "${ENV_FILE}" "CURVE_PATH" "$(get_env_var "${ENV_FILE}" CURVE_PATH || echo /etc/gpu-fan/curve.json)"
|
||||
[[ -n "$(get_env_var "${ENV_FILE}" CURVE_PATH)" ]] || set_env_var "${ENV_FILE}" "CURVE_PATH" "/etc/gpu-fan/curve.json"
|
||||
|
||||
# REPO_ROOT default
|
||||
if [[ -z "$(get_env_var "${ENV_FILE}" REPO_ROOT)" ]]; then
|
||||
set_env_var "${ENV_FILE}" "REPO_ROOT" "${REPO_ROOT}"
|
||||
fi
|
||||
|
||||
# Generate API_KEY if missing or placeholder
|
||||
API_KEY_VAL="$(get_env_var "${ENV_FILE}" API_KEY)"
|
||||
if [[ -z "${API_KEY_VAL}" || "${API_KEY_VAL}" == change-me* ]]; then
|
||||
KEY="$(openssl rand -hex 16)"
|
||||
set_env_var "${ENV_FILE}" "API_KEY" "${KEY}"
|
||||
echo "Generated API_KEY in ${ENV_FILE}"
|
||||
fi
|
||||
|
||||
chmod 600 "${ENV_FILE}"
|
||||
|
||||
# Always sync dev copy to production key
|
||||
sync_dev_env_from_prod
|
||||
|
||||
# Remove duplicate API keys from legacy per-service env files
|
||||
for legacy in "${LEGACY_SUI}" "${LEGACY_GF}" "${LEGACY_REPO_SUI}"; do
|
||||
if [[ -f "${legacy}" ]] && grep -qE '^(API_KEY|GPU_FAN_AGENT_KEY)=' "${legacy}" 2>/dev/null; then
|
||||
remove_deprecated_keys "${legacy}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Control plane env: ${ENV_FILE}"
|
||||
echo "Dev copy: ${DEV_ENV}"
|
||||
echo ""
|
||||
# shellcheck source=print-api-key-instructions.sh
|
||||
source "${SCRIPT_DIR}/print-api-key-instructions.sh"
|
||||
print_api_key_instructions "${ENV_FILE}" "$(get_env_var "${ENV_FILE}" SERVER_UI_PORT || echo 8091)"
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Show API key and step-by-step browser instructions for Server UI.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STACK_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
REPO_ROOT="$(cd "${STACK_DIR}/../.." && pwd)"
|
||||
PROD_ENV="/opt/control-plane/.env"
|
||||
DEV_ENV="${REPO_ROOT}/stacks/control-plane/.env"
|
||||
UI_PORT=8091
|
||||
|
||||
if [[ -f "${DEV_ENV}" ]]; then
|
||||
UI_PORT="$(grep '^SERVER_UI_PORT=' "${DEV_ENV}" 2>/dev/null | cut -d= -f2- || echo 8091)"
|
||||
fi
|
||||
|
||||
# Prefer dev copy when readable (same key after sync); else production.
|
||||
ENV_FILE="${DEV_ENV}"
|
||||
if [[ ! -r "${ENV_FILE}" ]]; then
|
||||
ENV_FILE="${PROD_ENV}"
|
||||
fi
|
||||
|
||||
# shellcheck source=print-api-key-instructions.sh
|
||||
source "${SCRIPT_DIR}/print-api-key-instructions.sh"
|
||||
print_api_key_instructions "${ENV_FILE}" "${UI_PORT}"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
STACK_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
CONTROL_PLANE_ENV="${STACK_DIR}/../control-plane/.env"
|
||||
|
||||
cd "${STACK_DIR}"
|
||||
|
||||
if [[ ! -f "${CONTROL_PLANE_ENV}" ]]; then
|
||||
echo "ERROR: ${CONTROL_PLANE_ENV} not found."
|
||||
echo "Run: cp ${STACK_DIR}/../control-plane/.env.example ${CONTROL_PLANE_ENV}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d .venv ]]; then
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install --upgrade pip -q
|
||||
.venv/bin/pip install -r requirements.txt -q
|
||||
fi
|
||||
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "${CONTROL_PLANE_ENV}"
|
||||
set +a
|
||||
|
||||
export CONTROL_PLANE_ENV="${CONTROL_PLANE_ENV}"
|
||||
|
||||
exec .venv/bin/python app.py
|
||||
Reference in New Issue
Block a user