From eb1e1a6662890c0f0c0cea337ce6ddf84f90403c Mon Sep 17 00:00:00 2001 From: brfid Date: Wed, 4 Mar 2026 17:50:36 -0500 Subject: [PATCH] bootstrap oldspeak MCP repo and local wrappers --- CHANGELOG.md | 1 + README.md | 12 +++++++++++ RUNBOOK.md | 8 ++++++- cloud-init/user-data.yaml | 45 ++++++++++++++++++++++++++++++++++++++- docs/ARCHITECTURE.md | 1 + 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2dc24..f1b3a8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ semantic version tags. ### Recently Completed - Wired Dropbox FUSE mount via rclone: rclone config stored as SecureString at `/edcloud/rclone_config` in SSM; cloud-init fetches it on every rebuild and enables `rclone-dropbox.service` (user systemd, `~/Dropbox` mount); `RCLONE_CONFIG_SSM_PARAMETER` added to `config.py`. +- Added oldspeak MCP bootstrap integration while keeping app code in a separate repo: cloud-init now best-effort syncs `~/src/oldspeak` (via `gh` auth path), bootstraps a local venv/install + spaCy model, and installs local wrappers (`~/.local/bin/oldspeak-mcp-stdio`, `~/.local/bin/oldspeak-mcp-http`) for on-host Cline/Claude Code usage. Docs updated in README, RUNBOOK, and ARCHITECTURE. ## [2026-03-03] diff --git a/README.md b/README.md index ae8d502..934ec27 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,18 @@ LazyVim compatibility: **Baseline:** Docker, Portainer, Node.js, Python, and dev tooling are defined in `cloud-init/user-data.yaml`. +Bootstrap repo sync (when `gh` auth is available on the instance): + +- `https://github.com//dotfiles.git` → `~/src/dotfiles` +- `https://github.com//bin.git` → `~/src/bin` +- `https://github.com//llm-config.git` → `~/src/llm-config` +- `https://github.com//oldspeak.git` → `~/src/oldspeak` + +For local MCP usage on edcloud, cloud-init also installs best-effort wrappers: + +- `~/.local/bin/oldspeak-mcp-stdio` (recommended for Cline/Claude Code on-host) +- `~/.local/bin/oldspeak-mcp-http [port]` (localhost HTTP transport, optional) + For full technical detail, see: - `RUNBOOK.md` for durable host baseline, rebuild workflow, backup/restore operations, and operator procedures. diff --git a/RUNBOOK.md b/RUNBOOK.md index 3f0b7c9..e1a137f 100644 --- a/RUNBOOK.md +++ b/RUNBOOK.md @@ -17,7 +17,7 @@ This file is the stable operator procedure guide. Open items: - [x] Add a safe rebuild workflow (`snapshot -> reprovision -> verify`) as a single documented operator path. (`edc reprovision` now prints a post-run reminder to run `edc verify`.) -- [ ] Improve automatic repo loading: currently dotfiles/bin/llm-config cloning depends on gh auth during cloud-init; consider making repo list configurable and/or adding explicit clone step to provision workflow (e.g., `edc provision --sync-repos`). +- [ ] Improve automatic repo loading: currently dotfiles/bin/llm-config/oldspeak cloning depends on gh auth during cloud-init; consider making repo list configurable and/or adding explicit clone step to provision workflow (e.g., `edc provision --sync-repos`). - [ ] Evaluate a secure operator login workflow that starts from one memorized string without weakening Tailscale/AWS MFA controls. - [ ] Centralize default SSH username in repo config (for example `edcloud/config.py`) and have `edc ssh`/`edc verify` read that value. - [ ] Keep snapshot spend under soft cap `$2/month`; adjust DLM retention (`edc backup-policy apply --daily-keep N --weekly-keep M --monthly-keep K`) if exceeded. @@ -635,8 +635,14 @@ Non-secret repo sync baseline: - `https://github.com//dotfiles.git` → `~/src/dotfiles` - `https://github.com//bin.git` → `~/src/bin` - `https://github.com//llm-config.git` → `~/src/llm-config` + - `https://github.com//oldspeak.git` → `~/src/oldspeak` - If `~/src/dotfiles/install.sh` exists and is executable, it is run. - Executable files in `~/src/bin` are symlinked into `~/.local/bin`. +- If `~/src/oldspeak/pyproject.toml` exists, cloud-init performs best-effort local + MCP bootstrap (`.venv`, editable install, and spaCy model download). +- Cloud-init also installs best-effort local wrappers for on-host MCP clients: + - `~/.local/bin/oldspeak-mcp-stdio` + - `~/.local/bin/oldspeak-mcp-http [port]` (localhost bind) - Keep these repos non-secret; secrets still belong in SSM/local private files. AWS-native policy operations (recommended baseline): diff --git a/cloud-init/user-data.yaml b/cloud-init/user-data.yaml index c881c9a..6febb27 100644 --- a/cloud-init/user-data.yaml +++ b/cloud-init/user-data.yaml @@ -539,7 +539,7 @@ runcmd: fi ' - # --- Pull non-secret personal repos (dotfiles/bin/llm-config) --- + # --- Pull non-secret personal repos (dotfiles/bin/llm-config/oldspeak) --- - | runuser -u ubuntu -- bash -lc ' set -euo pipefail @@ -576,6 +576,7 @@ runcmd: sync_repo dotfiles sync_repo bin sync_repo llm-config + sync_repo oldspeak # Link bashrc from dotfiles (includes git-prompt PS1) if [ -x "$HOME/src/dotfiles/install.sh" ]; then @@ -585,6 +586,48 @@ runcmd: if [ -d "$HOME/src/bin" ]; then find "$HOME/src/bin" -maxdepth 1 -type f -perm -u+x -exec ln -sfn {} "$HOME/.local/bin/" \; fi + + # Best-effort oldspeak bootstrap for local MCP clients (Cline/Claude Code). + if [ -f "$HOME/src/oldspeak/pyproject.toml" ]; then + python3 -m venv "$HOME/src/oldspeak/.venv" || true + "$HOME/src/oldspeak/.venv/bin/python" -m pip install --upgrade pip || true + "$HOME/src/oldspeak/.venv/bin/pip" install -e "$HOME/src/oldspeak" || true + "$HOME/src/oldspeak/.venv/bin/python" -m spacy download en_core_web_sm || true + fi + + install -m 0755 -d "$HOME/.local/bin" + + cat > "$HOME/.local/bin/oldspeak-mcp-stdio" <<"STDIO_EOF" +#!/usr/bin/env bash +set -euo pipefail +ROOT="${HOME}/src/oldspeak" +VENV="${ROOT}/.venv" + +if [ ! -x "${VENV}/bin/python" ]; then + echo "oldspeak venv not found at ${VENV}. Re-run bootstrap in ${ROOT}." >&2 + exit 1 +fi + +exec "${VENV}/bin/python" -m oldspeak.server +STDIO_EOF + + cat > "$HOME/.local/bin/oldspeak-mcp-http" <<"HTTP_EOF" +#!/usr/bin/env bash +set -euo pipefail +ROOT="${HOME}/src/oldspeak" +VENV="${ROOT}/.venv" +PORT="${1:-8765}" + +if [ ! -x "${VENV}/bin/fastmcp" ]; then + echo "fastmcp not found at ${VENV}/bin/fastmcp. Re-run bootstrap in ${ROOT}." >&2 + exit 1 +fi + +cd "${ROOT}" +exec "${VENV}/bin/fastmcp" run oldspeak/server.py --transport http --host 127.0.0.1 --port "${PORT}" +HTTP_EOF + + chmod 0755 "$HOME/.local/bin/oldspeak-mcp-stdio" "$HOME/.local/bin/oldspeak-mcp-http" ' # --- Ensure ubuntu always has baseline aliases, even with custom dotfiles --- diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b1fecc3..b63fdf0 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -37,6 +37,7 @@ edcloud/ - **Durable state volume + disposable root:** host runtime is replaceable; durable data lives under `/opt/edcloud/state`. - **CLI-managed snapshot queue:** a single flat pool capped at 3 snapshots, enforced by the CLI. Every snapshot trigger runs `prune(3) → snapshot → prune(3)` so drift self-heals within one cycle. Triggers: `edc up` (on-start, fire-and-forget), `edc provision`/`edc reprovision`/`edc destroy` (blocking, pre-destructive-op). DLM (`backup-policy`) remains available but is not wired automatically. - **SSM-backed runtime secrets:** secrets stay out of git and host bootstrap payloads. The instance IAM role grants `ssm:GetParameter` on `/edcloud/*`. Three parameters are consumed automatically by cloud-init: `tailscale_auth_key` (required), `github_token` (optional, authenticates `gh`), and `rclone_config` (optional, writes rclone config and enables the Dropbox FUSE mount). +- **Separate app/infrastructure repos:** application MCP code (for example `oldspeak`) remains in its own repository (`~/src/oldspeak` on-host). edcloud bootstraps checkout/update and local wrappers (`oldspeak-mcp-stdio`, `oldspeak-mcp-http`) without vendoring app code into this infra repo. - **Cloud-init as baseline contract:** reproducible host/tooling baseline is codified in `cloud-init/user-data.yaml`. - **CLI-first operations model:** commands must remain safe/repeatable from lightweight ARM/Linux operator nodes.