Written after the service was live, so every command and every number here was
run against the real deployment rather than assumed:
* DEPLOY.md records the things a rebuild needs and git does not hold — the
Redis ACL user (and why +getdel is the one to notice), the GCP Secret
Manager entry, the DNS record, and the three coordinated edits vmalert
needs because it has no ConfigMap auto-discovery.
* It also records two blockers rather than hiding them: Woodpecker is NOT
activated (the token in rdev-credentials returns 401), so pushes do not
deploy yet and the Kaniko Job is the interim path; and the host is
hush.threesix.ai rather than hush.orchard9.ai because orchard9.ai is on
GoDaddy and no GoDaddy credential exists anywhere I can reach.
* OPERATIONS.md is one section per alert, plus the failure modes that are not
alerts — chiefly that "gone" cannot distinguish already-revealed from
expired from LRU-evicted, on purpose, so the operator's default reading of
an unexpected "gone" is that the secret is compromised and should be
rotated.
* scripts/logs.sh and alerts-check.sh verify rather than assert:
alerts-check asks vmalert what it actually loaded AND checks each rule's
series exists, because a rule reading a metric nothing exports can never
fire and looks exactly like a healthy service.
* scripts/smoke.sh is a real client — it generates a key, encrypts, posts only
ciphertext, reveals, decrypts, then asserts the second reveal is 410, that
three GETs did not consume the secret, that missing and malformed ids are
indistinguishable, and that a plaintext field is refused.
install-mcp.sh proves the MCP handshake before writing any config, backs up
mcp.json, and rewrites only hush's entry — a config pointing at a broken server
surfaces as an opaque host-side connect failure, which is worth one extra check
to avoid.
80 lines
2.9 KiB
Bash
Executable File
80 lines
2.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build hush-mcp and register it with omp.
|
|
#
|
|
# The server runs LOCALLY and does the AES-256-GCM itself. That is the point: if
|
|
# hushd served an MCP endpoint and encrypted server-side, the server could read
|
|
# every secret created through MCP, and hush's guarantee would hold for browser
|
|
# users while quietly not holding for agent users. This binary is a peer of the
|
|
# browser, not of the server.
|
|
#
|
|
# Idempotent: re-running rebuilds the binary and rewrites only hush's entry in
|
|
# ~/.omp/agent/mcp.json, leaving every other server alone.
|
|
set -euo pipefail
|
|
|
|
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
|
|
MCP_JSON="${MCP_JSON:-$HOME/.omp/agent/mcp.json}"
|
|
BASE_URL="${HUSH_BASE_URL:-https://hush.threesix.ai}"
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
|
echo "building hush-mcp"
|
|
mkdir -p "$BIN_DIR"
|
|
(cd "$ROOT" && go build -trimpath -ldflags="-s -w" -o "$BIN_DIR/hush-mcp" ./cmd/hush-mcp)
|
|
echo " installed $BIN_DIR/hush-mcp"
|
|
|
|
# Prove the binary speaks MCP before wiring it in. A config pointing at a broken
|
|
# server surfaces as an opaque host-side connect failure, so the handshake is
|
|
# checked here where the error is legible.
|
|
echo "verifying the MCP handshake"
|
|
HANDSHAKE=$(printf '%s\n' \
|
|
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}' \
|
|
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
|
|
| HUSH_BASE_URL="$BASE_URL" "$BIN_DIR/hush-mcp" 2>/dev/null)
|
|
echo "$HANDSHAKE" | python3 -c '
|
|
import json, sys
|
|
tools = None
|
|
for line in sys.stdin:
|
|
m = json.loads(line)
|
|
if m.get("id") == 1:
|
|
print(" protocol", m["result"]["protocolVersion"], "server", m["result"]["serverInfo"]["name"])
|
|
if m.get("id") == 2:
|
|
tools = [t["name"] for t in m["result"]["tools"]]
|
|
if not tools:
|
|
sys.exit(" the server did not answer tools/list")
|
|
print(" tools:", ", ".join(tools))
|
|
'
|
|
|
|
echo "registering with omp at $MCP_JSON"
|
|
mkdir -p "$(dirname "$MCP_JSON")"
|
|
[ -f "$MCP_JSON" ] || printf '{"mcpServers":{}}\n' > "$MCP_JSON"
|
|
# Back up before touching a config that may hold other servers.
|
|
cp "$MCP_JSON" "$MCP_JSON.bak.$(date +%Y%m%d%H%M%S)"
|
|
|
|
BIN="$BIN_DIR/hush-mcp" BASE="$BASE_URL" TARGET="$MCP_JSON" python3 - <<'PY'
|
|
import json, os
|
|
|
|
target = os.environ["TARGET"]
|
|
with open(target) as f:
|
|
cfg = json.load(f)
|
|
|
|
cfg.setdefault("$schema",
|
|
"https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json")
|
|
servers = cfg.setdefault("mcpServers", {})
|
|
|
|
# stdio, not http: the encryption has to happen on this machine.
|
|
servers["hush"] = {
|
|
"type": "stdio",
|
|
"command": os.environ["BIN"],
|
|
"env": {"HUSH_BASE_URL": os.environ["BASE"]},
|
|
"timeout": 20000,
|
|
}
|
|
|
|
with open(target, "w") as f:
|
|
json.dump(cfg, f, indent=2)
|
|
f.write("\n")
|
|
|
|
print(" servers now configured:", ", ".join(sorted(servers)))
|
|
PY
|
|
|
|
echo
|
|
echo "done. Restart omp to pick up the new server, then: hush_create / hush_reveal"
|