Files
light-heart-labs--dreamserver/ods/scripts/migrate-config.sh
T
wehub-resource-sync 9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:33 +08:00

339 lines
9.3 KiB
Bash
Executable File

#!/bin/bash
#=============================================================================
# Config Migration Manager for ODS
#
# Handles configuration changes between versions.
# Automatically detects changes and migrates user configs safely.
#
# Usage:
# ./migrate-config.sh check # Check if migration needed
# ./migrate-config.sh migrate # Run pending migrations
# ./migrate-config.sh diff # Show what changed
# ./migrate-config.sh backup # Backup current config
#=============================================================================
set -euo pipefail
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_DIR="${INSTALL_DIR:-$(dirname "$SCRIPT_DIR")}"
DATA_DIR="${DATA_DIR:-$HOME/.ods}"
BACKUP_DIR="${DATA_DIR}/backups"
# Migrations live in INSTALL_DIR/migrations/ in deployed installs;
# in source tree they live at ods/migrations/ (peer of scripts/).
# SCRIPT_DIR is ods/scripts/ when run from source tree, or
# INSTALL_DIR/scripts/ when run from a deployed install — both resolve correctly.
MIGRATIONS_DIR="${SCRIPT_DIR}/../migrations"
VERSION_FILE="${INSTALL_DIR}/.version"
MIGRATION_STATE="${DATA_DIR}/.migration-state"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Get current version
get_current_version() {
if [[ -f "$VERSION_FILE" ]]; then
if jq -e '.version' "$VERSION_FILE" >/dev/null 2>&1; then
jq -r '.version' "$VERSION_FILE"
else
cat "$VERSION_FILE" | tr -d '[:space:]'
fi
else
echo "0.0.0"
fi
}
# Get last migrated version
get_last_migrated_version() {
if [[ -f "$MIGRATION_STATE" ]]; then
cat "$MIGRATION_STATE" | tr -d '[:space:]'
else
echo "0.0.0"
fi
}
# Set last migrated version
set_last_migrated_version() {
local version="$1"
mkdir -p "$(dirname "$MIGRATION_STATE")"
echo "$version" > "$MIGRATION_STATE"
}
# Compare semantic versions
# Returns: 0 if equal, 1 if v1 > v2, 2 if v1 < v2
compare_versions() {
local v1="${1#v}"
local v2="${2#v}"
if [[ "$v1" == "$v2" ]]; then
return 0
fi
IFS='.' read -ra V1_PARTS <<< "$v1"
IFS='.' read -ra V2_PARTS <<< "$v2"
for i in {0..2}; do
local p1="${V1_PARTS[$i]:-0}"
local p2="${V2_PARTS[$i]:-0}"
if [[ "$p1" -gt "$p2" ]]; then
return 1
elif [[ "$p1" -lt "$p2" ]]; then
return 2
fi
done
return 0
}
# Backup current configuration
cmd_backup() {
log_info "Backing up current configuration..."
local backup_name backup_path
backup_name="config-$(date +%Y%m%d-%H%M%S)"
backup_path="${BACKUP_DIR}/${backup_name}"
mkdir -p "$backup_path"
# Backup key config files
local cp_exit=0
cp "${INSTALL_DIR}/.env" "$backup_path/" 2>&1 || cp_exit=$?
cp "${INSTALL_DIR}/.version" "$backup_path/" 2>&1 || cp_exit=$?
cp "${INSTALL_DIR}/docker-compose.yml" "$backup_path/" 2>&1 || cp_exit=$?
cp -r "${INSTALL_DIR}/config" "$backup_path/" 2>&1 || cp_exit=$?
# Backup user data references
cp -r "${DATA_DIR}" "$backup_path/data/" 2>&1 || cp_exit=$?
log_success "Configuration backed up to: $backup_path"
echo "$backup_path"
}
# Show diff between current and example configs
cmd_diff() {
log_info "Checking for configuration changes..."
local example_env="${INSTALL_DIR}/.env.example"
local current_env="${INSTALL_DIR}/.env"
if [[ ! -f "$example_env" ]]; then
log_warn "No .env.example found — cannot show diff"
return 1
fi
if [[ ! -f "$current_env" ]]; then
log_warn "No .env found — user hasn't configured yet"
return 1
fi
echo ""
echo "=== Environment Variable Changes ==="
echo ""
# Show variables in example but not in current
echo "New variables (add these to your .env):"
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" =~ ^# ]] && continue
local grep_exit=0
grep -q "^${key}=" "$current_env" 2>&1 || grep_exit=$?
if [[ $grep_exit -ne 0 ]]; then
echo " + $key=$value"
fi
done < "$example_env"
echo ""
echo "Deprecated variables (remove from your .env):"
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" =~ ^# ]] && continue
local grep_exit=0
grep -q "^${key}=" "$example_env" 2>&1 || grep_exit=$?
if [[ $grep_exit -ne 0 ]]; then
echo " - $key"
fi
done < "$current_env"
}
# Check if migration is needed
cmd_check() {
local current_version
local last_migrated
current_version=$(get_current_version)
last_migrated=$(get_last_migrated_version)
log_info "Current version: $current_version"
log_info "Last migrated: $last_migrated"
compare_versions "$current_version" "$last_migrated"
local result
result=$?
if [[ $result -eq 2 ]]; then
log_warn "Migration needed: $last_migrated$current_version"
# List pending migrations
echo ""
echo "Pending migrations:"
for migration in "$MIGRATIONS_DIR"/migrate-v*.sh; do
if [[ -f "$migration" ]]; then
local migration_version
migration_version=$(basename "$migration" | sed 's/migrate-v//;s/.sh//')
compare_versions "$migration_version" "$last_migrated"
if [[ $? -eq 1 ]]; then
echo " - $migration_version: $(head -5 "$migration" | grep '^# Description:' | sed 's/# Description://')"
fi
fi
done
return 2
else
log_success "No migration needed (already at $current_version)"
return 0
fi
}
# Run pending migrations
cmd_migrate() {
log_info "Starting configuration migration..."
local current_version
local last_migrated
current_version=$(get_current_version)
last_migrated=$(get_last_migrated_version)
# Create backup first
cmd_backup >/dev/null
compare_versions "$current_version" "$last_migrated"
if [[ $? -ne 2 ]]; then
log_success "Already up to date ($current_version)"
return 0
fi
log_info "Migrating: $last_migrated$current_version"
# Run migrations in order
local failed=0
local ls_exit=0
local migrations
migrations=$(ls -1 "$MIGRATIONS_DIR"/migrate-v*.sh 2>&1 | sort) || ls_exit=$?
if [[ $ls_exit -ne 0 ]]; then
log_success "No migration scripts found"
return 0
fi
for migration in $migrations; do
if [[ -f "$migration" ]]; then
local migration_version
migration_version=$(basename "$migration" | sed 's/migrate-v//;s/.sh//')
# Check if this migration is needed
compare_versions "$migration_version" "$last_migrated"
if [[ $? -eq 1 ]]; then
log_info "Running migration: $migration_version"
if bash "$migration"; then
log_success "Migration $migration_version completed"
set_last_migrated_version "$migration_version"
else
log_error "Migration $migration_version failed!"
((failed++)) || true
break
fi
fi
fi
done
if [[ $failed -eq 0 ]]; then
set_last_migrated_version "$current_version"
log_success "Migration complete! Updated to $current_version"
return 0
else
log_error "Migration failed. Check logs and restore from backup."
return 1
fi
}
# Validate .env against schema
cmd_validate() {
local validator="${SCRIPT_DIR}/validate-env.sh"
local env_file="${INSTALL_DIR}/.env"
local schema_file="${INSTALL_DIR}/.env.schema.json"
if [[ ! -f "$validator" ]]; then
log_error "Validator script missing: $validator"
return 1
fi
if [[ ! -f "$schema_file" ]]; then
log_error "Schema missing: $schema_file"
return 1
fi
bash "$validator" "$env_file" "$schema_file"
}
# Show help
cmd_help() {
cat << 'EOF'
ODS Config Migration Manager
Usage: ./migrate-config.sh [command]
Commands:
check Check if migration is needed
migrate Run pending migrations (with backup)
diff Show configuration differences
backup Backup current configuration
validate Validate .env against .env.schema.json
help Show this help message
Examples:
./migrate-config.sh check
./migrate-config.sh migrate
./migrate-config.sh diff
./migrate-config.sh validate
Migration scripts should be placed in the migrations/ directory
and named: migrate-vX.Y.Z.sh
EOF
}
# Main
case "${1:-help}" in
check)
cmd_check
;;
migrate)
cmd_migrate
;;
diff)
cmd_diff
;;
backup)
cmd_backup
;;
validate)
cmd_validate
;;
help|--help|-h)
cmd_help
;;
*)
log_error "Unknown command: $1"
cmd_help
exit 1
;;
esac