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:
tomasz-syn-grzegorza
2026-07-05 12:02:04 +00:00
commit 359afb3a59
153 changed files with 18169 additions and 0 deletions
+29
View File
@@ -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
View File
@@ -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"
+107
View File
@@ -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"
+89
View File
@@ -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
View File
@@ -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
+17
View File
@@ -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
View File
@@ -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)"
+24
View File
@@ -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}"
+29
View File
@@ -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