Add migration mode for importing existing Docker containers
- New [M] Migration option at script start - Auto-detects Docker directories (/var/docker, /opt/docker, ~/docker) - Scans for docker-compose.yml files and lists containers with sizes - Whiptail checklist for selecting containers to migrate - Option to stop containers before copy (clean database state) - Preserves versions - no unwanted upgrades during migration - After migration, offers to install additional services - Three modes now: Normal install, Migration, Disaster Recovery
This commit is contained in:
@@ -265,11 +265,41 @@ INSTALLATION MODE
|
|||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
[N] Normal install - Fresh install or modify existing
|
[N] Normal install - Fresh install or modify existing
|
||||||
|
[M] Migration - Import existing Docker containers
|
||||||
[R] Disaster recovery - Restore from Kopia backup
|
[R] Disaster recovery - Restore from Kopia backup
|
||||||
|
|
||||||
Select mode (N/R) [N]:
|
Select mode (N/M/R) [N]:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Migration Mode
|
||||||
|
|
||||||
|
If you have existing Docker containers from another setup (different directory structure, another server, etc.), migration mode imports them without changing versions.
|
||||||
|
|
||||||
|
### When to Use Migration
|
||||||
|
|
||||||
|
- Moving from `/var/docker` or `/opt/docker` to `~/docker`
|
||||||
|
- Importing containers from another machine
|
||||||
|
- Adopting this script's structure for existing setups
|
||||||
|
|
||||||
|
### What Migration Does
|
||||||
|
|
||||||
|
1. **Scans source directory** - Auto-detects Docker directories, finds all compose files
|
||||||
|
2. **Shows containers found** - Lists each container with size
|
||||||
|
3. **Select what to migrate** - Whiptail checklist or text menu
|
||||||
|
4. **Stops containers** (optional) - Ensures clean copy of databases
|
||||||
|
5. **Copies containers** - Preserves versions, configs, and all data
|
||||||
|
6. **Starts containers** - In the new location
|
||||||
|
7. **Offers additional services** - Continue with normal install for more apps
|
||||||
|
|
||||||
|
### Migration vs Restore
|
||||||
|
|
||||||
|
| Feature | Migration | Disaster Recovery |
|
||||||
|
|---------|-----------|-------------------|
|
||||||
|
| Source | Existing Docker directory | Kopia backup |
|
||||||
|
| Versions | Preserved exactly | Preserved exactly |
|
||||||
|
| After | Install more services | Reconnect backups |
|
||||||
|
| Use case | Restructuring setup | OS drive failure |
|
||||||
|
|
||||||
### 5. Follow Interactive Prompts
|
### 5. Follow Interactive Prompts
|
||||||
|
|
||||||
The script shows current system status and asks:
|
The script shows current system status and asks:
|
||||||
@@ -1048,6 +1078,14 @@ This script is provided as-is for Ubuntu 24.04 Desktop installations.
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
- **v2.10**: Migration mode for existing Docker setups
|
||||||
|
- New **Migration mode** - Import existing Docker containers from any directory
|
||||||
|
- Auto-detects Docker directories and scans for compose files
|
||||||
|
- Preserves container versions (no unwanted upgrades)
|
||||||
|
- Whiptail checklist for selecting which containers to migrate
|
||||||
|
- Option to stop containers for clean database copy
|
||||||
|
- After migration, offers to install additional services
|
||||||
|
- Three modes now: Normal install, Migration, Disaster Recovery
|
||||||
- **v2.9**: Immich photo library, Watchtower, recovery improvements
|
- **v2.9**: Immich photo library, Watchtower, recovery improvements
|
||||||
- **Immich**: Now asks for photo storage location (default: `~/drives/primary/photos`)
|
- **Immich**: Now asks for photo storage location (default: `~/drives/primary/photos`)
|
||||||
- **Immich**: External library support for existing photos (read-only, no duplication)
|
- **Immich**: External library support for existing photos (read-only, no duplication)
|
||||||
|
|||||||
+276
-1
@@ -513,6 +513,267 @@ run_disaster_recovery() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Migration function - import existing Docker containers
|
||||||
|
run_migration() {
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "MIGRATION MODE"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo "This will migrate existing Docker containers to this script's structure."
|
||||||
|
echo "Containers will be copied (not moved) and versions preserved."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Get source Docker directory
|
||||||
|
echo "Step 1: Locate existing containers"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo "Common Docker directory locations:"
|
||||||
|
echo " • /var/docker"
|
||||||
|
echo " • ~/docker"
|
||||||
|
echo " • /opt/docker"
|
||||||
|
echo " • /home/*/docker"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Try to auto-detect
|
||||||
|
DETECTED_DIRS=()
|
||||||
|
for check_dir in /var/docker /opt/docker "$HOME/docker" /home/*/docker; do
|
||||||
|
if [ -d "$check_dir" ] && [ "$(find "$check_dir" -maxdepth 2 -name 'docker-compose.yml' -o -name 'compose.yml' 2>/dev/null | head -1)" ]; then
|
||||||
|
DETECTED_DIRS+=("$check_dir")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#DETECTED_DIRS[@]} -gt 0 ]; then
|
||||||
|
echo "Auto-detected Docker directories:"
|
||||||
|
for dir in "${DETECTED_DIRS[@]}"; do
|
||||||
|
count=$(find "$dir" -maxdepth 2 \( -name 'docker-compose.yml' -o -name 'compose.yml' \) 2>/dev/null | wc -l)
|
||||||
|
echo " • $dir ($count containers)"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "Enter source Docker directory: " SOURCE_DOCKER_DIR
|
||||||
|
|
||||||
|
# Expand ~ if used
|
||||||
|
SOURCE_DOCKER_DIR="${SOURCE_DOCKER_DIR/#\~/$HOME}"
|
||||||
|
|
||||||
|
if [ ! -d "$SOURCE_DOCKER_DIR" ]; then
|
||||||
|
echo "❌ Directory not found: $SOURCE_DOCKER_DIR"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 2: Scan for containers
|
||||||
|
echo ""
|
||||||
|
echo "Step 2: Scanning for containers"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
CONTAINERS_FOUND=()
|
||||||
|
CONTAINER_PATHS=()
|
||||||
|
|
||||||
|
while IFS= read -r compose_file; do
|
||||||
|
if [ -n "$compose_file" ]; then
|
||||||
|
container_dir=$(dirname "$compose_file")
|
||||||
|
container_name=$(basename "$container_dir")
|
||||||
|
CONTAINERS_FOUND+=("$container_name")
|
||||||
|
CONTAINER_PATHS+=("$container_dir")
|
||||||
|
fi
|
||||||
|
done < <(find "$SOURCE_DOCKER_DIR" -maxdepth 2 \( -name 'docker-compose.yml' -o -name 'compose.yml' \) 2>/dev/null)
|
||||||
|
|
||||||
|
if [ ${#CONTAINERS_FOUND[@]} -eq 0 ]; then
|
||||||
|
echo "❌ No Docker containers found in $SOURCE_DOCKER_DIR"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found ${#CONTAINERS_FOUND[@]} container(s):"
|
||||||
|
for i in "${!CONTAINERS_FOUND[@]}"; do
|
||||||
|
container="${CONTAINERS_FOUND[$i]}"
|
||||||
|
path="${CONTAINER_PATHS[$i]}"
|
||||||
|
size=$(du -sh "$path" 2>/dev/null | cut -f1)
|
||||||
|
echo " [$((i+1))] $container ($size)"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 3: Select containers to migrate
|
||||||
|
echo "Step 3: Select containers to migrate"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
SELECTED_CONTAINERS=()
|
||||||
|
|
||||||
|
if command -v whiptail &> /dev/null && [ ${#CONTAINERS_FOUND[@]} -gt 3 ]; then
|
||||||
|
# Use whiptail for many containers
|
||||||
|
CHECKLIST_ARGS=()
|
||||||
|
for container in "${CONTAINERS_FOUND[@]}"; do
|
||||||
|
CHECKLIST_ARGS+=("$container" "" "ON")
|
||||||
|
done
|
||||||
|
|
||||||
|
SELECTED=$(whiptail --title "Select Containers to Migrate" \
|
||||||
|
--checklist "Use SPACE to select/deselect, ENTER to confirm:" \
|
||||||
|
20 60 12 \
|
||||||
|
"${CHECKLIST_ARGS[@]}" \
|
||||||
|
3>&1 1>&2 2>&3)
|
||||||
|
|
||||||
|
if [ $? -eq 0 ] && [ -n "$SELECTED" ]; then
|
||||||
|
# Parse whiptail output
|
||||||
|
for container in $SELECTED; do
|
||||||
|
# Remove quotes
|
||||||
|
container="${container//\"/}"
|
||||||
|
SELECTED_CONTAINERS+=("$container")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Text-based selection
|
||||||
|
echo "Select containers to migrate:"
|
||||||
|
echo " [A] All containers"
|
||||||
|
echo " [S] Select individually"
|
||||||
|
echo " [N] None (cancel)"
|
||||||
|
echo ""
|
||||||
|
read -p "Choice (A/S/N) [A]: " MIGRATE_CHOICE
|
||||||
|
|
||||||
|
case "${MIGRATE_CHOICE^^}" in
|
||||||
|
N)
|
||||||
|
echo "Migration cancelled."
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
S)
|
||||||
|
for container in "${CONTAINERS_FOUND[@]}"; do
|
||||||
|
read -p " Migrate $container? (y/n) [y]: " MIGRATE_THIS
|
||||||
|
if [ "$MIGRATE_THIS" != "n" ] && [ "$MIGRATE_THIS" != "N" ]; then
|
||||||
|
SELECTED_CONTAINERS+=("$container")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
SELECTED_CONTAINERS=("${CONTAINERS_FOUND[@]}")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
|
||||||
|
echo "No containers selected."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Will migrate ${#SELECTED_CONTAINERS[@]} container(s):"
|
||||||
|
for container in "${SELECTED_CONTAINERS[@]}"; do
|
||||||
|
echo " • $container"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 4: Stop running containers (optional)
|
||||||
|
echo "Step 4: Container status"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo "Should running containers be stopped before migration?"
|
||||||
|
echo " • Stopping ensures clean copy of data/databases"
|
||||||
|
echo " • Not stopping may result in inconsistent state"
|
||||||
|
echo ""
|
||||||
|
read -p "Stop containers before copying? (y/n) [y]: " STOP_CONTAINERS
|
||||||
|
|
||||||
|
if [ "$STOP_CONTAINERS" != "n" ] && [ "$STOP_CONTAINERS" != "N" ]; then
|
||||||
|
echo "Stopping containers..."
|
||||||
|
for container in "${SELECTED_CONTAINERS[@]}"; do
|
||||||
|
for i in "${!CONTAINERS_FOUND[@]}"; do
|
||||||
|
if [ "${CONTAINERS_FOUND[$i]}" = "$container" ]; then
|
||||||
|
container_dir="${CONTAINER_PATHS[$i]}"
|
||||||
|
if [ -f "$container_dir/docker-compose.yml" ] || [ -f "$container_dir/compose.yml" ]; then
|
||||||
|
echo " Stopping $container..."
|
||||||
|
(cd "$container_dir" && docker compose down 2>/dev/null) || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 5: Copy containers
|
||||||
|
echo "Step 5: Migrating containers"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create target directory
|
||||||
|
mkdir -p "$DOCKER_DIR"
|
||||||
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$DOCKER_DIR"
|
||||||
|
|
||||||
|
MIGRATED_COUNT=0
|
||||||
|
for container in "${SELECTED_CONTAINERS[@]}"; do
|
||||||
|
for i in "${!CONTAINERS_FOUND[@]}"; do
|
||||||
|
if [ "${CONTAINERS_FOUND[$i]}" = "$container" ]; then
|
||||||
|
source_dir="${CONTAINER_PATHS[$i]}"
|
||||||
|
target_dir="$DOCKER_DIR/$container"
|
||||||
|
|
||||||
|
# Check if already exists
|
||||||
|
if [ -d "$target_dir" ]; then
|
||||||
|
echo " ⚠ $container already exists at $target_dir"
|
||||||
|
read -p " Overwrite? (y/n) [n]: " OVERWRITE
|
||||||
|
if [ "$OVERWRITE" != "y" ] && [ "$OVERWRITE" != "Y" ]; then
|
||||||
|
echo " Skipping $container"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
rm -rf "$target_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Copying $container..."
|
||||||
|
cp -a "$source_dir" "$target_dir"
|
||||||
|
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$target_dir"
|
||||||
|
((MIGRATED_COUNT++))
|
||||||
|
echo " ✓ $container migrated"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ Migrated $MIGRATED_COUNT container(s) to $DOCKER_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 6: Start migrated containers (optional)
|
||||||
|
echo "Step 6: Start containers"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
read -p "Start migrated containers? (y/n) [y]: " START_MIGRATED
|
||||||
|
|
||||||
|
if [ "$START_MIGRATED" != "n" ] && [ "$START_MIGRATED" != "N" ]; then
|
||||||
|
for container in "${SELECTED_CONTAINERS[@]}"; do
|
||||||
|
target_dir="$DOCKER_DIR/$container"
|
||||||
|
if [ -d "$target_dir" ]; then
|
||||||
|
echo " Starting $container..."
|
||||||
|
(cd "$target_dir" && docker compose up -d 2>/dev/null) || echo " ⚠ Failed to start $container"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 7: Offer additional services
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "MIGRATION COMPLETE"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo "Containers migrated to: $DOCKER_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "Would you like to install additional services not in your migration?"
|
||||||
|
echo "This will continue with the normal install process where you can"
|
||||||
|
echo "choose which additional apps to install (Immich, Frigate, etc.)"
|
||||||
|
echo ""
|
||||||
|
read -p "Continue to install additional services? (y/n) [y]: " INSTALL_MORE
|
||||||
|
|
||||||
|
if [ "$INSTALL_MORE" = "n" ] || [ "$INSTALL_MORE" = "N" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Migration complete. You can run this script again to install more services."
|
||||||
|
echo ""
|
||||||
|
echo "To check running containers:"
|
||||||
|
echo " docker ps"
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return to continue with normal install
|
||||||
|
INSTALL_MODE="post-migration"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# Check for restore mode flag
|
# Check for restore mode flag
|
||||||
if [ "$RESTORE_MODE" = true ]; then
|
if [ "$RESTORE_MODE" = true ]; then
|
||||||
run_disaster_recovery
|
run_disaster_recovery
|
||||||
@@ -526,15 +787,29 @@ if [ "$UNATTENDED" != true ]; then
|
|||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo ""
|
echo ""
|
||||||
echo " [N] Normal install - Fresh install or modify existing"
|
echo " [N] Normal install - Fresh install or modify existing"
|
||||||
|
echo " [M] Migration - Import existing Docker containers"
|
||||||
echo " [R] Disaster recovery - Restore from Kopia backup"
|
echo " [R] Disaster recovery - Restore from Kopia backup"
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Select mode (N/R) [N]: " MODE_SELECT
|
read -p "Select mode (N/M/R) [N]: " MODE_SELECT
|
||||||
|
|
||||||
case "${MODE_SELECT^^}" in
|
case "${MODE_SELECT^^}" in
|
||||||
R)
|
R)
|
||||||
run_disaster_recovery
|
run_disaster_recovery
|
||||||
exit $?
|
exit $?
|
||||||
;;
|
;;
|
||||||
|
M)
|
||||||
|
run_migration
|
||||||
|
# If migration returns 0 and INSTALL_MODE is post-migration, continue
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$INSTALL_MODE" != "post-migration" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "Continuing with additional service installation..."
|
||||||
|
echo ""
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
INSTALL_MODE="normal"
|
INSTALL_MODE="normal"
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user