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>
This commit is contained in:
290
scripts/migration-pack.sh
Normal file
290
scripts/migration-pack.sh
Normal file
@@ -0,0 +1,290 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user