-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy_phase7.sh
More file actions
executable file
·303 lines (257 loc) · 12 KB
/
Copy pathdeploy_phase7.sh
File metadata and controls
executable file
·303 lines (257 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#!/usr/bin/env bash
# deploy_phase7.sh — FCP v3 live cutover
#
# Run from the repo root on the Mac Mini:
# bash scripts/deploy_phase7.sh [--dry-run]
#
# What it does (in order):
# 1. Preflight checks
# 2. Back up SOUL.md files and existing chains.jsonl (if any)
# 3. Deploy fcp_hook.py to ~/.hermes/fleet/hooks/
# 4. Deploy the FCP plugin per-profile (fcp-v3/plugin/ → each profile's plugins/fcp/)
# 5. Enable plugin in each profile's config.yaml (plugins.enabled: [fcp])
# 6. Patch all four SOUL.md files (FCP v2 → v3 stub)
# 7. Bootstrap Arch if its service is not loaded in launchd
# 8. Restart all four gateways (hard cutover — all at once)
# 9. Health check: confirm all four gateways are running
#
# Rollback: set FCP_HOOK_DISABLED=1 in each profile's .env and restart gateways.
# See IMPLEMENTATION.md §7 for the full rollback procedure.
#
# Flags:
# --dry-run Run checks and show diffs; no restarts, no file writes.
set -euo pipefail
# ─── config ────────────────────────────────────────────────────────────────
HERMES_HOME="$HOME/.hermes"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PLUGIN_SRC="$REPO_ROOT/fcp-v3/plugin"
HOOK_SRC="$REPO_ROOT/fcp-v3/hooks/fcp_hook.py"
HOOKS_DST="$HERMES_HOME/fleet/hooks"
BACKUP_DIR="$HERMES_HOME/fleet/backups/phase7_$(date +%Y%m%d_%H%M%S)"
AGENTS=(oracle arch eureka aurus)
LAUNCHD_DOMAIN="gui/$(id -u)"
PLIST_DIR="$HOME/Library/LaunchAgents"
DRY_RUN=0
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=1
echo "=== DRY RUN MODE — no files will be written, no services restarted ==="
fi
# ─── helpers ───────────────────────────────────────────────────────────────
info() { echo " [INFO] $*"; }
ok() { echo " [ OK ] $*"; }
warn() { echo " [WARN] $*"; }
fail() { echo " [FAIL] $*" >&2; exit 1; }
run() {
if [[ $DRY_RUN -eq 1 ]]; then
echo " [DRY] would run: $*"
else
"$@"
fi
}
# ─── step 1: preflight checks ──────────────────────────────────────────────
echo
echo "╔══════════════════════════════════════════╗"
echo "║ FCP v3 — Phase 7 Deploy (preflight) ║"
echo "╚══════════════════════════════════════════╝"
echo
# Plugin source files
for f in plugin.yaml __init__.py; do
[[ -f "$PLUGIN_SRC/$f" ]] || fail "Missing plugin file: $PLUGIN_SRC/$f — is the repo up to date?"
done
ok "Plugin source files present ($PLUGIN_SRC)"
# fcp_hook.py
[[ -f "$HOOK_SRC" ]] || fail "fcp_hook.py not found at $HOOK_SRC"
ok "fcp_hook.py present"
# patch_soul.py
[[ -f "$REPO_ROOT/scripts/patch_soul.py" ]] || fail "patch_soul.py not found"
ok "patch_soul.py present"
# All four SOUL.md files
for agent in "${AGENTS[@]}"; do
soul="$HERMES_HOME/profiles/$agent/SOUL.md"
[[ -f "$soul" ]] || fail "Missing $soul"
done
ok "All four SOUL.md files found"
# python3
command -v python3 >/dev/null || fail "python3 not found in PATH"
ok "python3 available"
# yq or python — for config.yaml edits
if ! command -v yq &>/dev/null && ! python3 -c "import yaml" &>/dev/null; then
warn "Neither yq nor python3 yaml module found — config.yaml plugin entry must be added manually"
YAML_TOOL="manual"
else
YAML_TOOL="python3"
ok "yaml editing tool available"
fi
# ─── dry-run SOUL diff ──────────────────────────────────────────────────────
if [[ $DRY_RUN -eq 1 ]]; then
echo
echo "=== SOUL.md diff preview ==="
python3 "$REPO_ROOT/scripts/patch_soul.py" --dry-run
echo
echo "=== plugin.yaml that will be installed ==="
cat "$PLUGIN_SRC/plugin.yaml"
echo
echo "=== plugin install targets ==="
for agent in "${AGENTS[@]}"; do
echo " $HERMES_HOME/profiles/$agent/plugins/fcp/"
done
echo
echo "=== END DRY RUN — exiting without changes ==="
exit 0
fi
# ─── step 2: backup ────────────────────────────────────────────────────────
echo
echo "── Step 2: backup ─────────────────────────────────────────────"
mkdir -p "$BACKUP_DIR"
for agent in "${AGENTS[@]}"; do
src="$HERMES_HOME/profiles/$agent/SOUL.md"
cp "$src" "$BACKUP_DIR/SOUL_${agent}.md"
info "Backed up $agent SOUL.md"
done
# Backup existing chains.jsonl if present
chains="$HERMES_HOME/fleet/chains.jsonl"
[[ -f "$chains" ]] && { cp "$chains" "$BACKUP_DIR/chains.jsonl.bak"; info "Backed up chains.jsonl"; }
# Backup existing fcp_hook.py if present
existing_hook="$HOOKS_DST/fcp_hook.py"
[[ -f "$existing_hook" ]] && { cp "$existing_hook" "$BACKUP_DIR/fcp_hook.py.bak"; info "Backed up existing fcp_hook.py"; }
ok "Backups written to $BACKUP_DIR"
# ─── step 3: deploy fcp_hook.py ────────────────────────────────────────────
echo
echo "── Step 3: deploy fcp_hook.py ──────────────────────────────────"
run mkdir -p "$HOOKS_DST"
run cp "$HOOK_SRC" "$HOOKS_DST/fcp_hook.py"
ok "fcp_hook.py deployed to $HOOKS_DST/"
# ─── step 4: deploy plugin per-profile ─────────────────────────────────────
echo
echo "── Step 4: deploy plugin per-profile ───────────────────────────"
for agent in "${AGENTS[@]}"; do
dst="$HERMES_HOME/profiles/$agent/plugins/fcp"
run mkdir -p "$dst"
run cp "$PLUGIN_SRC/plugin.yaml" "$dst/plugin.yaml"
run cp "$PLUGIN_SRC/__init__.py" "$dst/__init__.py"
info "Plugin installed to $dst/"
done
ok "Plugin deployed to all four profiles"
# ─── step 5: enable plugin in config.yaml ──────────────────────────────────
echo
echo "── Step 5: enable plugin in config.yaml ────────────────────────"
for agent in "${AGENTS[@]}"; do
cfg="$HERMES_HOME/profiles/$agent/config.yaml"
if [[ ! -f "$cfg" ]]; then
warn "config.yaml not found for $agent — skipping plugin enable"
continue
fi
if grep -q "fcp" "$cfg" 2>/dev/null; then
info "$agent: fcp already present in config.yaml"
continue
fi
if [[ "$YAML_TOOL" == "python3" ]]; then
# Append plugins.enabled block, or add fcp to existing list
python3 - "$cfg" <<'PYEOF'
import sys, re
path = sys.argv[1]
text = open(path).read()
# If plugins.enabled already exists, inject fcp
if re.search(r'^plugins:\s*\n.*enabled:', text, re.MULTILINE | re.DOTALL):
# Find the enabled list and append fcp if not present
text = re.sub(
r'(plugins:\s*\n\s*enabled:\s*\n)((?:\s+-\s+\S+\n)*)',
r'\1\2 - fcp\n',
text
)
elif 'plugins:' in text:
# plugins: block exists but no enabled key
text = re.sub(r'(plugins:\s*\n)', r'\1 enabled:\n - fcp\n', text)
else:
# No plugins block at all — append to end
text = text.rstrip('\n') + '\n\nplugins:\n enabled:\n - fcp\n'
open(path, 'w').write(text)
print(f" [INFO] {path}: plugins.enabled += fcp")
PYEOF
else
warn "$agent: add 'plugins:\\n enabled:\\n - fcp' to $cfg manually"
fi
done
ok "Plugin enabled in config.yaml for all four profiles"
# ─── step 6: patch SOUL.md files ───────────────────────────────────────────
echo
echo "── Step 6: patch SOUL.md files ─────────────────────────────────"
python3 "$REPO_ROOT/scripts/patch_soul.py"
ok "SOUL.md files patched"
# ─── step 7: bootstrap Arch if needed ──────────────────────────────────────
echo
echo "── Step 7: Arch gateway ────────────────────────────────────────"
ARCH_PLIST="$PLIST_DIR/ai.hermes.gateway-arch.plist"
if ! launchctl print "$LAUNCHD_DOMAIN/ai.hermes.gateway-arch" &>/dev/null; then
if [[ -f "$ARCH_PLIST" ]]; then
info "Arch service not loaded — bootstrapping"
run launchctl bootstrap "$LAUNCHD_DOMAIN" "$ARCH_PLIST"
ok "Arch bootstrapped"
else
warn "Arch plist not found at $ARCH_PLIST"
warn "Run 'hermes -p arch gateway install' first, then re-run this script"
fi
else
ok "Arch service already in launchd domain"
fi
# ─── step 8: hard cutover — restart all four gateways ──────────────────────
echo
echo "── Step 8: hard cutover ────────────────────────────────────────"
info "Stopping all four gateways..."
for agent in "${AGENTS[@]}"; do
label="ai.hermes.gateway-$agent"
if launchctl print "$LAUNCHD_DOMAIN/$label" &>/dev/null; then
run launchctl stop "$LAUNCHD_DOMAIN/$label" || warn "Stop $label returned non-zero"
fi
done
sleep 2
info "Starting all four gateways..."
for agent in "${AGENTS[@]}"; do
label="ai.hermes.gateway-$agent"
run launchctl kickstart -k "$LAUNCHD_DOMAIN/$label" || {
warn "kickstart $label failed — check $HERMES_HOME/profiles/$agent/logs/gateway.error.log"
}
done
ok "All four gateways kicked"
# ─── step 9: health check ──────────────────────────────────────────────────
echo
echo "── Step 9: health check (15s settle) ───────────────────────────"
sleep 15
PASS_COUNT=0
FAIL_COUNT=0
for agent in "${AGENTS[@]}"; do
label="ai.hermes.gateway-$agent"
pid=$(launchctl print "$LAUNCHD_DOMAIN/$label" 2>/dev/null | awk '/pid =/ {print $3}')
if [[ -n "$pid" && "$pid" != "0" ]]; then
ok "$agent gateway running (PID $pid)"
PASS_COUNT=$((PASS_COUNT + 1))
else
warn "$agent gateway NOT running — check $HERMES_HOME/profiles/$agent/logs/gateway.error.log"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
done
echo
echo "╔══════════════════════════════════════════╗"
if [[ $FAIL_COUNT -eq 0 ]]; then
echo "║ ✓ All 4 gateways up. FCP v3 is live. ║"
echo "╚══════════════════════════════════════════╝"
echo
echo "Next: run Phase A regression tests (IMPLEMENTATION.md §7.4):"
echo " python3 scripts/regression_phase7.py"
echo
echo "Rollback if needed:"
echo " Add FCP_HOOK_DISABLED=1 to each profile's .env, restart gateways."
echo " SOUL.md originals: $BACKUP_DIR"
else
echo "║ ✗ $FAIL_COUNT gateway(s) failed to start. ║"
echo "╚══════════════════════════════════════════╝"
echo
echo "Check logs:"
for agent in "${AGENTS[@]}"; do
echo " tail -50 $HERMES_HOME/profiles/$agent/logs/gateway.error.log"
done
echo
echo "Originals backed up in: $BACKUP_DIR"
echo "To rollback plugin: rm -rf \$HERMES_HOME/profiles/<agent>/plugins/fcp for each agent"
exit 1
fi