6ac1a2e91f
Co-Authored-By: Paperclip <noreply@paperclip.ing>
290 lines
7.6 KiB
Bash
Executable File
290 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Kill all local Paperclip workspace runtime service processes.
|
|
#
|
|
# This targets managed workspace services such as preview/dev commands started
|
|
# from project or execution workspaces. Use scripts/kill-dev.sh for Paperclip
|
|
# server processes.
|
|
#
|
|
# Usage:
|
|
# scripts/kill-workspaces.sh # kill workspace runtime services
|
|
# scripts/kill-workspaces.sh --dry # preview what would be killed
|
|
#
|
|
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
|
|
DRY_RUN=false
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--dry|--dry-run|-n)
|
|
DRY_RUN=true
|
|
;;
|
|
--help|-h)
|
|
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $arg" >&2
|
|
echo "Usage: scripts/kill-workspaces.sh [--dry]" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
REPO_PARENT="$(dirname "$REPO_ROOT")"
|
|
CURRENT_PID="$$"
|
|
CURRENT_PGID="$(ps -o pgid= -p "$CURRENT_PID" 2>/dev/null | tr -d '[:space:]' || true)"
|
|
|
|
expand_home() {
|
|
local value="$1"
|
|
if [[ "$value" == "~" ]]; then
|
|
printf '%s\n' "$HOME"
|
|
elif [[ "$value" == "~/"* ]]; then
|
|
printf '%s\n' "$HOME/${value#"~/"}"
|
|
else
|
|
printf '%s\n' "$value"
|
|
fi
|
|
}
|
|
|
|
runtime_dirs=()
|
|
declare -A seen_runtime_dirs=()
|
|
|
|
append_runtime_dir() {
|
|
local dir="$1"
|
|
[[ -d "$dir" ]] || return 0
|
|
dir="$(cd "$dir" && pwd)"
|
|
if [[ -z "${seen_runtime_dirs[$dir]:-}" ]]; then
|
|
seen_runtime_dirs["$dir"]=1
|
|
runtime_dirs+=("$dir")
|
|
fi
|
|
}
|
|
|
|
paperclip_home="$(expand_home "${PAPERCLIP_HOME:-$HOME/.paperclip}")"
|
|
paperclip_instance_id="${PAPERCLIP_INSTANCE_ID:-default}"
|
|
append_runtime_dir "$paperclip_home/instances/$paperclip_instance_id/runtime-services"
|
|
|
|
if [[ "${PAPERCLIP_KILL_WORKSPACES_ONLY_CURRENT:-}" != "1" ]]; then
|
|
for dir in \
|
|
"$HOME"/.paperclip/instances/*/runtime-services \
|
|
"$HOME"/.paperclip-worktrees/instances/*/runtime-services \
|
|
"$REPO_ROOT"/.paperclip/instances/*/runtime-services \
|
|
"$REPO_ROOT"/.paperclip/runtime-services/instances/*/runtime-services
|
|
do
|
|
append_runtime_dir "$dir"
|
|
done
|
|
|
|
for sibling_root in "$REPO_PARENT"/paperclip*; do
|
|
[[ -d "$sibling_root" ]] || continue
|
|
for dir in \
|
|
"$sibling_root"/.paperclip/instances/*/runtime-services \
|
|
"$sibling_root"/.paperclip/runtime-services/instances/*/runtime-services
|
|
do
|
|
append_runtime_dir "$dir"
|
|
done
|
|
done
|
|
fi
|
|
|
|
record_lines=()
|
|
if [[ ${#runtime_dirs[@]} -gt 0 ]]; then
|
|
mapfile -t record_lines < <(node - "${runtime_dirs[@]}" <<'NODE'
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
function clean(value) {
|
|
return String(value ?? "").replace(/[\t\r\n]+/g, " ");
|
|
}
|
|
|
|
for (const dir of process.argv.slice(2)) {
|
|
let entries = [];
|
|
try {
|
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
} catch {
|
|
continue;
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
const filePath = path.resolve(dir, entry.name);
|
|
let record;
|
|
try {
|
|
record = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (!record || record.profileKind !== "workspace-runtime") continue;
|
|
if (!Number.isInteger(record.pid) || record.pid <= 0) continue;
|
|
|
|
console.log([
|
|
filePath,
|
|
record.serviceKey,
|
|
record.serviceName,
|
|
record.pid,
|
|
Number.isInteger(record.processGroupId) && record.processGroupId > 0 ? record.processGroupId : "",
|
|
Number.isInteger(record.port) && record.port > 0 ? record.port : "",
|
|
record.cwd,
|
|
record.url,
|
|
record.runtimeServiceId,
|
|
].map(clean).join("\x1f"));
|
|
}
|
|
}
|
|
NODE
|
|
)
|
|
fi
|
|
|
|
is_pid_running() {
|
|
local pid="$1"
|
|
kill -0 "$pid" 2>/dev/null
|
|
}
|
|
|
|
is_group_running() {
|
|
local pgid="$1"
|
|
[[ -n "$pgid" && "$pgid" =~ ^[0-9]+$ && "$pgid" != "1" ]] || return 1
|
|
[[ -z "$CURRENT_PGID" || "$pgid" != "$CURRENT_PGID" ]] || return 1
|
|
kill -0 "-$pgid" 2>/dev/null
|
|
}
|
|
|
|
target_is_running() {
|
|
local type="$1"
|
|
local id="$2"
|
|
if [[ "$type" == "group" ]]; then
|
|
is_group_running "$id"
|
|
else
|
|
is_pid_running "$id"
|
|
fi
|
|
}
|
|
|
|
signal_target() {
|
|
local type="$1"
|
|
local id="$2"
|
|
local signal="$3"
|
|
if [[ "$type" == "group" ]]; then
|
|
kill "-$signal" "-$id" 2>/dev/null
|
|
else
|
|
kill "-$signal" "$id" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
active_files=()
|
|
stale_files=()
|
|
display_lines=()
|
|
target_keys=()
|
|
target_types=()
|
|
target_ids=()
|
|
declare -A seen_target_keys=()
|
|
|
|
for line in "${record_lines[@]}"; do
|
|
IFS=$'\x1f' read -r file service_key service_name pid pgid port cwd url runtime_service_id <<< "$line"
|
|
|
|
target_type=""
|
|
target_id=""
|
|
if is_group_running "$pgid"; then
|
|
target_type="group"
|
|
target_id="$pgid"
|
|
elif is_pid_running "$pid" && [[ "$pid" != "$CURRENT_PID" ]]; then
|
|
target_type="pid"
|
|
target_id="$pid"
|
|
fi
|
|
|
|
if [[ -z "$target_id" ]]; then
|
|
stale_files+=("$file")
|
|
continue
|
|
fi
|
|
|
|
active_files+=("$file")
|
|
target_key="$target_type:$target_id"
|
|
if [[ -z "${seen_target_keys[$target_key]:-}" ]]; then
|
|
seen_target_keys["$target_key"]=1
|
|
target_keys+=("$target_key")
|
|
target_types+=("$target_type")
|
|
target_ids+=("$target_id")
|
|
fi
|
|
|
|
short_file="${file/#$HOME\//}"
|
|
short_cwd="${cwd/#$HOME\//}"
|
|
target_label="$target_type:$target_id"
|
|
display_lines+=("$(printf " %-24s pid %-7s target %-14s port %-6s cwd %-45s registry %s" \
|
|
"${service_name:-workspace-runtime}" "$pid" "$target_label" "${port:-"-"}" "${short_cwd:-"-"}" "$short_file")")
|
|
if [[ -n "${url:-}" ]]; then
|
|
display_lines+=("$(printf " %-24s url %s" "" "$url")")
|
|
fi
|
|
if [[ -n "${runtime_service_id:-}" ]]; then
|
|
display_lines+=("$(printf " %-24s runtimeServiceId %s" "" "$runtime_service_id")")
|
|
fi
|
|
if [[ -n "${service_key:-}" ]]; then
|
|
display_lines+=("$(printf " %-24s serviceKey %s" "" "$service_key")")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#active_files[@]} -eq 0 && ${#stale_files[@]} -eq 0 ]]; then
|
|
echo "No Paperclip workspace runtime services found."
|
|
exit 0
|
|
fi
|
|
|
|
if [[ ${#active_files[@]} -gt 0 ]]; then
|
|
echo "Found ${#active_files[@]} Paperclip workspace runtime service record(s):"
|
|
echo ""
|
|
printf '%s\n' "${display_lines[@]}"
|
|
echo ""
|
|
fi
|
|
|
|
if [[ ${#stale_files[@]} -gt 0 ]]; then
|
|
echo "Found ${#stale_files[@]} stale workspace runtime service registry record(s)."
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
stale_preview_limit=20
|
|
stale_preview_count=0
|
|
for file in "${stale_files[@]}"; do
|
|
if (( stale_preview_count >= stale_preview_limit )); then
|
|
remaining=$(( ${#stale_files[@]} - stale_preview_count ))
|
|
echo " ... ${remaining} more stale record(s)"
|
|
break
|
|
fi
|
|
echo " stale ${file/#$HOME\//}"
|
|
((stale_preview_count += 1))
|
|
done
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo "Dry run — re-run without --dry to kill these services and remove stale registry records."
|
|
exit 0
|
|
fi
|
|
|
|
if [[ ${#target_keys[@]} -gt 0 ]]; then
|
|
echo "Sending SIGTERM to workspace runtime process targets..."
|
|
for i in "${!target_keys[@]}"; do
|
|
type="${target_types[$i]}"
|
|
id="${target_ids[$i]}"
|
|
if signal_target "$type" "$id" "TERM"; then
|
|
echo " signaled $type $id"
|
|
else
|
|
echo " $type $id already gone"
|
|
fi
|
|
done
|
|
|
|
sleep 2
|
|
|
|
for i in "${!target_keys[@]}"; do
|
|
type="${target_types[$i]}"
|
|
id="${target_ids[$i]}"
|
|
if target_is_running "$type" "$id"; then
|
|
echo " $type $id still alive, sending SIGKILL..."
|
|
signal_target "$type" "$id" "KILL" || true
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ ${#active_files[@]} -gt 0 || ${#stale_files[@]} -gt 0 ]]; then
|
|
echo "Removing workspace runtime service registry records..."
|
|
for file in "${active_files[@]:-}" "${stale_files[@]:-}"; do
|
|
[[ -n "$file" ]] || continue
|
|
rm -f "$file"
|
|
echo " removed ${file/#$HOME\//}"
|
|
done
|
|
fi
|
|
|
|
echo "Done."
|