haste/migrate-to-ingress.sh

239 lines
7.6 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
# Migration script to move haste.nixc.us services from macmini3 to ingress.nixc.us
# This script uses non-interactive SSH and handles Docker volume migration
# Configuration
SOURCE_HOST="${SOURCE_HOST:-macmini3}"
TARGET_HOST="${TARGET_HOST:-ingress.nixc.us}"
STACK_NAME="${STACK_NAME:-haste}"
TARGET_HOSTNAME="${TARGET_HOSTNAME:-}" # Will be auto-detected if not set
VOLUMES=("redis_data" "public_system")
BACKUP_DIR="/tmp/haste-migration-$(date +%Y%m%d-%H%M%S)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# SSH helper function for non-interactive commands
ssh_cmd() {
local host="$1"
shift
ssh -o BatchMode=yes -o ConnectTimeout=5 "$host" "$@"
}
# Check if SSH key is available
check_ssh() {
log_info "Checking SSH connectivity..."
if ! ssh_cmd "${SOURCE_HOST}" "echo 'SSH to source OK'" 2>/dev/null; then
log_error "Cannot connect to ${SOURCE_HOST} via SSH. Please ensure SSH keys are set up."
exit 1
fi
if ! ssh_cmd "${TARGET_HOST}" "echo 'SSH to target OK'" 2>/dev/null; then
log_error "Cannot connect to ${TARGET_HOST} via SSH. Please ensure SSH keys are set up."
exit 1
fi
log_info "SSH connectivity verified"
}
# Backup volumes from source host
backup_volumes() {
log_info "Backing up volumes from ${SOURCE_HOST}..."
ssh_cmd "${SOURCE_HOST}" "mkdir -p ${BACKUP_DIR}"
for volume in "${VOLUMES[@]}"; do
log_info "Backing up volume: ${volume}"
# Docker Swarm prefixes volumes with stack name
VOLUME_NAME="${STACK_NAME}_${volume}"
# Check if volume exists
if ! ssh_cmd "${SOURCE_HOST}" "docker volume inspect ${VOLUME_NAME} >/dev/null 2>&1"; then
log_warn "Volume ${VOLUME_NAME} not found, skipping..."
continue
fi
# Create backup using a temporary container
ssh_cmd "${SOURCE_HOST}" "docker run --rm -v ${VOLUME_NAME}:/source:ro -v ${BACKUP_DIR}:/backup alpine:latest tar czf /backup/${volume}.tar.gz -C /source ."
if [ $? -eq 0 ]; then
log_info "Successfully backed up ${volume}"
else
log_error "Failed to backup ${volume}"
exit 1
fi
done
log_info "Volume backup completed"
}
# Transfer backups to target host
transfer_backups() {
log_info "Transferring backups to ${TARGET_HOST}..."
ssh_cmd "${TARGET_HOST}" "mkdir -p ${BACKUP_DIR}"
for volume in "${VOLUMES[@]}"; do
log_info "Transferring ${volume}.tar.gz..."
ssh_cmd "${SOURCE_HOST}" "cat ${BACKUP_DIR}/${volume}.tar.gz" | \
ssh_cmd "${TARGET_HOST}" "cat > ${BACKUP_DIR}/${volume}.tar.gz"
if [ $? -eq 0 ]; then
log_info "Successfully transferred ${volume}"
else
log_error "Failed to transfer ${volume}"
exit 1
fi
done
log_info "Backup transfer completed"
}
# Restore volumes on target host
restore_volumes() {
log_info "Restoring volumes on ${TARGET_HOST}..."
for volume in "${VOLUMES[@]}"; do
log_info "Restoring volume: ${volume}"
# Docker Swarm prefixes volumes with stack name
VOLUME_NAME="${STACK_NAME}_${volume}"
# Create volume if it doesn't exist
ssh_cmd "${TARGET_HOST}" "docker volume create ${VOLUME_NAME}" || true
# Restore backup using a temporary container
ssh_cmd "${TARGET_HOST}" "docker run --rm -v ${VOLUME_NAME}:/target -v ${BACKUP_DIR}:/backup alpine:latest sh -c 'rm -rf /target/* && tar xzf /backup/${volume}.tar.gz -C /target'"
if [ $? -eq 0 ]; then
log_info "Successfully restored ${volume}"
else
log_error "Failed to restore ${volume}"
exit 1
fi
done
log_info "Volume restoration completed"
}
# Update stack.production.yml to use new hostname
update_stack_config() {
log_info "Updating stack.production.yml..."
# Determine target hostname if not already set
if [ -z "${TARGET_HOSTNAME}" ]; then
# Try to get the actual hostname from the target
TARGET_HOSTNAME=$(ssh_cmd "${TARGET_HOST}" "hostname" 2>/dev/null || echo "ingress")
log_info "Target hostname auto-detected: ${TARGET_HOSTNAME}"
else
log_info "Using specified target hostname: ${TARGET_HOSTNAME}"
fi
# Update the stack file
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS uses BSD sed
sed -i.bak "s/node.hostname == macmini3/node.hostname == ${TARGET_HOSTNAME}/g" stack.production.yml
else
# Linux uses GNU sed
sed -i.bak "s/node.hostname == macmini3/node.hostname == ${TARGET_HOSTNAME}/g" stack.production.yml
fi
log_info "Stack configuration updated"
log_warn "Backup of original file saved as stack.production.yml.bak"
}
# Deploy to new location
deploy_to_target() {
log_info "Deploying stack to ${TARGET_HOST}..."
# Copy updated stack file to target
scp -o BatchMode=yes stack.production.yml "${TARGET_HOST}:/tmp/stack.production.yml"
# Deploy on target (assuming Docker Swarm manager is accessible)
ssh_cmd "${TARGET_HOST}" "docker stack deploy --with-registry-auth -c /tmp/stack.production.yml ${STACK_NAME}"
if [ $? -eq 0 ]; then
log_info "Stack deployed successfully"
else
log_error "Failed to deploy stack"
exit 1
fi
}
# Verify deployment
verify_deployment() {
log_info "Verifying deployment..."
sleep 10 # Give services time to start
ssh_cmd "${TARGET_HOST}" "docker stack services ${STACK_NAME} --format 'table {{.Name}}\t{{.Replicas}}\t{{.Image}}'"
log_info "Deployment verification completed"
}
# Optional cleanup function
do_cleanup_old() {
if [ "${CLEANUP_OLD:-false}" = "true" ]; then
log_info "Removing old deployment from ${SOURCE_HOST}..."
ssh_cmd "${SOURCE_HOST}" "docker stack rm ${STACK_NAME}" || log_warn "Stack removal failed or already removed"
sleep 5
ssh_cmd "${SOURCE_HOST}" "docker volume rm ${STACK_NAME}_redis_data ${STACK_NAME}_public_system" || log_warn "Volume removal failed or already removed"
log_info "Old deployment cleanup completed"
else
log_warn "Skipping cleanup of old deployment on ${SOURCE_HOST}"
log_warn "To remove old deployment, run manually:"
log_warn " ssh ${SOURCE_HOST} 'docker stack rm ${STACK_NAME}'"
log_warn " ssh ${SOURCE_HOST} 'docker volume rm ${STACK_NAME}_redis_data ${STACK_NAME}_public_system'"
log_warn ""
log_warn "Or set CLEANUP_OLD=true environment variable to auto-cleanup"
fi
}
# Cleanup temporary files
cleanup_temp_files() {
log_info "Cleaning up temporary files..."
ssh_cmd "${SOURCE_HOST}" "rm -rf ${BACKUP_DIR}" || true
ssh_cmd "${TARGET_HOST}" "rm -rf ${BACKUP_DIR}" || true
log_info "Cleanup completed"
}
# Main execution
main() {
log_info "Starting migration from ${SOURCE_HOST} to ${TARGET_HOST}"
check_ssh
backup_volumes
transfer_backups
restore_volumes
update_stack_config
deploy_to_target
verify_deployment
cleanup_temp_files
do_cleanup_old
log_info "Migration completed successfully!"
log_warn "Remember to:"
log_warn " 1. Test the service at https://haste.nixc.us"
log_warn " 2. Commit the updated stack.production.yml"
log_warn " 3. Clean up old deployment on ${SOURCE_HOST} when ready"
}
# Run main function
main "$@"