Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<gh-user>/dotfiles.git` → `~/src/dotfiles`
- `https://github.com/<gh-user>/bin.git` → `~/src/bin`
- `https://github.com/<gh-user>/llm-config.git` → `~/src/llm-config`
- `https://github.com/<gh-user>/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.
Expand Down
8 changes: 7 additions & 1 deletion RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -635,8 +635,14 @@ Non-secret repo sync baseline:
- `https://github.com/<gh-user>/dotfiles.git` → `~/src/dotfiles`
- `https://github.com/<gh-user>/bin.git` → `~/src/bin`
- `https://github.com/<gh-user>/llm-config.git` → `~/src/llm-config`
- `https://github.com/<gh-user>/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):
Expand Down
45 changes: 44 additions & 1 deletion cloud-init/user-data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 ---
Expand Down
1 change: 1 addition & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading