#!/usr/bin/env bash # Sync the user's pi config (from dotfiles) into docker/pi-agent/ so the # worker image bakes it. Filtered: no secrets, no machine-local state. set -euo pipefail SRC="${LVMH_PI_AGENT_DIR:-$HOME/.dotfiles/pi/agent}" DEST="${LVMH_PI_BAKE_DIR:-$(dirname "$0")/../docker/pi-agent}" # DEST must exist even without dotfiles: the worker Dockerfile COPYs it # unconditionally (a missing dir would fail the build). mkdir -p "$DEST" if [ ! -f "$SRC/settings.json" ]; then echo "rsync-pi-agent: $SRC/settings.json not found — skipping dotfiles sync" >&2 exit 0 fi rsync -a --delete \ --exclude 'auth.json' \ --exclude 'models-store.json' \ --exclude 'sessions/' \ --exclude 'cache/' \ --exclude 'npm/' \ --exclude 'git/' \ --exclude 'trust.json' \ --exclude 'mcp-*' \ --exclude 'run-history*' \ --exclude 'vibes' \ --exclude '*.log' \ --exclude 'scratch/' \ --exclude '.pi/' \ "$SRC/" "$DEST/" # Pre-seed the git: package cache: clone each pinned git: package into # docker/pi-agent/git/... where pi's package manager installs them, with # lifecycle scripts ignored (postinstalls like husky crash headless). # Keeps subagents/todo/async tooling present with no runtime network need. # Requires network access to github.com at bake time. python3 - "$DEST" <<'PY' import json, os, re, shutil, subprocess, sys from pathlib import Path dest = Path(sys.argv[1]) settings = json.loads((dest / "settings.json").read_text()) git_root = dest / "git" if git_root.exists(): shutil.rmtree(git_root) for pkg in settings.get("packages") or []: if not pkg.startswith("git:"): continue rest = pkg[4:] m = re.match(r"^(.+)@([0-9a-f]{7,40})$", rest) repo, ref = (m.group(1), m.group(2)) if m else (rest, None) target = git_root / repo target.parent.mkdir(parents=True, exist_ok=True) url = f"https://{repo}" if not repo.startswith("https://") else repo print(f"rsync-pi-agent: cloning {repo}@{ref or 'HEAD'}") subprocess.run(["git", "clone", "--quiet", url, str(target)], check=True) if ref: subprocess.run(["git", "checkout", "--quiet", ref], cwd=target, check=True) # keep .git: pi's package manager runs `git fetch` + `rev-parse` on # existing installs; a stripped dir would fail resolution. if (target / "package.json").exists(): npm = "/usr/bin/npm" if Path("/usr/bin/npm").exists() else "npm" # --ignore-scripts: prepare scripts (husky) are devDependency-based # and fail headless; extensions are plain TS needing runtime deps only. subprocess.run( [npm, "install", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"], cwd=target, check=True, capture_output=True, ) PY # Append the lvmh deployment guide to the baked APPEND_SYSTEM.md so every # spawned agent knows how to deploy hosts / push / open PRs. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" if [ -f "$SCRIPT_DIR/../docker/agent-append-system.md" ]; then printf '\n%s\n' "$(cat "$SCRIPT_DIR/../docker/agent-append-system.md")" >> "$DEST/APPEND_SYSTEM.md" fi echo "rsync-pi-agent: synced $(find "$DEST" -type f | wc -l) files from $SRC (git: packages preserved)"