diff --git a/QUICKSTART.md b/QUICKSTART.md index 49224b0..b20deb0 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -16,7 +16,7 @@ cd bookstack-mcp You should now be able to run this with no errors — all three paths must exist: ```bash -ls pyproject.toml bookstack_mcp/server.py deploy/lxc/install.sh +ls pyproject.toml bookstack_mcp/server.py scripts/install-lxc.sh ``` ## 1. Install @@ -24,7 +24,7 @@ ls pyproject.toml bookstack_mcp/server.py deploy/lxc/install.sh Requires Debian 12+ or Ubuntu 24.04+ (Python 3.11 or newer). ```bash -bash deploy/lxc/install.sh +bash scripts/install-lxc.sh ``` This creates the `bookstack-mcp` service user, a venv at @@ -61,7 +61,7 @@ MCP_STATIC_TOKENS= ``` Add claude.ai later by switching to `MCP_AUTH_MODE=oidc+token` and filling in -the OIDC block — see `deploy/authelia/configuration.snippet.yml`. +the OIDC block — see `examples/authelia/configuration.snippet.yml`. ## 4. Start diff --git a/deploy/authelia/configuration.snippet.yml b/examples/authelia/configuration.snippet.yml similarity index 100% rename from deploy/authelia/configuration.snippet.yml rename to examples/authelia/configuration.snippet.yml diff --git a/examples/flatcar/bookstack-mcp.bu b/examples/flatcar/bookstack-mcp.bu new file mode 100644 index 0000000..eb3bccf --- /dev/null +++ b/examples/flatcar/bookstack-mcp.bu @@ -0,0 +1,53 @@ +# Butane config for running bookstack-mcp as a container on Flatcar Container +# Linux. Convert to Ignition and hand it to the machine on first boot: +# +# butane --pretty --strict bookstack-mcp.bu > bookstack-mcp.ign +# +# Fill in the image reference below (your Gitea registry) and the values in +# the env file. systemd's EnvironmentFile= only sets the *unit's* environment +# (i.e. what the docker CLI sees), not the container's -- that's why the unit +# passes --env-file explicitly to `docker run`. +variant: flatcar +version: 1.0.0 +storage: + files: + - path: /etc/bookstack-mcp.env + mode: 0600 + contents: + inline: | + BOOKSTACK_URL=https://wiki.example.com + BOOKSTACK_TOKEN_ID= + BOOKSTACK_TOKEN_SECRET= + BOOKSTACK_READ_ONLY=true + MCP_HOST=0.0.0.0 + MCP_PORT=8080 + MCP_PATH=/mcp + MCP_AUTH_MODE=oidc+token + MCP_PUBLIC_URL=https://mcp.example.com + MCP_STATIC_TOKENS= + # See example.env at the project root for the full list of options + # (OIDC, GitHub OAuth, allowed users/groups, etc). +systemd: + units: + - name: bookstack-mcp.service + enabled: true + contents: | + [Unit] + Description=BookStack MCP Server + After=docker.service network-online.target + Requires=docker.service + Wants=network-online.target + + [Service] + ExecStartPre=-/usr/bin/docker rm -f bookstack-mcp + ExecStartPre=/usr/bin/docker pull git.example.com/owner/bookstack-mcp:latest + ExecStart=/usr/bin/docker run --rm --name bookstack-mcp \ + --env-file /etc/bookstack-mcp.env \ + -p 8080:8080 \ + git.example.com/owner/bookstack-mcp:latest + ExecStop=/usr/bin/docker stop bookstack-mcp + Restart=on-failure + RestartSec=5 + + [Install] + WantedBy=multi-user.target diff --git a/deploy/lxc/bookstack-mcp.service b/examples/lxc/bookstack-mcp.service similarity index 100% rename from deploy/lxc/bookstack-mcp.service rename to examples/lxc/bookstack-mcp.service diff --git a/scripts/install-lxc.sh b/scripts/install-lxc.sh new file mode 100644 index 0000000..c37b29c --- /dev/null +++ b/scripts/install-lxc.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# +# Install the BookStack MCP server into a Debian/Ubuntu LXC container. +# Run as root, from inside the container, in the directory containing pyproject.toml: +# +# bash scripts/install-lxc.sh +# +set -euo pipefail + +APP_DIR=/opt/bookstack-mcp +CONF_DIR=/etc/bookstack-mcp +SVC_USER=bookstack-mcp +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } +die() { printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; exit 1; } + +# Find the project root. Normally this script lives at scripts/install-lxc.sh +# so the root is one level up, but people often copy the tree around flat, so +# check the obvious candidates rather than assuming. +find_src() { + local candidate + for candidate in "$SCRIPT_DIR/.." "$SCRIPT_DIR" "$PWD"; do + if [[ -f "$candidate/pyproject.toml" && -d "$candidate/bookstack_mcp" ]]; then + (cd "$candidate" && pwd) + return 0 + fi + done + return 1 +} + +if ! SRC_DIR="$(find_src)"; then + die "Can't find the project source (needs pyproject.toml and a bookstack_mcp/ directory). + Looked in: $SCRIPT_DIR/.. , $SCRIPT_DIR , $PWD + + Get the whole project into the container, then run the script from its root: + git clone /opt/src/bookstack-mcp + cd /opt/src/bookstack-mcp + bash scripts/install-lxc.sh" +fi + +[[ $EUID -eq 0 ]] || die "Run this as root (or with sudo)." + +# The script copies SRC_DIR into APP_DIR, so they must not be the same place. +if [[ "$SRC_DIR" == "$APP_DIR" ]]; then + die "The source is $APP_DIR, which is also the install target. + Keep the source somewhere else and let the installer copy it in: + mkdir -p /opt/src && mv $APP_DIR /opt/src/bookstack-mcp + cd /opt/src/bookstack-mcp && bash scripts/install-lxc.sh" +fi + +log "Installing from $SRC_DIR" + +# --- Python version check ------------------------------------------------- +# Needs 3.11+. Debian 12/13 and Ubuntu 24.04 are fine; Debian 11 and +# Ubuntu 22.04 ship something older and need a newer python installed first. +log "Checking Python version" +if ! command -v python3 >/dev/null; then + die "python3 not installed. Try: apt install -y python3 python3-venv" +fi +PY_OK=$(python3 -c 'import sys; print(1 if sys.version_info >= (3,11) else 0)') +if [[ "$PY_OK" != "1" ]]; then + die "Python $(python3 -V | cut -d' ' -f2) found, but 3.11+ is required. + Debian 12+/Ubuntu 24.04+ work out of the box. On older releases install a + newer python3 first (e.g. via deadsnakes) and re-run." +fi + +# --- Dependencies --------------------------------------------------------- +log "Installing system packages" +export DEBIAN_FRONTEND=noninteractive +# A single broken third-party repo shouldn't abort the whole install, so the +# update is advisory; only the install itself is allowed to fail the script. +apt-get update -qq || log "apt update reported errors, continuing" +apt-get install -y -qq python3-venv python3-pip ca-certificates >/dev/null + +# --- Service user --------------------------------------------------------- +if ! id -u "$SVC_USER" >/dev/null 2>&1; then + log "Creating service user $SVC_USER" + useradd --system --home-dir "$APP_DIR" --shell /usr/sbin/nologin "$SVC_USER" +fi + +# --- Application ---------------------------------------------------------- +log "Installing application to $APP_DIR" +mkdir -p "$APP_DIR" +rm -rf "$APP_DIR/bookstack_mcp" +cp -r "$SRC_DIR/bookstack_mcp" "$APP_DIR/" +cp "$SRC_DIR/pyproject.toml" "$APP_DIR/" + +if [[ ! -d "$APP_DIR/venv" ]]; then + log "Creating virtualenv" + python3 -m venv "$APP_DIR/venv" +fi + +log "Installing Python dependencies (this takes a minute)" +"$APP_DIR/venv/bin/pip" install --quiet --upgrade pip +"$APP_DIR/venv/bin/pip" install --quiet "$APP_DIR" + +chown -R "$SVC_USER:$SVC_USER" "$APP_DIR" + +# --- Configuration -------------------------------------------------------- +# Prefer a filled-in .env if one exists, otherwise fall back to the template. +ENV_SRC="" +for candidate in "$SRC_DIR/.env" "$SRC_DIR/.env.example"; do + [[ -f "$candidate" ]] && { ENV_SRC="$candidate"; break; } +done +[[ -n "$ENV_SRC" ]] || die "Can't find $SRC_DIR/.env or $SRC_DIR/.env.example to seed the config from." + +mkdir -p "$CONF_DIR" +# An empty file counts as "not configured" -- a previous failed run can leave +# a zero-byte config behind, and silently keeping it is worse than replacing it. +if [[ ! -s "$CONF_DIR/env" ]]; then + log "Creating $CONF_DIR/env from $(basename "$ENV_SRC")" + # systemd EnvironmentFile does not understand quotes or inline comments the + # way a shell does, so strip comments and blank lines out of the source. + # Write to a temp file first so a failure here can't leave an empty config. + grep -vE '^\s*(#|$)' "$ENV_SRC" > "$CONF_DIR/env.tmp" + mv "$CONF_DIR/env.tmp" "$CONF_DIR/env" + NEW_CONFIG=1 +else + log "Keeping existing $CONF_DIR/env" + NEW_CONFIG=0 +fi +chown root:"$SVC_USER" "$CONF_DIR/env" +chmod 640 "$CONF_DIR/env" + +# --- systemd -------------------------------------------------------------- +log "Installing systemd unit" +UNIT_SRC="" +for candidate in "$SRC_DIR/examples/lxc/bookstack-mcp.service" "$SCRIPT_DIR/bookstack-mcp.service"; do + [[ -f "$candidate" ]] && { UNIT_SRC="$candidate"; break; } +done +[[ -n "$UNIT_SRC" ]] || die "Can't find bookstack-mcp.service" +install -m 644 "$UNIT_SRC" /etc/systemd/system/bookstack-mcp.service +systemctl daemon-reload +systemctl enable bookstack-mcp >/dev/null + +echo +log "Install complete." +echo +if [[ "$NEW_CONFIG" == "1" ]]; then + cat <