Add multi-machine state sync script and extend verify.sh coverage
D - resources/scripts/sync-copilot-state.sh: rsync-based push/pull for publish-log.tsv and project-ports-registry.json; dry-run by default; reads remote config from env or ~/.copilot-resources-state/sync.conf; excludes quarantine dir and machine-local log files E - install/verify.sh: add checks for session end hook script and all three history maintenance scripts (cleanup, restore, purge)
This commit is contained in:
@@ -75,7 +75,11 @@ main() {
|
||||
local local_mcp_overrides_file="$canonical_home/.local/mcp.local.jsonc"
|
||||
local session_start_hook_file="$canonical_home/resources/hooks/session-audit.json"
|
||||
local session_start_hook_script="$canonical_home/resources/scripts/report-hook-event.sh"
|
||||
local session_end_hook_script="$canonical_home/resources/scripts/report-hook-event-end.sh"
|
||||
local port_registry_script="$canonical_home/resources/scripts/update-port-registry.mjs"
|
||||
local cleanup_history_script="$canonical_home/resources/scripts/cleanup-copilot-history.sh"
|
||||
local restore_history_script="$canonical_home/resources/scripts/restore-copilot-history.sh"
|
||||
local purge_history_script="$canonical_home/resources/scripts/purge-copilot-history-quarantine.sh"
|
||||
|
||||
check_path "repo root" "$repo_root"
|
||||
check_path "canonical home" "$canonical_home"
|
||||
@@ -86,8 +90,16 @@ main() {
|
||||
check_path "session start hook" "$session_start_hook_file"
|
||||
check_path "session start hook script" "$session_start_hook_script"
|
||||
check_readable_file "session start hook script" "$session_start_hook_script"
|
||||
check_path "session end hook script" "$session_end_hook_script"
|
||||
check_readable_file "session end hook script" "$session_end_hook_script"
|
||||
check_path "port registry updater script" "$port_registry_script"
|
||||
check_readable_file "port registry updater script" "$port_registry_script"
|
||||
check_path "history cleanup script" "$cleanup_history_script"
|
||||
check_readable_file "history cleanup script" "$cleanup_history_script"
|
||||
check_path "history restore script" "$restore_history_script"
|
||||
check_readable_file "history restore script" "$restore_history_script"
|
||||
check_path "history purge script" "$purge_history_script"
|
||||
check_readable_file "history purge script" "$purge_history_script"
|
||||
check_command "session start hook shell" "bash"
|
||||
check_command "port registry updater runtime" "node"
|
||||
check_path "prompts link" "$vscode_user_dir/prompts"
|
||||
|
||||
Executable
+125
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sync persistent Copilot state files between machines using rsync over SSH.
|
||||
# Only publish-log.tsv and project-ports-registry.json are synced; machine-
|
||||
# local files (hook-events.log, session-events.tsv) and the quarantine
|
||||
# directory are always excluded.
|
||||
#
|
||||
# Usage:
|
||||
# sync-copilot-state.sh --mode push|pull --remote user@host [options]
|
||||
#
|
||||
# Configuration:
|
||||
# Set COPILOT_STATE_SYNC_REMOTE in environment or in
|
||||
# ~/.copilot-resources-state/sync.conf to avoid passing --remote every time.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
state_dir="${COPILOT_RESOURCES_STATE_DIR:-$HOME/.copilot-resources-state}"
|
||||
sync_conf="$state_dir/sync.conf"
|
||||
|
||||
dry_run="true"
|
||||
mode=""
|
||||
remote=""
|
||||
ssh_port="22"
|
||||
|
||||
function usage() {
|
||||
cat >&2 <<EOF
|
||||
Usage: sync-copilot-state.sh --mode <push|pull> [options]
|
||||
|
||||
Required:
|
||||
--mode <push|pull> push: send local state to remote.
|
||||
pull: fetch remote state to local.
|
||||
|
||||
Optional:
|
||||
--remote <user@host> Remote SSH target. Overrides COPILOT_STATE_SYNC_REMOTE
|
||||
and sync.conf.
|
||||
--remote-path <path> Remote state directory. Default: ~/.copilot-resources-state
|
||||
--ssh-port <port> SSH port. Default: 22
|
||||
--execute Apply the sync. Default is dry-run.
|
||||
--help Show this help text.
|
||||
|
||||
Configuration file:
|
||||
$sync_conf
|
||||
Supported keys (shell variable syntax):
|
||||
COPILOT_STATE_SYNC_REMOTE=user@host
|
||||
COPILOT_STATE_SYNC_REMOTE_PATH=/custom/path
|
||||
COPILOT_STATE_SYNC_SSH_PORT=22
|
||||
EOF
|
||||
}
|
||||
|
||||
# Load optional config file before parsing args so args take precedence.
|
||||
if [[ -f "$sync_conf" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$sync_conf"
|
||||
fi
|
||||
|
||||
remote="${COPILOT_STATE_SYNC_REMOTE:-}"
|
||||
remote_path="${COPILOT_STATE_SYNC_REMOTE_PATH:-.copilot-resources-state}"
|
||||
ssh_port="${COPILOT_STATE_SYNC_SSH_PORT:-22}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mode)
|
||||
mode="$2"; shift 2 ;;
|
||||
--remote)
|
||||
remote="$2"; shift 2 ;;
|
||||
--remote-path)
|
||||
remote_path="$2"; shift 2 ;;
|
||||
--ssh-port)
|
||||
ssh_port="$2"; shift 2 ;;
|
||||
--execute)
|
||||
dry_run="false"; shift ;;
|
||||
--help)
|
||||
usage; exit 0 ;;
|
||||
*)
|
||||
printf 'Unknown argument: %s\n' "$1" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$mode" ]]; then
|
||||
printf '--mode push|pull is required.\n' >&2; usage; exit 1
|
||||
fi
|
||||
|
||||
if [[ "$mode" != "push" && "$mode" != "pull" ]]; then
|
||||
printf '--mode must be push or pull.\n' >&2; exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$remote" ]]; then
|
||||
printf 'No remote configured. Pass --remote user@host or set COPILOT_STATE_SYNC_REMOTE.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p -- "$state_dir"
|
||||
|
||||
# Files to sync: only durable shared state; exclude machine-local and ephemeral.
|
||||
sync_files=(
|
||||
"publish-log.tsv"
|
||||
"project-ports-registry.json"
|
||||
)
|
||||
|
||||
rsync_args=(-avz --checksum -e "ssh -p ${ssh_port}")
|
||||
|
||||
if [[ "$dry_run" == "true" ]]; then
|
||||
rsync_args+=(--dry-run)
|
||||
printf 'Dry-run mode — no changes will be made. Pass --execute to apply.\n\n'
|
||||
fi
|
||||
|
||||
if [[ "$mode" == "push" ]]; then
|
||||
printf 'Pushing state to %s:%s\n\n' "$remote" "$remote_path"
|
||||
for file in "${sync_files[@]}"; do
|
||||
local_file="$state_dir/$file"
|
||||
if [[ ! -f "$local_file" ]]; then
|
||||
printf 'SKIP (not found): %s\n' "$local_file"
|
||||
continue
|
||||
fi
|
||||
printf 'SYNC: %s -> %s:%s/%s\n' "$local_file" "$remote" "$remote_path" "$file"
|
||||
rsync "${rsync_args[@]}" "$local_file" "${remote}:${remote_path}/${file}"
|
||||
done
|
||||
else
|
||||
printf 'Pulling state from %s:%s\n\n' "$remote" "$remote_path"
|
||||
for file in "${sync_files[@]}"; do
|
||||
printf 'SYNC: %s:%s/%s -> %s/%s\n' "$remote" "$remote_path" "$file" "$state_dir" "$file"
|
||||
rsync "${rsync_args[@]}" "${remote}:${remote_path}/${file}" "$state_dir/${file}"
|
||||
done
|
||||
fi
|
||||
|
||||
printf '\nDone.\n'
|
||||
Reference in New Issue
Block a user