#!/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)"