CB-624: add scripts/rename-checkout.sh (rename checkout claude-bridge -> fleetd) #143
Executable
+484
@@ -0,0 +1,484 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Rename this checkout from ~/LTMS/claude-bridge to ~/LTMS/fleetd.
|
||||
#
|
||||
# The project was renamed upstream (fleet/fleetd on git.ltms.dev), but the checkout keeps its old
|
||||
# name. Renaming it by hand misses surfaces, because the absolute path is written down in more
|
||||
# places than the directory itself:
|
||||
#
|
||||
# 1. The launchd agent (~/Library/LaunchAgents/dev.ltms.bridged.plist) hard-codes absolute paths
|
||||
# to the wrapper script and the jar. Move the checkout without fixing it and the daemon never
|
||||
# starts again after a reboot.
|
||||
# 2. Every git worktree carries a `.git` FILE with an ABSOLUTE pointer into the main checkout
|
||||
# (`gitdir: /…/claude-bridge/.git/worktrees/<id>`). Rename the main checkout and every
|
||||
# worktree breaks at once ("not a git repository").
|
||||
# 3. Claude Code keys saved sessions and memory by a slug derived from the working directory,
|
||||
# with `/` and `.` replaced by `-`. Move the checkout and it computes a NEW slug, finds
|
||||
# nothing, and silently starts empty — every saved session is still on disk, just unreachable.
|
||||
# 4. bridged/bridged.yaml is gitignored, so it survives only in the live checkout and holds
|
||||
# `fleet.leaders.*.cwd` and `configDir` — both absolute, both old.
|
||||
# 5. A running daemon holds the old paths open. Moving underneath it corrupts its state, so the
|
||||
# daemon stops FIRST, and the script waits for the process to actually exit rather than
|
||||
# assuming (same trap as redeploy-bridged.sh).
|
||||
# 6. An old daemon that never died looks identical from the outside, so verification polls
|
||||
# /healthz AND demands a FRESH "bridged listening" log line dated after the restart, anchored
|
||||
# to a line-count marker taken before anything moved.
|
||||
#
|
||||
# Three more surfaces hold the old path but this script REPORTS them and never rewrites them.
|
||||
# That is a decision, not an oversight:
|
||||
#
|
||||
# A. ~/.claude.json — Claude Code's GLOBAL config for every project on this machine (the
|
||||
# `projects` map keys projects by their absolute path). It is written by live sessions while
|
||||
# this script could run, and one bad edit there breaks every project on the machine, not just
|
||||
# this repo. The entry holds trust (`hasTrustDialogAccepted`), onboarding state,
|
||||
# `enabledMcpjsonServers`, `allowedTools` and `lastSessionId`.
|
||||
# B. ~/.config/herdr/session.json — live session records of a RUNNING process. Rewriting it
|
||||
# underneath herdr corrupts its state.
|
||||
# C. JetBrains options (recentProjects.xml, trusted-paths.xml under any IntellIJIDEA* version) —
|
||||
# the IDE rewrites these itself when the project is reopened at the new path, so editing them
|
||||
# by hand is both unnecessary and racy with a possibly-running IDE.
|
||||
#
|
||||
# For each of the three, --check prints the exact manual follow-up instead of fixing it.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/rename-checkout.sh --check # report what holds the old path; changes nothing
|
||||
# scripts/rename-checkout.sh # stop daemon, move everything, restart, verify
|
||||
# scripts/rename-checkout.sh --yes # skip the confirmation prompt
|
||||
#
|
||||
# Run this FROM INSIDE OR OUTSIDE the checkout — it operates on fixed absolute paths, never on
|
||||
# $PWD, because after the rename $PWD itself is gone.
|
||||
#
|
||||
# Exits non-zero on any failure. A failed pre-flight never touches the daemon.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OLD_PATH="/Users/dai.ha/LTMS/claude-bridge"
|
||||
NEW_PATH="/Users/dai.ha/LTMS/fleetd"
|
||||
BRIDGED_SUB="bridged" # daemon lives at <checkout>/bridged/
|
||||
JAR_NAME="bridged.jar"
|
||||
OUT_NAME="bridged.out"
|
||||
|
||||
CC_INSTANCE="ltms"
|
||||
CC_PROJECTS="$HOME/.ccs/instances/$CC_INSTANCE/projects"
|
||||
|
||||
# Report-only surfaces (see header, items A-C) — never rewritten by apply mode.
|
||||
CLAUDE_JSON="$HOME/.claude.json"
|
||||
HERDR_SESSION="$HOME/.config/herdr/session.json"
|
||||
JETBRAINS_ROOT="$HOME/Library/Application Support/JetBrains"
|
||||
JETBRAINS_FILES=(recentProjects.xml trusted-paths.xml)
|
||||
|
||||
LAUNCHD_LABEL='dev.ltms.bridged'
|
||||
LAUNCHD_PLIST="$HOME/Library/LaunchAgents/$LAUNCHD_LABEL.plist"
|
||||
|
||||
HEALTH='http://127.0.0.1:8765/healthz'
|
||||
PATTERN="target/$JAR_NAME"
|
||||
STOP_WAIT=30 # seconds to wait for a clean daemon exit before reporting failure
|
||||
HEALTH_WAIT=60 # seconds to wait for /healthz to answer after start
|
||||
|
||||
ASSUME_YES=0; CHECK_ONLY=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--check) CHECK_ONLY=1 ;;
|
||||
--yes|-y) ASSUME_YES=1 ;;
|
||||
-h|--help) sed -n '3,40p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
||||
*) echo "unknown option: $arg (try --help)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
say() { printf '\n\033[1m== %s\033[0m\n' "$*"; }
|
||||
ok() { printf ' ok %s\n' "$*"; }
|
||||
warn() { printf ' WARN %s\n' "$*"; }
|
||||
die() { printf '\n FAIL %s\n\n' "$*" >&2; exit 1; }
|
||||
|
||||
running_pid() { pgrep -f "$PATTERN" || true; }
|
||||
launchd_installed() { [ -f "$LAUNCHD_PLIST" ]; }
|
||||
launchd_loaded() { launchctl list "$LAUNCHD_LABEL" >/dev/null 2>&1; }
|
||||
|
||||
# Claude Code derives its per-project directory name from the working directory by replacing
|
||||
# "/" and "." with "-". Derived here, never hardcoded, so the script stays correct if either
|
||||
# endpoint path ever changes again.
|
||||
cc_slug() { printf '%s' "$1" | tr '/.' '-'; }
|
||||
|
||||
OLD_OUT="$OLD_PATH/$BRIDGED_SUB/$OUT_NAME"
|
||||
NEW_OUT="$NEW_PATH/$BRIDGED_SUB/$OUT_NAME"
|
||||
|
||||
# Files under a directory that contain the literal old path, with a hit count each. Read-only.
|
||||
# Skips .git internals, build output and node_modules: they are generated, huge, and repaired by
|
||||
# rebuilding rather than rewriting.
|
||||
scan_old_path_in() {
|
||||
local root="$1"
|
||||
[ -e "$root" ] || return 0
|
||||
grep -rIlF --exclude-dir=.git --exclude-dir=target --exclude-dir=node_modules \
|
||||
"$OLD_PATH" "$root" 2>/dev/null || true
|
||||
}
|
||||
|
||||
report_hits() {
|
||||
local label="$1" root="$2" f n total=0
|
||||
while IFS= read -r f; do
|
||||
n="$(grep -cF "$OLD_PATH" "$f" 2>/dev/null || true)"
|
||||
printf ' %5d %s\n' "$n" "${f#"$root"/}"
|
||||
total=$((total + 1))
|
||||
done < <(scan_old_path_in "$root")
|
||||
if [ "$total" -eq 0 ]; then
|
||||
ok "$label: no files reference the old path"
|
||||
else
|
||||
warn "$label: $total file(s) reference the old path:"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Report-only surfaces (header items A-C). Counted here, NEVER rewritten by
|
||||
# apply mode — the header comment explains why each is left alone. Both
|
||||
# --check (section 6) and apply mode's closing summary call this, so the two
|
||||
# reports can never drift apart.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
count_old_path_in_file() { # <file> <fixed-string-pattern> -> stdout: hit count (0 if file absent)
|
||||
if [ ! -f "$1" ]; then echo 0; return; fi
|
||||
# "no match" makes grep exit 1 and, under `set -o pipefail`, the whole pipeline too — so the
|
||||
# assignment needs its own `|| true`; wc still prints the correct 0.
|
||||
local n
|
||||
n="$(grep -oF -- "$2" "$1" 2>/dev/null | wc -l | tr -d ' ')" || true
|
||||
printf '%s' "${n:-0}"
|
||||
}
|
||||
|
||||
report_only_surfaces() {
|
||||
local n
|
||||
|
||||
# A. Claude Code global config — the per-project entry keyed by absolute path.
|
||||
if [ -f "$CLAUDE_JSON" ]; then
|
||||
n="$(count_old_path_in_file "$CLAUDE_JSON" "$OLD_PATH")"
|
||||
KEY="unknown"
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
KEY="$(python3 -c 'import json,sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
print("present" if sys.argv[2] in d.get("projects", {}) else "absent")' \
|
||||
"$CLAUDE_JSON" "$OLD_PATH" 2>/dev/null)" || KEY="unknown"
|
||||
fi
|
||||
warn "A. $CLAUDE_JSON — old-path occurrences: $n; projects[\"$OLD_PATH\"] entry: $KEY"
|
||||
echo " holds trust/onboarding state, enabledMcpjsonServers, allowedTools, lastSessionId."
|
||||
echo " FOLLOW-UP: quit every Claude Code session first (this file is rewritten on exit),"
|
||||
echo " then rename the key projects[\"$OLD_PATH\"] -> projects[\"$NEW_PATH\"], keeping the"
|
||||
echo " entry's contents. If skipped: the trust dialog appears once at the new path and"
|
||||
echo " .mcp.json servers start disabled until re-enabled."
|
||||
else
|
||||
ok "A. $CLAUDE_JSON absent — nothing to report"
|
||||
fi
|
||||
|
||||
# B. herdr live session records.
|
||||
n="$(count_old_path_in_file "$HERDR_SESSION" "$OLD_PATH")"
|
||||
if [ "$n" -gt 0 ]; then
|
||||
warn "B. $HERDR_SESSION — $n occurrence(s) of the old path (live herdr state)"
|
||||
echo " FOLLOW-UP: stop the daemon/herdr FIRST, then edit the file (or leave it: sessions"
|
||||
echo " started from the new checkout afterwards use the new path). Never edit while herdr runs."
|
||||
elif [ -f "$HERDR_SESSION" ]; then
|
||||
ok "B. $HERDR_SESSION does not reference the old path"
|
||||
else
|
||||
ok "B. $HERDR_SESSION absent"
|
||||
fi
|
||||
|
||||
# C. JetBrains recent projects + trusted paths, any IntelliJIdea version.
|
||||
# JetBrains stores these paths as its own "$USER_HOME$/..." MACRO, not as the literal absolute
|
||||
# path, so a plain absolute-path grep reports 0 hits on files that are in fact full of them.
|
||||
# Count both forms; report the macro form when that is where the hits live.
|
||||
FOUND=0
|
||||
JET_MACRO='$USER_HOME$'"${OLD_PATH#"$HOME"}" # e.g. $USER_HOME$/LTMS/claude-bridge
|
||||
if [ -d "$JETBRAINS_ROOT" ]; then
|
||||
for VDIR in "$JETBRAINS_ROOT"/IntelliJIdea*/; do
|
||||
[ -d "$VDIR" ] || continue
|
||||
VDIR="${VDIR%/}"
|
||||
for F in "${JETBRAINS_FILES[@]}"; do
|
||||
FPATH="$VDIR/options/$F"
|
||||
ABS_N="$(count_old_path_in_file "$FPATH" "$OLD_PATH")"
|
||||
MACRO_N="$(count_old_path_in_file "$FPATH" "$JET_MACRO")"
|
||||
n=$((ABS_N + MACRO_N))
|
||||
if [ "$n" -gt 0 ]; then
|
||||
warn "C. ${FPATH#"$HOME"/} — $n occurrence(s) of the old path ($MACRO_N as the \${USER_HOME} macro, $ABS_N literal)"
|
||||
FOUND=$((FOUND + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
if [ "$FOUND" -eq 0 ]; then
|
||||
ok "C. no JetBrains IntelliJIdea* options file references the old path"
|
||||
else
|
||||
echo " FOLLOW-UP: open the project at $NEW_PATH in IntelliJ — the IDE rewrites"
|
||||
echo " recentProjects.xml and trusted-paths.xml itself (it asks to trust once). Editing"
|
||||
echo " these by hand is unnecessary and racy with a running IDE."
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------- --check
|
||||
|
||||
if [ "$CHECK_ONLY" = 1 ]; then
|
||||
say "--check (read-only): who holds $OLD_PATH"
|
||||
|
||||
say "1. files containing the old absolute path"
|
||||
if [ -d "$OLD_PATH" ]; then
|
||||
report_hits "checkout ($OLD_PATH)" "$OLD_PATH"
|
||||
else
|
||||
warn "checkout $OLD_PATH does not exist — skipping its scan"
|
||||
fi
|
||||
if launchd_installed; then
|
||||
report_hits "installed launchd plist ($LAUNCHD_PLIST)" "$LAUNCHD_PLIST"
|
||||
else
|
||||
warn "installed launchd plist not found at $LAUNCHD_PLIST"
|
||||
fi
|
||||
report_hits "Claude Code instance data ($CC_PROJECTS)" "$CC_PROJECTS"
|
||||
|
||||
say "2. launchd agent"
|
||||
if launchd_installed; then
|
||||
ok "installed: $LAUNCHD_PLIST"
|
||||
ARGS="$(/usr/libexec/PlistBuddy -c 'Print :ProgramArguments' "$LAUNCHD_PLIST" 2>/dev/null | grep -F '/' || true)"
|
||||
if printf '%s' "$ARGS" | grep -qF "$OLD_PATH"; then
|
||||
warn "plist ProgramArguments point at the OLD path:"
|
||||
printf '%s\n' "$ARGS" | sed 's/^/ /'
|
||||
elif [ -n "$ARGS" ]; then
|
||||
ok "plist ProgramArguments do not reference the old path"
|
||||
else
|
||||
warn "could not read ProgramArguments from the plist"
|
||||
fi
|
||||
else
|
||||
warn "launchd agent NOT installed (daemon would be started by hand only)"
|
||||
fi
|
||||
if launchd_loaded; then
|
||||
ok "loaded: launchd supervises '$LAUNCHD_LABEL'"
|
||||
else
|
||||
warn "launchd agent not loaded"
|
||||
fi
|
||||
|
||||
say "3. Claude Code project-state directory"
|
||||
OLD_SLUG="$(cc_slug "$OLD_PATH")"; NEW_SLUG="$(cc_slug "$NEW_PATH")"
|
||||
if [ -d "$CC_PROJECTS/$OLD_SLUG" ]; then
|
||||
ok "old slug EXISTS: $CC_PROJECTS/$OLD_SLUG ($(find "$CC_PROJECTS/$OLD_SLUG" -type f | wc -l | tr -d ' ') files)"
|
||||
else
|
||||
warn "old slug missing: $CC_PROJECTS/$OLD_SLUG"
|
||||
fi
|
||||
if [ -e "$CC_PROJECTS/$NEW_SLUG" ]; then
|
||||
warn "new slug ALREADY EXISTS: $CC_PROJECTS/$NEW_SLUG — moving over it needs a decision"
|
||||
else
|
||||
ok "new slug absent (clean): $CC_PROJECTS/$NEW_SLUG"
|
||||
fi
|
||||
|
||||
say "4. git worktrees"
|
||||
if [ -d "$OLD_PATH" ]; then
|
||||
git -C "$OLD_PATH" worktree list || warn "'git worktree list' failed"
|
||||
BROKEN=0
|
||||
while IFS= read -r wt; do
|
||||
[ "$wt" = "$OLD_PATH" ] && continue # the main checkout moves WITH the repo
|
||||
GITFILE="$wt/.git"
|
||||
if [ -f "$GITFILE" ] && grep -qF "$OLD_PATH" "$GITFILE" 2>/dev/null; then
|
||||
warn "worktree $wt — .git pointer references the old path (breaks on rename)"
|
||||
BROKEN=$((BROKEN + 1))
|
||||
fi
|
||||
done < <(git -C "$OLD_PATH" worktree list --porcelain 2>/dev/null | sed -n 's/^worktree //p')
|
||||
if [ "$BROKEN" -eq 0 ]; then
|
||||
ok "no external worktree .git pointer references the old path"
|
||||
fi
|
||||
else
|
||||
warn "checkout $OLD_PATH missing — cannot enumerate worktrees"
|
||||
fi
|
||||
|
||||
say "5. daemon process"
|
||||
PID="$(running_pid)"
|
||||
if [ -n "$PID" ]; then
|
||||
ok "daemon running, pid $PID"
|
||||
CMD="$(ps -p "$PID" -o command= 2>/dev/null || true)"
|
||||
if printf '%s' "$CMD" | grep -qF "$OLD_PATH"; then
|
||||
warn "it runs the OLD path: $CMD"
|
||||
else
|
||||
ok "command: $CMD"
|
||||
fi
|
||||
else
|
||||
warn "no daemon running"
|
||||
fi
|
||||
|
||||
say "6. report-only surfaces (counted, NEVER rewritten — see header items A-C)"
|
||||
report_only_surfaces
|
||||
|
||||
say "--check done: nothing changed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------- apply mode
|
||||
|
||||
say "pre-flight"
|
||||
[ -d "$OLD_PATH" ] || die "old checkout $OLD_PATH does not exist — nothing to rename"
|
||||
[ -e "$NEW_PATH" ] && die "new path $NEW_PATH already exists — refusing to overwrite"
|
||||
if [ -n "$(git -C "$OLD_PATH" status --porcelain 2>/dev/null)" ]; then
|
||||
git -C "$OLD_PATH" status --short | sed 's/^/ /'
|
||||
die "working tree at $OLD_PATH is dirty — commit or stash first, then rerun"
|
||||
fi
|
||||
ok "working tree clean, target path free"
|
||||
|
||||
if [ "$ASSUME_YES" = 0 ]; then
|
||||
echo
|
||||
echo " This MOVES the checkout this system runs from and RESTARTS the daemon."
|
||||
echo " If you are connected through that daemon, your connection dies mid-turn."
|
||||
echo
|
||||
read -r -p " Type yes to continue: " reply
|
||||
[ "$reply" = "yes" ] || die "aborted — nothing changed"
|
||||
fi
|
||||
|
||||
SUPERVISED=0
|
||||
launchd_loaded && SUPERVISED=1
|
||||
|
||||
# ------------------------------------------------------- stop the daemon FIRST
|
||||
say "stop"
|
||||
OLD_PID="$(running_pid)"
|
||||
RESTART_MARK="$(wc -l < "$OLD_OUT" 2>/dev/null || echo 0)" # demand a FRESH listening line later
|
||||
if [ -n "$OLD_PID" ]; then
|
||||
if [ "$SUPERVISED" = 1 ]; then
|
||||
echo " supervision is ON: 'launchctl unload' (not kill) so launchd's KeepAlive cannot"
|
||||
echo " restart the OLD jar out from under this script."
|
||||
launchctl unload -w "$LAUNCHD_PLIST" \
|
||||
|| die "launchctl unload failed — investigate before retrying; NOTHING has been moved"
|
||||
else
|
||||
kill "$OLD_PID"
|
||||
fi
|
||||
for _ in $(seq "$STOP_WAIT"); do
|
||||
[ -z "$(running_pid)" ] && break
|
||||
sleep 1
|
||||
done
|
||||
[ -z "$(running_pid)" ] || die "pid $OLD_PID still alive after ${STOP_WAIT}s — NOT escalating to kill -9 and NOT moving anything. Wait or stop it by hand."
|
||||
ok "pid $OLD_PID exited"
|
||||
elif [ "$SUPERVISED" = 1 ]; then
|
||||
launchctl unload -w "$LAUNCHD_PLIST" 2>/dev/null || true
|
||||
ok "launchd agent unloaded (daemon was not running)"
|
||||
else
|
||||
ok "daemon was not running"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------- move things
|
||||
say "move"
|
||||
mv "$OLD_PATH" "$NEW_PATH" || die "mv $OLD_PATH -> $NEW_PATH failed — the checkout is now at its old name, nothing else was touched"
|
||||
ok "checkout moved: $OLD_PATH -> $NEW_PATH"
|
||||
|
||||
OLD_SLUG="$(cc_slug "$OLD_PATH")"; NEW_SLUG="$(cc_slug "$NEW_PATH")"
|
||||
if [ -d "$CC_PROJECTS/$OLD_SLUG" ]; then
|
||||
if [ -e "$CC_PROJECTS/$NEW_SLUG" ]; then
|
||||
warn "NOT moving Claude Code state: $CC_PROJECTS/$NEW_SLUG already exists — merge by hand"
|
||||
else
|
||||
mv "$CC_PROJECTS/$OLD_SLUG" "$CC_PROJECTS/$NEW_SLUG" \
|
||||
|| warn "moving Claude Code project state failed — sessions remain under the old slug"
|
||||
ok "Claude Code project state moved: $OLD_SLUG -> $NEW_SLUG"
|
||||
fi
|
||||
else
|
||||
warn "no Claude Code project state at $CC_PROJECTS/$OLD_SLUG — nothing to move"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------- repair the worktrees
|
||||
say "repair worktrees"
|
||||
FIXED=0
|
||||
for GITDIR_FILE in "$NEW_PATH"/.git/worktrees/*/gitdir; do
|
||||
[ -f "$GITDIR_FILE" ] || continue
|
||||
WT_GIT="$(cat "$GITDIR_FILE")"
|
||||
if [ -f "$WT_GIT" ] && grep -qF "$OLD_PATH" "$WT_GIT"; then
|
||||
sed -i '' "s|$OLD_PATH|$NEW_PATH|g" "$WT_GIT" \
|
||||
|| warn "could not rewrite $WT_GIT — that worktree stays broken until fixed by hand"
|
||||
FIXED=$((FIXED + 1))
|
||||
fi
|
||||
done
|
||||
if git -C "$NEW_PATH" worktree list >/dev/null 2>&1; then
|
||||
ok "main repo answers after the rename"
|
||||
else
|
||||
die "git cannot read $NEW_PATH after the rename — inspect .git by hand"
|
||||
fi
|
||||
if [ "$FIXED" -gt 0 ]; then
|
||||
ok "rewrote $FIXED worktree .git pointer(s) to the new path"
|
||||
else
|
||||
ok "no worktree pointer referenced the old path"
|
||||
fi
|
||||
|
||||
# ------------------------------------- rewrite gitignored config, if it survived
|
||||
YAML="$NEW_PATH/$BRIDGED_SUB/bridged.yaml"
|
||||
if [ -f "$YAML" ]; then
|
||||
if grep -qF "$OLD_PATH" "$YAML"; then
|
||||
cp "$YAML" "$YAML.pre-rename.bak"
|
||||
sed -i '' "s|$OLD_PATH|$NEW_PATH|g" "$YAML"
|
||||
ok "rewrote $YAML (backup: $YAML.pre-rename.bak)"
|
||||
else
|
||||
ok "$YAML does not reference the old path"
|
||||
fi
|
||||
else
|
||||
warn "no $YAML present — skipped (this branch could not be exercised from a worktree)"
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------- fix + reload launchd
|
||||
say "launchd"
|
||||
if launchd_installed; then
|
||||
if grep -qF "$OLD_PATH" "$LAUNCHD_PLIST"; then
|
||||
cp "$LAUNCHD_PLIST" "$LAUNCHD_PLIST.pre-rename.bak"
|
||||
sed -i '' "s|$OLD_PATH|$NEW_PATH|g" "$LAUNCHD_PLIST"
|
||||
plutil -lint "$LAUNCHD_PLIST" >/dev/null \
|
||||
|| die "rewritten plist fails plutil -lint — restore $LAUNCHD_PLIST.pre-rename.bak and fix by hand"
|
||||
ok "rewrote $LAUNCHD_PLIST (backup beside it)"
|
||||
else
|
||||
ok "installed plist does not reference the old path"
|
||||
fi
|
||||
else
|
||||
warn "launchd agent not installed — starting unsupervised below"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------- start + verify
|
||||
say "start"
|
||||
if [ "$SUPERVISED" = 1 ]; then
|
||||
launchctl load -w "$LAUNCHD_PLIST" || die "launchctl load failed — recover with: launchctl load -w \"$LAUNCHD_PLIST\""
|
||||
else
|
||||
JAR="$NEW_PATH/$BRIDGED_SUB/target/$JAR_NAME"
|
||||
[ -f "$JAR" ] || die "no jar at $JAR — build in the new location, then rerun"
|
||||
( cd "$NEW_PATH/$BRIDGED_SUB" && zsh -lc "nohup java -jar '$JAR' >> $OUT_NAME 2>&1 &" )
|
||||
fi
|
||||
|
||||
for _ in $(seq 10); do
|
||||
NEW_PID="$(running_pid)"; [ -n "$NEW_PID" ] && break; sleep 1
|
||||
done
|
||||
[ -n "${NEW_PID:-}" ] || die "no daemon process appeared. Last lines of $NEW_OUT:
|
||||
$(tail -20 "$NEW_OUT" 2>/dev/null)"
|
||||
[ "$NEW_PID" != "${OLD_PID:-}" ] || die "pid unchanged ($NEW_PID) — the old daemon never died"
|
||||
ok "started, pid $NEW_PID"
|
||||
|
||||
say "verify"
|
||||
HEALTH_OK=0; CODE=""
|
||||
for _ in $(seq "$HEALTH_WAIT"); do
|
||||
CODE="$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 "$HEALTH" 2>/dev/null || echo 000)"
|
||||
case "$CODE" in
|
||||
200) HEALTH_OK=1; ok "/healthz 200"; break ;;
|
||||
# 503 still means the daemon is UP — it means herdr is unreachable.
|
||||
503) HEALTH_OK=1; warn "/healthz answers 503 degraded — daemon up, herdr unreachable"; break ;;
|
||||
esac
|
||||
sleep 1
|
||||
done
|
||||
[ "$HEALTH_OK" = 1 ] || die "/healthz never answered within ${HEALTH_WAIT}s (last code: ${CODE:-000})"
|
||||
|
||||
if tail -n "+$((RESTART_MARK + 1))" "$NEW_OUT" 2>/dev/null | grep -q 'bridged listening'; then
|
||||
ok "$(tail -n "+$((RESTART_MARK + 1))" "$NEW_OUT" | grep 'bridged listening' | tail -1)"
|
||||
else
|
||||
warn "no fresh 'bridged listening' line after the restart — check $NEW_OUT yourself"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------- summary
|
||||
say "changed"
|
||||
echo " checkout: $OLD_PATH -> $NEW_PATH"
|
||||
echo " claude code state: $CC_PROJECTS/$OLD_SLUG -> $CC_PROJECTS/$NEW_SLUG (when present)"
|
||||
echo " worktree .git ptr: rewritten where they named the old path"
|
||||
[ -f "$YAML" ] && echo " bridged.yaml: rewritten (backup kept)"
|
||||
launchd_installed && echo " launchd plist: rewritten (backup kept)"
|
||||
|
||||
say "still referencing the old path (left alone on purpose)"
|
||||
LEFT=$(scan_old_path_in "$NEW_PATH")
|
||||
if [ -n "$LEFT" ]; then
|
||||
printf '%s\n' "$LEFT" | sed 's/^/ /'
|
||||
echo " Review these by hand: tracked docs and IDE state are yours to update,"
|
||||
echo " session history must not be rewritten wholesale."
|
||||
else
|
||||
ok "(none inside the checkout)"
|
||||
fi
|
||||
scan_old_path_in "$CC_PROJECTS" | head -5 | sed 's/^/ /' || true
|
||||
echo " (session transcripts quote the old cwd — history, left untouched)"
|
||||
|
||||
say "left alone on purpose — report-only surfaces and their manual follow-up"
|
||||
report_only_surfaces
|
||||
|
||||
echo
|
||||
echo " Next: reconnect leads/members to the renamed checkout and re-check with --check."
|
||||
Reference in New Issue
Block a user