Rework Immich setup UX with strategy-based photo library flow

Replace the confusing two-prompt (upload/external) configuration with a
cohesive strategy-based flow:

- One question for photo library path instead of two separate prompts
- Ask if user has existing photos, then present two clear strategies:
  [1] Import everything into Immich (unified library, recommended)
  [2] Keep existing photos in place (external library, read-only)
- Generate strategy-specific docker-compose.yml (no unused external
  mount when not needed)
- Strategy-specific .env files with relevant instructions only
- Fix nesting issue: external library uploads now go to sibling dir
  instead of subfolder (prevents duplicate scan)
- Unified post-setup instructions that match chosen strategy

https://claude.ai/code/session_01NAtxAkC5t6YVb3gcP1VcX8
This commit is contained in:
Claude
2026-02-19 02:37:02 +00:00
parent 104ce83ac0
commit 430a30ca18
+216 -63
View File
@@ -2811,40 +2811,82 @@ else
if [ "$IMMICH_RECONFIGURE" = "true" ]; then
echo "Installing Immich..."
# Photo storage configuration
# ── Photo library setup ─────────────────────────────────────
echo ""
echo "Photo Storage Configuration:"
echo ""
echo "Immich needs two separate locations:"
echo " 1. UPLOAD folder - Where NEW photos from phone/web uploads go"
echo " 2. EXTERNAL folder - Where EXISTING photos are (read-only access)"
echo "┌─────────────────────────────────────────────────────────────────┐"
echo "│ PHOTO LIBRARY SETUP │"
echo "└─────────────────────────────────────────────────────────────────┘"
echo ""
DEFAULT_PHOTOS_DIR="$PRIMARY_DRIVE_PATH/photos"
echo " Where should Immich store your photo library?"
echo " Default: $DEFAULT_PHOTOS_DIR"
prompt_text " Photo library path:" "$DEFAULT_PHOTOS_DIR" PHOTOS_DIR 2>/dev/null || PHOTOS_DIR="$DEFAULT_PHOTOS_DIR"
PHOTOS_DIR="${PHOTOS_DIR/#\~/$ACTUAL_HOME}"
# Strip trailing slash for consistent path handling
PHOTOS_DIR="${PHOTOS_DIR%/}"
# Use globally detected drives
# Upload location (for new photos)
DEFAULT_UPLOAD_DIR="$PRIMARY_DRIVE_PATH/photos/immich-uploads"
echo "NEW UPLOADS (from phone apps, web uploads):"
echo " Default: $DEFAULT_UPLOAD_DIR"
prompt_text " Upload folder path:" "$DEFAULT_UPLOAD_DIR" UPLOAD_LOCATION 2>/dev/null || UPLOAD_LOCATION="$DEFAULT_UPLOAD_DIR"
UPLOAD_LOCATION="${UPLOAD_LOCATION/#\~/$ACTUAL_HOME}"
# External library (for existing photos)
echo ""
DEFAULT_EXTERNAL_DIR="$PRIMARY_DRIVE_PATH/photos"
echo "EXISTING PHOTOS (external library, read-only):"
echo " Default: $DEFAULT_EXTERNAL_DIR"
echo " Leave blank if you don't have existing photos to import"
prompt_text " Existing photos path:" "$DEFAULT_EXTERNAL_DIR" EXTERNAL_LIBRARY 2>/dev/null || EXTERNAL_LIBRARY=""
EXTERNAL_LIBRARY="${EXTERNAL_LIBRARY/#\~/$ACTUAL_HOME}"
prompt_yn " Do you have existing photos to include? (y/n):" "y" HAS_EXISTING_PHOTOS
# If external library is same as upload, warn
if [ -n "$EXTERNAL_LIBRARY" ] && [ "$EXTERNAL_LIBRARY" = "$UPLOAD_LOCATION" ]; then
IMMICH_STRATEGY=""
EXTERNAL_LIBRARY=""
if [ "$HAS_EXISTING_PHOTOS" = "y" ] || [ "$HAS_EXISTING_PHOTOS" = "Y" ]; then
echo ""
echo " ⚠️ Warning: External library and upload location are the same."
echo " This may cause duplicate imports. Consider using different paths."
echo " How should Immich handle your existing photos?"
echo ""
echo " [1] Import everything into Immich (recommended)"
echo " Immich manages all photos in one unified library."
echo " Dates preserved via EXIF metadata. Organized by date."
echo " Your original folder names are NOT kept on disk"
echo " (use Immich albums to organize instead)."
echo ""
echo " [2] Keep existing photos in place (read-only)"
echo " Immich indexes your existing photos without moving them."
echo " New uploads go to a separate folder alongside them."
echo " Two storage locations, but your folder structure stays."
echo ""
if [ "$UNATTENDED" = true ]; then
IMMICH_STRATEGY="1"
echo " Strategy: [auto: 1]"
else
read -p " Choose [1/2]: " IMMICH_STRATEGY
IMMICH_STRATEGY="${IMMICH_STRATEGY:-1}"
fi
else
IMMICH_STRATEGY="1"
fi
# Set paths based on chosen strategy
if [ "$IMMICH_STRATEGY" = "2" ]; then
# External library: existing photos stay in place, uploads go alongside
UPLOAD_LOCATION="$PRIMARY_DRIVE_PATH/immich-uploads"
EXTERNAL_LIBRARY="$PHOTOS_DIR"
echo ""
echo " Your photo library:"
echo " $PHOTOS_DIR"
echo " ├── [your existing folders] ← indexed by Immich (read-only)"
echo " $PRIMARY_DRIVE_PATH"
echo " └── immich-uploads/ ← new phone/web uploads"
echo " └── 2026/01/filename.jpg organized by date"
else
# Unified library: everything in one place, managed by Immich
UPLOAD_LOCATION="$PHOTOS_DIR"
EXTERNAL_LIBRARY=""
echo ""
if [ "$HAS_EXISTING_PHOTOS" = "y" ] || [ "$HAS_EXISTING_PHOTOS" = "Y" ]; then
echo " All photos (existing + new) will live in:"
else
echo " All photos will be stored in:"
fi
echo " $PHOTOS_DIR"
echo " └── 2026/01/filename.jpg organized by date"
fi
echo ""
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would create $IMMICH_DIR"
echo "[DRY-RUN] Would store uploads at $UPLOAD_LOCATION"
@@ -2856,8 +2898,10 @@ else
[ -n "$EXTERNAL_LIBRARY" ] && mkdir -p "$EXTERNAL_LIBRARY" 2>/dev/null || true
cd "$IMMICH_DIR" 2>/dev/null || cd "$DOCKER_DIR"
# Create docker-compose.yml with external library support
cat > docker-compose.yml << 'IMMICH_COMPOSE'
# Create docker-compose.yml
if [ -n "$EXTERNAL_LIBRARY" ]; then
# Strategy 2: external library mount included
cat > docker-compose.yml << 'IMMICH_COMPOSE'
name: immich
services:
@@ -2865,10 +2909,8 @@ services:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
# Main photo storage - new uploads go here
- ${UPLOAD_LOCATION}:/usr/src/app/upload
# External library - for existing photos (read-only by Immich)
- ${EXTERNAL_LIBRARY:-/dev/null}:/usr/src/app/external:ro
- ${EXTERNAL_LIBRARY}:/usr/src/app/external:ro
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
@@ -2920,14 +2962,78 @@ services:
volumes:
model-cache:
IMMICH_COMPOSE
else
# Strategy 1: unified library, no external mount
cat > docker-compose.yml << 'IMMICH_COMPOSE'
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- 2283:2283
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
volumes:
- model-cache:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm
healthcheck:
test: valkey-cli ping || exit 1
restart: always
database:
container_name: immich_postgres
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(googlechecksum(googlechecksum(SPLIT_PART(googlechecksum::text, ''x'', 2)::bit(32)::int)), 0) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ''public'' AND c.relkind = ''r''')"; echo "googlechecksum: $$Chksum"; exit 0
interval: 5m
start_interval: 30s
start_period: 5m
command: ["postgres", "-c", "shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
restart: always
volumes:
model-cache:
IMMICH_COMPOSE
fi
# Generate random password
DB_PASS=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)
# Create .env file
cat > .env << IMMICH_ENV
# Create .env file with strategy-appropriate comments
if [ "$IMMICH_STRATEGY" = "2" ]; then
cat > .env << IMMICH_ENV
# ============================================================
# IMMICH CONFIGURATION
# IMMICH CONFIGURATION — External Library Mode
# ============================================================
#
# STORAGE TEMPLATE (configure in Immich web UI):
@@ -2935,25 +3041,19 @@ IMMICH_COMPOSE
# Template: {{y}}/{{MM}}/{{filename}}
# This organizes new uploads into: immich-uploads/2026/01/filename.jpg
#
# UPLOADING OLD PHOTOS WITH CORRECT DATES:
# Use immich-cli to import with proper EXIF date extraction:
#
# npm install -g @immich/cli
# immich login http://localhost:2283/api <your-api-key>
# immich upload --recursive /path/to/old/photos
#
# The CLI reads EXIF data to set correct dates automatically.
# Get your API key from: User Settings → API Keys
# EXTERNAL LIBRARY SETUP:
# Admin → External Libraries → Create Library
# Import path: /usr/src/app/external
# Click "Scan" to index your existing photos.
#
# ============================================================
# Photo storage location (new uploads from phone/web)
# New uploads from phone/web
UPLOAD_LOCATION=$UPLOAD_LOCATION
# External library for existing photos (read-only)
# Configure in: Admin → External Libraries → Create Library
# Import path inside container: /usr/src/app/external
EXTERNAL_LIBRARY=${EXTERNAL_LIBRARY:-}
# Existing photos (read-only, indexed by Immich)
# Mounted at /usr/src/app/external inside the container
EXTERNAL_LIBRARY=$EXTERNAL_LIBRARY
# Database location (keep on fast storage)
DB_DATA_LOCATION=./postgres
@@ -2966,6 +3066,42 @@ DB_DATABASE_NAME=immich
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
IMMICH_ENV
else
cat > .env << IMMICH_ENV
# ============================================================
# IMMICH CONFIGURATION — Unified Library
# ============================================================
#
# STORAGE TEMPLATE (configure in Immich web UI):
# Admin → Settings → Storage Template → Enable
# Template: {{y}}/{{MM}}/{{filename}}
# This organizes photos into: photos/2026/01/filename.jpg
#
# IMPORTING EXISTING PHOTOS:
# npm install -g @immich/cli
# immich login http://localhost:2283/api <your-api-key>
# immich upload --recursive /path/to/existing/photos
#
# The CLI reads EXIF data and sets correct dates automatically.
# Get your API key: User Settings → API Keys
#
# ============================================================
# All photos stored here (uploads + imported)
UPLOAD_LOCATION=$UPLOAD_LOCATION
# Database location (keep on fast storage)
DB_DATA_LOCATION=./postgres
IMMICH_VERSION=release
DB_PASSWORD=$DB_PASS
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
IMMICH_ENV
fi
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$IMMICH_DIR" 2>/dev/null || true
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$UPLOAD_LOCATION" 2>/dev/null || true
@@ -2973,8 +3109,8 @@ IMMICH_ENV
echo ""
echo "✓ Immich configured at $IMMICH_DIR"
echo " Uploads: $UPLOAD_LOCATION"
[ -n "$EXTERNAL_LIBRARY" ] && echo " External: $EXTERNAL_LIBRARY"
echo " Photo library: $PHOTOS_DIR"
[ -n "$EXTERNAL_LIBRARY" ] && echo " New uploads: $UPLOAD_LOCATION"
echo ""
# Configure Caddy reverse proxy before starting
@@ -2987,25 +3123,42 @@ IMMICH_ENV
docker compose up -d 2>/dev/null && echo " ✓ Immich started" || echo " ⚠ Failed to start"
fi
# ── Post-setup instructions ─────────────────────────────
echo ""
echo " Access at: http://localhost:2283"
echo "┌─────────────────────────────────────────────────────────────────┐"
echo "│ IMMICH SETUP STEPS │"
echo "└─────────────────────────────────────────────────────────────────┘"
echo ""
echo " SETUP STEPS:"
echo " 1. Create admin account on first visit"
echo " 2. Go to Admin → Settings → Storage Template"
echo " 3. Enable and set template to: {{y}}/{{MM}}/{{filename}}"
echo " 1. Open http://localhost:2283 and create your admin account"
echo ""
if [ -n "$EXTERNAL_LIBRARY" ]; then
echo " FOR EXISTING PHOTOS:"
echo " 1. Go to Admin → External Libraries → Create Library"
echo " 2. Set import path to: /usr/src/app/external"
echo " 3. Scan library to import"
echo " 2. Configure storage template (keeps files organized by date):"
echo " Admin → Settings → Storage Template → Enable"
echo " Template: {{y}}/{{MM}}/{{filename}}"
echo ""
if [ "$IMMICH_STRATEGY" = "2" ]; then
echo " 3. Set up your existing photo library:"
echo " Admin → External Libraries → Create Library"
echo " Import path: /usr/src/app/external"
echo " Click Scan to index your photos"
echo ""
echo " New uploads from the mobile app or web UI are stored"
echo " separately in: $UPLOAD_LOCATION"
else
if [ "$HAS_EXISTING_PHOTOS" = "y" ] || [ "$HAS_EXISTING_PHOTOS" = "Y" ]; then
echo " 3. Import your existing photos (preserves EXIF dates):"
echo " npm install -g @immich/cli"
echo " immich login http://localhost:2283/api <your-api-key>"
echo " immich upload --recursive /path/to/existing/photos"
echo ""
echo " Get your API key: User Settings → API Keys"
echo " After import, all photos live in one library at:"
echo " $PHOTOS_DIR"
else
echo " That's it. Upload photos from the mobile app or web UI."
echo " All photos are stored in: $PHOTOS_DIR"
fi
fi
echo " UPLOADING OLD PHOTOS WITH CORRECT DATES:"
echo " npm install -g @immich/cli"
echo " immich login http://localhost:2283/api <your-api-key>"
echo " immich upload --recursive /path/to/old/photos"
echo ""
fi
fi # End IMMICH_RECONFIGURE check