#!/usr/bin/env bash # The opencode config, generated rather than linked. # # Every other personal destination is a symlink or a one-time copy, so getting # one wrong is visible and cheap to undo. This one is written whole by # setup/scripts/link-mcp, and it is the only file in the set that carries # secrets -- which is the reason it is generated at all. Three properties are # worth pinning: # # 1. The tracked template is the source of truth and holds no secret. It is # readable by anyone who finds the repository, so a token in it is a # disclosure rather than a mistake. # 2. The generation is faithful: the template's keys survive, the MCP block # appears, and a leading ~/ is resolved so the output is readable. # 3. Nothing is destroyed and nothing churns. A file somebody wrote by hand # goes to config/old/, and running the stage again does not fill it up. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" linker="$repo_dir/setup/scripts/link-mcp" template="$repo_dir/user/agents/opencode/config.json" servers="$repo_dir/user/agents/mcp/servers" findings=() note() { findings+=("$1"); } [[ -x "$linker" ]] || { printf 'opencode config contract: %s is not executable\n' "$linker" >&2; exit 1; } # ── 1. The template holds no secret ────────────────────────────────────────── if [[ ! -r "$template" ]]; then note 'user/agents/opencode/config.json is missing, so nothing generates an opencode config' else jq -e . "$template" >/dev/null 2>&1 \ || note 'user/agents/opencode/config.json is not valid JSON' if grep -qE 'BEGIN [A-Z ]*PRIVATE KEY|sk-ant-[A-Za-z0-9]|ghp_[A-Za-z0-9]{20}|xox[baprs]-[A-Za-z0-9]' "$template" 2>/dev/null; then note 'user/agents/opencode/config.json carries something that looks like a token' fi fi [[ -r "$servers" ]] || note 'user/agents/mcp/servers is missing, so no MCP servers can be registered' # ── 2 and 3, against a throwaway home ──────────────────────────────────────── work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT checkout="$work/Panama" mkdir -p "$checkout/setup/scripts" "$checkout/user/agents/mcp/bin" "$checkout/user/agents/opencode" cp "$linker" "$checkout/setup/scripts/link-mcp" cp "$template" "$checkout/user/agents/opencode/config.json" printf '#!/usr/bin/env bash\nexit 0\n' >"$checkout/user/agents/mcp/bin/gamma" chmod +x "$checkout/user/agents/mcp/bin/gamma" cat >"$checkout/user/agents/mcp/servers" <<'FIXTURE' # a comment, then a row that needs a token, one that needs none, and a stdio one alpha http https://alpha.example/mcp ALPHA_TOKEN beta http https://beta.example/mcp gamma stdio bin/gamma FIXTURE printf 'ALPHA_TOKEN=sekrit-token-value\n' >"$checkout/user/agents/mcp/env" # link-mcp registers through whichever runtime it finds, and gives up entirely # when it finds neither. A stub is enough to prove the config generation. mkdir -p "$work/bin" printf '#!/usr/bin/env bash\nexit 0\n' >"$work/bin/opencode" chmod +x "$work/bin/opencode" home="$work/home" state="$work/state" old="$checkout/config/old" # Every XDG directory is pinned to the throwaway home. XDG_CONFIG_HOME is # usually set in the environment this runs from, and leaving it alone would send # the generated config at the real ~/.config/opencode rather than at the fixture. mkdir -p "$home/.config/opencode" "$state/panama" run() { HOME="$home" XDG_CONFIG_HOME="$home/.config" XDG_STATE_HOME="$state" \ PANAMA_PATH="$checkout" PATH="$work/bin:$PATH" \ "$checkout/setup/scripts/link-mcp" >"$work/log" 2>&1 } generated="$home/.config/opencode/opencode.jsonc" # A file somebody wrote by hand is there before this stage ever runs. printf '{"model": "someone-elses-hand-edit"}\n' >"$generated" printf 'yes\n' >"$state/panama/user-content" if ! run; then note "link-mcp failed against a throwaway home: $(tail -1 "$work/log")" fi # ── The hand-edit is preserved, not deleted ────────────────────────────────── if ! grep -rq 'someone-elses-hand-edit' "$old" 2>/dev/null; then note 'a config written by hand was not preserved in config/old/' fi # ── The generation is faithful ─────────────────────────────────────────────── body="$(sed '/^\/\//d' "$generated" 2>/dev/null)" jq -e . <<<"$body" >/dev/null 2>&1 \ || note 'the generated opencode config is not valid JSON once its header comment is stripped' grep -qF 'generated by setup/scripts/link-mcp' "$generated" 2>/dev/null \ || note 'the generated opencode config does not say who wrote it or where to edit instead' if ! jq -e --arg home "$home" '.instructions | index($home + "/.claude/rules/*.md")' <<<"$body" >/dev/null 2>&1; then note 'the generated opencode config did not resolve the ~/ rules path to this home' fi jq -e '.permissions | length > 0' <<<"$body" >/dev/null 2>&1 \ || note 'the template permissions did not reach the generated opencode config' jq -e '.mcp.alpha.headers.Authorization == "sekrit-token-value"' <<<"$body" >/dev/null 2>&1 \ || note 'a token-carrying server was registered without its Authorization header' jq -e '.mcp.beta | has("headers") | not' <<<"$body" >/dev/null 2>&1 \ || note 'a server that needs no token was given a header anyway' jq -e --arg launcher "$checkout/user/agents/mcp/bin/gamma" '.mcp.gamma.command == [$launcher]' <<<"$body" >/dev/null 2>&1 \ || note 'a stdio server was not registered against its launcher in this checkout' # The file holds tokens, so it is private. Checked on the file rather than on # the umask, because the umask is not what a later reader sees. mode="$(stat -c '%a' "$generated" 2>/dev/null)" [[ "$mode" == 600 ]] || note "the generated opencode config is mode $mode, not 600" # The template is readable by anybody who finds the repository. The tokens live # in the generated file and in user/agents/mcp/env, never here. if grep -qF 'sekrit-token-value' "$template" 2>/dev/null; then note 'the tracked opencode template carries an MCP token' fi # ── Re-running replaces, and does not churn ────────────────────────────────── before="$(find "$old" -type f 2>/dev/null | wc -l)" run after="$(find "$old" -type f 2>/dev/null | wc -l)" [[ "$before" == "$after" ]] \ || note 're-running link-mcp buried its own output in config/old/, so it grows every upgrade' grep -qF 'generated by setup/scripts/link-mcp' "$generated" 2>/dev/null \ || note 're-running link-mcp did not rewrite the config it generates' # A machine that never said yes is left alone, the same gate every other # personal destination obeys. rm -f "$generated" printf 'no\n' >"$state/panama/user-content" run [[ ! -e "$generated" ]] \ || note 'an opencode config was generated on a machine that never said yes' if (( ${#findings[@]} > 0 )); then printf 'opencode config contract: %d finding(s)\n' "${#findings[@]}" >&2 printf ' - %s\n' "${findings[@]}" >&2 exit 1 fi printf 'opencode config contract: PASS\n'