diff --git a/ubuntu-post-install.sh b/ubuntu-post-install.sh index 7028c99..ac3a08e 100644 --- a/ubuntu-post-install.sh +++ b/ubuntu-post-install.sh @@ -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 -# 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 +# 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 " + 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 " - echo " immich upload --recursive /path/to/old/photos" echo "" fi fi # End IMMICH_RECONFIGURE check