feat(scripts): lxc-mcp-install env-var overrides + repo clone fallback

- All prompted values overridable via env vars (MCP_API_KEY,
  MCP_SERVICE_KEY, BACKEND_URL, INSTALL_DIR, etc.).
- Clone the repo into INSTALL_DIR if it isn't already present, so the
  script can be fetched and run directly inside a fresh LXC created by
  the community-scripts/ProxmoxVE helper (no manual git clone first).
- README: clarify the Proxmox flow (community-scripts creates the LXC,
  user runs this script inside it).
This commit is contained in:
Pouzor
2026-05-27 22:27:54 +02:00
parent fc765fa255
commit 529c75a175
2 changed files with 50 additions and 37 deletions
+3 -1
View File
@@ -223,7 +223,9 @@ docker compose up -d mcp
# MCP server is now listening on http://<your-homelab-ip>:8001 # MCP server is now listening on http://<your-homelab-ip>:8001
``` ```
> **Proxmox LXC / bare-metal (no Docker):** run `sudo bash scripts/lxc-mcp-install.sh`. > **Proxmox LXC / bare-metal (no Docker):** create the LXC via
> [community-scripts/ProxmoxVE](https://github.com/community-scripts/ProxmoxVE) (or any
> Debian/Ubuntu LXC), then inside it run `sudo bash scripts/lxc-mcp-install.sh`.
> Installs a `homelable-mcp` systemd service, prompts for `MCP_API_KEY` / `MCP_SERVICE_KEY` > Installs a `homelable-mcp` systemd service, prompts for `MCP_API_KEY` / `MCP_SERVICE_KEY`
> (auto-generated if you press Enter), and skips prompts if `mcp/.env` already exists. > (auto-generated if you press Enter), and skips prompts if `mcp/.env` already exists.
+41 -30
View File
@@ -1,16 +1,27 @@
#!/bin/bash #!/bin/bash
# Install / enable the Homelable MCP server as a systemd service on a # Install / enable the Homelable MCP server as a systemd service.
# Proxmox LXC (or any Debian/Ubuntu host) where Docker is NOT used. #
# Run interactively as root, inside an LXC or any Debian/Ubuntu host.
# Typical Proxmox VE flow: create the LXC via the community-scripts/ProxmoxVE
# helper, then run this script inside that LXC.
# #
# Idempotent: re-running is safe. If mcp/.env already exists, the script # Idempotent: re-running is safe. If mcp/.env already exists, the script
# keeps it untouched and only refreshes the venv + systemd unit. # keeps it untouched and only refreshes the venv + systemd unit.
# #
# Usage (run as root inside the LXC): # Optional env vars (override defaults / skip the matching prompt):
# bash scripts/lxc-mcp-install.sh # INSTALL_DIR repo root (default: /opt/homelable)
# INSTALL_DIR=/srv/homelable bash scripts/lxc-mcp-install.sh # REPO_URL clone URL if $INSTALL_DIR is empty (default: https://github.com/Pouzor/homelable.git)
# REPO_REF branch/tag/commit when cloning (default: main)
# SERVICE_USER systemd User= (default: homelable)
# MCP_PORT listen port (default: 8001)
# MCP_API_KEY client → MCP key (default: prompt, auto-gen on empty)
# MCP_SERVICE_KEY MCP → backend key (default: prompt, auto-gen on empty; must match backend .env)
# BACKEND_URL backend base URL (default: http://127.0.0.1:8000)
set -euo pipefail set -euo pipefail
INSTALL_DIR="${INSTALL_DIR:-/opt/homelable}" INSTALL_DIR="${INSTALL_DIR:-/opt/homelable}"
REPO_URL="${REPO_URL:-https://github.com/Pouzor/homelable.git}"
REPO_REF="${REPO_REF:-main}"
SERVICE_USER="${SERVICE_USER:-homelable}" SERVICE_USER="${SERVICE_USER:-homelable}"
SERVICE_NAME="homelable-mcp" SERVICE_NAME="homelable-mcp"
MCP_PORT="${MCP_PORT:-8001}" MCP_PORT="${MCP_PORT:-8001}"
@@ -20,48 +31,57 @@ log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; } warn() { printf '\033[1;33m!!\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; } fail() { printf '\033[1;31mxx\033[0m %s\n' "$*" >&2; exit 1; }
# --- preflight ---------------------------------------------------------------
[[ $EUID -eq 0 ]] || fail "Run as root (sudo bash $0)." [[ $EUID -eq 0 ]] || fail "Run as root (sudo bash $0)."
log "Installing OS dependencies (git, python3-venv, curl)"
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
git python3 python3-venv python3-pip curl iproute2 >/dev/null
MCP_DIR="$INSTALL_DIR/mcp" MCP_DIR="$INSTALL_DIR/mcp"
[[ -d "$MCP_DIR" ]] || fail "Not found: $MCP_DIR — clone the repo to $INSTALL_DIR first, or set INSTALL_DIR=..." if [[ ! -d "$MCP_DIR" ]]; then
log "Cloning $REPO_URL ($REPO_REF) → $INSTALL_DIR"
mkdir -p "$(dirname "$INSTALL_DIR")"
git clone --depth 1 --branch "$REPO_REF" "$REPO_URL" "$INSTALL_DIR"
fi
[[ -f "$MCP_DIR/requirements.txt" ]] || fail "Missing $MCP_DIR/requirements.txt — repo layout unexpected." [[ -f "$MCP_DIR/requirements.txt" ]] || fail "Missing $MCP_DIR/requirements.txt — repo layout unexpected."
if ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${MCP_PORT}$"; then if ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${MCP_PORT}$"; then
warn "Port $MCP_PORT already in use. If it's a previous $SERVICE_NAME instance this is fine; otherwise abort and free the port." warn "Port $MCP_PORT already in use. If it's a previous $SERVICE_NAME instance this is fine; otherwise abort and free the port."
fi fi
# --- packages ----------------------------------------------------------------
log "Installing OS dependencies (python3-venv, curl)"
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3 python3-venv python3-pip curl >/dev/null
# --- service user ------------------------------------------------------------
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
log "Creating service user '$SERVICE_USER'" log "Creating service user '$SERVICE_USER'"
useradd --system --home "$INSTALL_DIR" --shell /usr/sbin/nologin "$SERVICE_USER" useradd --system --home "$INSTALL_DIR" --shell /usr/sbin/nologin "$SERVICE_USER"
fi fi
# --- .env --------------------------------------------------------------------
ENV_FILE="$MCP_DIR/.env" ENV_FILE="$MCP_DIR/.env"
gen_key() { python3 -c "import secrets;print('$1' + secrets.token_hex(24))"; }
if [[ -f "$ENV_FILE" ]]; then if [[ -f "$ENV_FILE" ]]; then
log ".env already present at $ENV_FILE — keeping user values (prompts skipped)" log ".env already present at $ENV_FILE — keeping existing values"
else else
[[ -f "$MCP_DIR/.env.example" ]] || fail "Missing $MCP_DIR/.env.example" [[ -f "$MCP_DIR/.env.example" ]] || fail "Missing $MCP_DIR/.env.example"
log "No .env found — generating one (press Enter to accept defaults)" log "No .env found — generating one (press Enter to accept defaults)"
gen_key() { python3 -c "import secrets;print('$1' + secrets.token_hex(24))"; } api_key="${MCP_API_KEY:-}"
default_api_key="$(gen_key mcp_sk_)" svc_key="${MCP_SERVICE_KEY:-}"
default_svc_key="$(gen_key svc_)" backend_url="${BACKEND_URL:-}"
if [[ -z "$api_key" ]]; then
default_api_key="$(gen_key mcp_sk_)"
read -rp "MCP_API_KEY (client → MCP) [default: auto-generate]: " api_key read -rp "MCP_API_KEY (client → MCP) [default: auto-generate]: " api_key
api_key="${api_key:-$default_api_key}" api_key="${api_key:-$default_api_key}"
fi
if [[ -z "$svc_key" ]]; then
default_svc_key="$(gen_key svc_)"
read -rp "MCP_SERVICE_KEY (MCP → backend, must match backend .env) [default: auto-generate]: " svc_key read -rp "MCP_SERVICE_KEY (MCP → backend, must match backend .env) [default: auto-generate]: " svc_key
svc_key="${svc_key:-$default_svc_key}" svc_key="${svc_key:-$default_svc_key}"
fi
if [[ -z "$backend_url" ]]; then
read -rp "BACKEND_URL [$DEFAULT_BACKEND_URL]: " backend_url read -rp "BACKEND_URL [$DEFAULT_BACKEND_URL]: " backend_url
backend_url="${backend_url:-$DEFAULT_BACKEND_URL}" backend_url="${backend_url:-$DEFAULT_BACKEND_URL}"
fi
umask 077 umask 077
cat >"$ENV_FILE" <<EOF cat >"$ENV_FILE" <<EOF
@@ -70,15 +90,9 @@ MCP_SERVICE_KEY=$svc_key
BACKEND_URL=$backend_url BACKEND_URL=$backend_url
EOF EOF
log "Wrote $ENV_FILE (mode 600)" log "Wrote $ENV_FILE (mode 600)"
warn "If the backend runs elsewhere, set the SAME MCP_SERVICE_KEY in its .env."
if [[ "$svc_key" == "$default_svc_key" ]]; then
warn "MCP_SERVICE_KEY was auto-generated. Update backend .env with the SAME value:"
warn " MCP_SERVICE_KEY=$svc_key"
warn "Then restart the backend so MCP can authenticate."
fi
fi fi
# --- venv --------------------------------------------------------------------
VENV="$MCP_DIR/.venv" VENV="$MCP_DIR/.venv"
if [[ ! -d "$VENV" ]]; then if [[ ! -d "$VENV" ]]; then
log "Creating venv at $VENV" log "Creating venv at $VENV"
@@ -91,7 +105,6 @@ log "Installing Python deps"
chown -R "$SERVICE_USER":"$SERVICE_USER" "$MCP_DIR" chown -R "$SERVICE_USER":"$SERVICE_USER" "$MCP_DIR"
chmod 600 "$ENV_FILE" chmod 600 "$ENV_FILE"
# --- systemd unit ------------------------------------------------------------
UNIT="/etc/systemd/system/${SERVICE_NAME}.service" UNIT="/etc/systemd/system/${SERVICE_NAME}.service"
log "Writing $UNIT" log "Writing $UNIT"
cat >"$UNIT" <<EOF cat >"$UNIT" <<EOF
@@ -117,7 +130,6 @@ systemctl daemon-reload
systemctl enable --now "$SERVICE_NAME" systemctl enable --now "$SERVICE_NAME"
systemctl restart "$SERVICE_NAME" systemctl restart "$SERVICE_NAME"
# --- healthcheck -------------------------------------------------------------
log "Waiting for MCP to come up on :$MCP_PORT" log "Waiting for MCP to come up on :$MCP_PORT"
ok=0 ok=0
for _ in 1 2 3 4 5 6 7 8 9 10; do for _ in 1 2 3 4 5 6 7 8 9 10; do
@@ -132,7 +144,6 @@ else
log "MCP server is up." log "MCP server is up."
fi fi
# --- summary -----------------------------------------------------------------
LXC_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" LXC_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
API_KEY_VALUE="$(grep -E '^MCP_API_KEY=' "$ENV_FILE" | cut -d= -f2-)" API_KEY_VALUE="$(grep -E '^MCP_API_KEY=' "$ENV_FILE" | cut -d= -f2-)"