Files
claudetools/scripts/migration-pack.sh
Mike Swanson fa15b03180 sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00
Synced files:
- Quote wizard frontend (all components, hooks, types, config)
- API updates (config, models, routers, schemas, services)
- Client work (bg-builders, gurushow)
- Scripts (BGB Lesley termination, CIPP, Datto, migration)
- Temp files (Bardach contacts, VWP investigation, misc)
- Credentials and session logs
- Email service, PHP API, session logs

Machine: ACG-M-L5090
Timestamp: 2026-03-10 19:11:00

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:59:08 -07:00

291 lines
10 KiB
Bash

#!/bin/bash
###############################################################################
# migration-pack.sh
#
# Creates an encrypted migration archive of all non-git ClaudeTools data.
# Works in Git Bash on Windows AND native Linux bash.
#
# Usage: ./migration-pack.sh [source_dir]
# source_dir Path to ClaudeTools repo (default: script's parent directory)
#
# Output: claudetools-migration-YYYYMMDD.tar.gpg in current working directory
###############################################################################
set -euo pipefail
# ---------------------------------------------------------------------------
# Globals
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="${1:-"$(cd "$SCRIPT_DIR/.." && pwd)"}"
DATE_STAMP="$(date +%Y%m%d)"
ARCHIVE_NAME="claudetools-migration-${DATE_STAMP}.tar.gpg"
STAGING_DIR=""
MANIFEST_FILE="MIGRATION_MANIFEST.txt"
WARN_COUNT=0
COPY_COUNT=0
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
log_info() { echo "[INFO] $*"; }
log_ok() { echo "[OK] $*"; }
log_warn() { echo "[WARNING] $*"; WARN_COUNT=$((WARN_COUNT + 1)); }
log_error() { echo "[ERROR] $*"; }
cleanup() {
if [[ -n "$STAGING_DIR" && -d "$STAGING_DIR" ]]; then
log_info "Cleaning up staging directory..."
rm -rf "$STAGING_DIR"
fi
}
trap cleanup EXIT
check_tool() {
if ! command -v "$1" &>/dev/null; then
log_error "Required tool not found: $1"
exit 1
fi
}
# Copy a single file into the staging area, preserving relative path.
# Warns and continues if the source does not exist.
stage_file() {
local rel_path="$1"
local src="${SOURCE_DIR}/${rel_path}"
local dst="${STAGING_DIR}/${rel_path}"
if [[ ! -e "$src" ]]; then
log_warn "File not found, skipping: ${rel_path}"
return
fi
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst"
COPY_COUNT=$((COPY_COUNT + 1))
log_ok "Staged: ${rel_path}"
}
# Copy an entire directory into the staging area.
stage_dir() {
local rel_path="$1"
local src="${SOURCE_DIR}/${rel_path}"
local dst="${STAGING_DIR}/${rel_path}"
if [[ ! -d "$src" ]]; then
log_warn "Directory not found, skipping: ${rel_path}"
return
fi
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst"
COPY_COUNT=$((COPY_COUNT + 1))
log_ok "Staged directory: ${rel_path}"
}
# Detect the Claude AI context directory based on platform conventions.
# On Windows (Git Bash), the repo path D:\ClaudeTools becomes D--ClaudeTools.
# On Linux/macOS, /home/user/ClaudeTools becomes -home-user-ClaudeTools.
detect_claude_context_dir() {
local claude_projects_base="${HOME}/.claude/projects"
if [[ ! -d "$claude_projects_base" ]]; then
echo ""
return
fi
# Try Windows-style mapping first: D:\ClaudeTools -> D--ClaudeTools
# Convert SOURCE_DIR from /d/path or D:/path to D--path
local win_name=""
if [[ "$SOURCE_DIR" =~ ^/([a-zA-Z])/(.*) ]]; then
# Git Bash path like /d/ClaudeTools
local drive="${BASH_REMATCH[1]^^}"
local rest="${BASH_REMATCH[2]}"
win_name="${drive}--${rest//\//-}"
elif [[ "$SOURCE_DIR" =~ ^([a-zA-Z]):(.*) ]]; then
# Windows path like D:\ClaudeTools or D:/ClaudeTools
local drive="${BASH_REMATCH[1]^^}"
local rest="${BASH_REMATCH[2]}"
rest="${rest//\\/-}"
rest="${rest//\//-}"
rest="${rest#-}"
win_name="${drive}--${rest}"
fi
if [[ -n "$win_name" && -d "${claude_projects_base}/${win_name}" ]]; then
echo "${claude_projects_base}/${win_name}"
return
fi
# Try Linux-style mapping: absolute path with slashes replaced by dashes
local linux_name="${SOURCE_DIR//\//-}"
linux_name="${linux_name#-}"
if [[ -d "${claude_projects_base}/${linux_name}" ]]; then
echo "${claude_projects_base}/${linux_name}"
return
fi
echo ""
}
# Write a manifest of everything in the staging directory.
write_manifest() {
local manifest="${STAGING_DIR}/${MANIFEST_FILE}"
{
echo "============================================================"
echo " ClaudeTools Migration Manifest"
echo " Created: $(date '+%Y-%m-%d %H:%M:%S')"
echo " Source: ${SOURCE_DIR}"
echo " Host: $(hostname)"
echo "============================================================"
echo ""
echo "Contents:"
echo "------------------------------------------------------------"
# Use find to list all files with sizes.
# On Git Bash, stat flags differ from GNU coreutils; use portable approach.
find "$STAGING_DIR" -type f ! -name "$MANIFEST_FILE" -print0 | while IFS= read -r -d '' file; do
local rel="${file#"${STAGING_DIR}/"}"
local size
size="$(wc -c < "$file" 2>/dev/null || echo "?")"
printf " %-60s %s bytes\n" "$rel" "$size"
done | sort
echo "------------------------------------------------------------"
echo ""
# Directory count and file count
local dir_count file_count total_size
dir_count="$(find "$STAGING_DIR" -mindepth 1 -type d | wc -l)"
file_count="$(find "$STAGING_DIR" -type f ! -name "$MANIFEST_FILE" | wc -l)"
total_size="$(find "$STAGING_DIR" -type f ! -name "$MANIFEST_FILE" -print0 | xargs -0 wc -c 2>/dev/null | tail -n1 | awk '{print $1}')"
echo "Directories: ${dir_count}"
echo "Files: ${file_count}"
echo "Total size: ${total_size:-0} bytes"
} > "$manifest"
log_ok "Manifest written: ${MANIFEST_FILE}"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
echo "============================================================"
echo " ClaudeTools Migration Packer"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================================"
echo ""
# Validate source directory
if [[ ! -d "$SOURCE_DIR" ]]; then
log_error "Source directory does not exist: ${SOURCE_DIR}"
exit 1
fi
log_info "Source directory: ${SOURCE_DIR}"
# Check required tools
check_tool tar
check_tool gpg
log_ok "Required tools available (tar, gpg)"
# Create staging directory
STAGING_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t 'migration')"
log_info "Staging directory: ${STAGING_DIR}"
echo ""
# ------------------------------------------------------------------
# Stage individual files
# ------------------------------------------------------------------
log_info "--- Staging individual files ---"
stage_file "credentials.md"
stage_file ".env"
stage_file ".mcp.json"
stage_file "dataforth-notifications-creds.txt"
stage_file ".claude/settings.local.json"
stage_file "projects/solverbot/.env"
stage_file "session-logs/2026-02-25-session.md"
echo ""
# ------------------------------------------------------------------
# Stage directories
# ------------------------------------------------------------------
log_info "--- Staging directories ---"
stage_dir "imported-conversations"
stage_dir "backups"
stage_dir "clients/gurushow"
echo ""
# ------------------------------------------------------------------
# Stage Claude AI context
# ------------------------------------------------------------------
log_info "--- Staging Claude AI context ---"
local claude_ctx
claude_ctx="$(detect_claude_context_dir)"
if [[ -n "$claude_ctx" && -d "$claude_ctx" ]]; then
local ctx_dst="${STAGING_DIR}/claude-context"
mkdir -p "$ctx_dst"
cp -a "$claude_ctx"/. "$ctx_dst/"
COPY_COUNT=$((COPY_COUNT + 1))
log_ok "Staged Claude context from: ${claude_ctx}"
else
log_warn "Claude AI context directory not found. Looked under \$HOME/.claude/projects/"
log_warn "You may need to manually copy this after migration."
fi
echo ""
# ------------------------------------------------------------------
# Write manifest
# ------------------------------------------------------------------
log_info "--- Writing manifest ---"
write_manifest
echo ""
# ------------------------------------------------------------------
# Create encrypted archive
# ------------------------------------------------------------------
log_info "--- Creating encrypted archive ---"
log_info "You will be prompted for a passphrase to encrypt the archive."
echo ""
# Create tar from staging contents, then encrypt with GPG symmetric.
# Use --batch only if GPG_PASSPHRASE env var is set (for automation).
local tar_tmp="${STAGING_DIR}.tar"
tar -cf "$tar_tmp" -C "$STAGING_DIR" .
if [[ -n "${GPG_PASSPHRASE:-}" ]]; then
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--symmetric --cipher-algo AES256 \
--output "$ARCHIVE_NAME" "$tar_tmp"
else
gpg --symmetric --cipher-algo AES256 \
--output "$ARCHIVE_NAME" "$tar_tmp"
fi
rm -f "$tar_tmp"
if [[ ! -f "$ARCHIVE_NAME" ]]; then
log_error "Archive creation failed."
exit 1
fi
local archive_size
archive_size="$(wc -c < "$ARCHIVE_NAME")"
echo ""
echo "============================================================"
echo " Migration Pack Complete"
echo "============================================================"
echo ""
echo " Archive: $(pwd)/${ARCHIVE_NAME}"
echo " Size: ${archive_size} bytes"
echo " Items: ${COPY_COUNT} files/directories staged"
echo " Warnings: ${WARN_COUNT}"
echo " Encrypted: AES-256 (GPG symmetric)"
echo ""
echo " To restore, run:"
echo " ./migration-restore.sh ${ARCHIVE_NAME}"
echo ""
log_ok "Done."
}
main "$@"