diff --git a/README.md b/README.md index 9987945..71f705a 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,16 @@ Install containerized applications to `~/docker/{appname}/`: - **MeshCentral Server** - Self-hosted remote management server - **FindMyDevice** - Self-hosted Android device tracking - **Frigate-Notify** - Push notifications for Frigate AI events +- **Watchtower** - Container update monitoring (notify-only by default) ### Container Backup & Restore (Kopia) Backup all Docker container data (configs, databases, app data) to your backup drives for disaster recovery. Includes restore functionality to recover containers after OS drive failure. +**What lives where:** +- `~/docker/*/` (OS drive) - App configs, databases, compose files → **Backed up by Kopia** +- `~/drives/primary/` (data drive) - Media files, photos, documents → **Backed up by rsync** +- `/var/lib/docker/` (OS drive) - Container images, runtime state → **Not backed up** (re-pulled on restore) + ### Backup System (Optional) **Local Backup (rsync):** @@ -1036,6 +1042,12 @@ This script is provided as-is for Ubuntu 24.04 Desktop installations. ## Changelog +- **v2.9**: Immich photo library, Watchtower, documentation + - **Immich**: Now asks for photo storage location (default: `~/drives/primary/photos`) + - **Immich**: External library support for existing photos (read-only, no duplication) + - **Immich**: Storage template guidance for yyyy/mm folder organization + - Added Watchtower for container update monitoring (notify-only by default) + - Documented what Docker data lives where and what gets backed up - **v2.8**: MeshCentral Server and improved recovery - Added MeshCentral Server (self-hosted remote management, web-based RDP/terminal) - Recovery mode now installs core utilities first (openssh-server, git, curl, etc.) diff --git a/ubuntu-post-install.sh b/ubuntu-post-install.sh index ca36919..39f3ef1 100644 --- a/ubuntu-post-install.sh +++ b/ubuntu-post-install.sh @@ -1442,14 +1442,34 @@ else echo "Installing Immich..." IMMICH_DIR="$DOCKER_DIR/immich" + # Default photo location + DEFAULT_PHOTO_DIR="$HOME_DIR/drives/primary/photos" + + # Ask about photo storage location + echo "" + echo "Photo Storage Configuration:" + echo " Immich can store photos on your data drive instead of the OS drive." + echo " This keeps all photos in one place and makes backups simpler." + echo "" + echo " Default: $DEFAULT_PHOTO_DIR" + echo " (Press Enter to use default, or enter a custom path)" + echo "" + PHOTO_LOCATION="$DEFAULT_PHOTO_DIR" + prompt_text "Photo storage path:" "$DEFAULT_PHOTO_DIR" PHOTO_LOCATION 2>/dev/null || PHOTO_LOCATION="$DEFAULT_PHOTO_DIR" + + # Expand ~ if used + PHOTO_LOCATION="${PHOTO_LOCATION/#\~/$HOME_DIR}" + if [ "$DRY_RUN" = true ]; then echo "[DRY-RUN] Would create $IMMICH_DIR" + echo "[DRY-RUN] Would store photos at $PHOTO_LOCATION" echo "[DRY-RUN] Would create docker-compose.yml and .env" else - mkdir -p "$IMMICH_DIR" - cd "$IMMICH_DIR" + mkdir -p "$IMMICH_DIR" 2>/dev/null || true + mkdir -p "$PHOTO_LOCATION" 2>/dev/null || true + cd "$IMMICH_DIR" 2>/dev/null || cd "$DOCKER_DIR" - # Create docker-compose.yml + # Create docker-compose.yml with external library support cat > docker-compose.yml << 'IMMICH_COMPOSE' name: immich @@ -1458,7 +1478,10 @@ 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 - /etc/localtime:/etc/localtime:ro env_file: - .env @@ -1516,8 +1539,31 @@ IMMICH_COMPOSE # Create .env file cat > .env << IMMICH_ENV -# Immich Configuration -UPLOAD_LOCATION=./library +# ============================================================ +# IMMICH CONFIGURATION +# ============================================================ +# +# STORAGE TEMPLATE (configure in Immich web UI): +# Admin → Settings → Storage Template → Enable +# Template: {{y}}/{{MM}}/{{filename}} +# This organizes new uploads into: photos/2026/01/filename.jpg +# +# EXISTING PHOTOS: +# If you have existing photos, add them as an External Library: +# Admin → External Libraries → Create Library → Path: /usr/src/app/external +# Then scan the library to import them (read-only, no duplicates) +# +# ============================================================ + +# Photo storage location (new uploads from phone/web) +UPLOAD_LOCATION=$PHOTO_LOCATION + +# External library for existing photos (optional) +# Set this to your existing photo folder to make them visible in Immich +# They remain read-only - Immich won't modify or move them +EXTERNAL_LIBRARY=$PHOTO_LOCATION + +# Database location (keep on fast storage) DB_DATA_LOCATION=./postgres IMMICH_VERSION=release @@ -1529,13 +1575,27 @@ DB_DATABASE_NAME=immich TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") IMMICH_ENV - chown -R "$ACTUAL_USER:$ACTUAL_USER" "$IMMICH_DIR" + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$IMMICH_DIR" 2>/dev/null || true + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$PHOTO_LOCATION" 2>/dev/null || true echo "" echo "✓ Immich configured at $IMMICH_DIR" + echo " Photos stored at: $PHOTO_LOCATION" + echo "" echo " Start with: cd $IMMICH_DIR && docker compose up -d" echo " Access at: http://localhost:2283" echo "" + echo " IMPORTANT - After first login, configure storage template:" + echo " 1. Go to Admin → Settings → Storage Template" + echo " 2. Enable storage template" + echo " 3. Set template to: {{y}}/{{MM}}/{{filename}}" + echo " 4. New uploads will be organized as: 2026/01/photo.jpg" + echo "" + echo " For existing photos in $PHOTO_LOCATION:" + 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 (photos stay in place, not duplicated)" + echo "" fi fi @@ -3170,6 +3230,127 @@ FN_CONFIG fi fi + # ---- WATCHTOWER ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ WATCHTOWER - Container update monitoring │" + echo "│ Monitor containers for updates. NOTIFY ONLY by default. │" + echo "│ Why notify-only? Apps like Immich have breaking DB migrations. │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Watchtower? (y/n):" "n" INSTALL_WATCHTOWER + + if [ "$INSTALL_WATCHTOWER" = "y" ] || [ "$INSTALL_WATCHTOWER" = "Y" ]; then + echo "Installing Watchtower..." + WT_DIR="$DOCKER_DIR/watchtower" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $WT_DIR" + else + mkdir -p "$WT_DIR" 2>/dev/null || true + cd "$WT_DIR" 2>/dev/null || cd "$DOCKER_DIR" + + # Ask about mode + echo "" + echo "Watchtower Mode:" + echo " [M] Monitor only - Get notifications about available updates (SAFE)" + echo " [A] Auto-update - Automatically pull and restart containers (RISKY)" + echo "" + echo " ⚠️ Auto-update can break apps like Immich that need DB migrations!" + echo " Recommendation: Use monitor mode, update manually when ready." + echo "" + WT_MODE="M" + prompt_text "Mode [M/A]:" "M" WT_MODE 2>/dev/null || WT_MODE="M" + WT_MODE=$(echo "$WT_MODE" | tr '[:lower:]' '[:upper:]') + + if [ "$WT_MODE" = "A" ]; then + MONITOR_ONLY="false" + echo " Mode: Auto-update (containers will be updated automatically)" + else + MONITOR_ONLY="true" + echo " Mode: Monitor only (you'll be notified of updates)" + fi + + # Check for ntfy + NTFY_URL="" + if [ -d "$DOCKER_DIR/ntfy" ]; then + echo " ✓ ntfy detected - configuring notifications" + NTFY_URL="http://ntfy/watchtower" + fi + + cat > docker-compose.yml << WT_COMPOSE +name: watchtower + +services: + watchtower: + image: containrrr/watchtower:latest + container_name: watchtower + hostname: watchtower + restart: unless-stopped + environment: + # Check for updates daily at 4 AM + - WATCHTOWER_SCHEDULE=0 0 4 * * * + # Monitor only - don't auto-update (change to false for auto-update) + - WATCHTOWER_MONITOR_ONLY=${MONITOR_ONLY} + # Cleanup old images after update + - WATCHTOWER_CLEANUP=true + # Include stopped containers + - WATCHTOWER_INCLUDE_STOPPED=true + # Notification URL (ntfy, Discord, Slack, etc.) + - WATCHTOWER_NOTIFICATION_URL=${NOTIFICATION_URL:-} + # Show debug info + - WATCHTOWER_DEBUG=false + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro +WT_COMPOSE + + # Create .env + cat > .env << WT_ENV +# Watchtower Configuration +# ========================= +# +# Monitor-only mode: Watchtower checks for updates but doesn't apply them. +# This is SAFER because some apps (Immich, Mealie) have database migrations +# that can break if you update without proper procedures. +# +# To update manually: +# cd ~/docker/{app} +# docker compose pull +# docker compose up -d + +# Set to "false" to enable auto-updates (RISKY!) +MONITOR_ONLY=$MONITOR_ONLY + +# Notification URL (optional) +# Examples: +# ntfy: ntfy://ntfy.example.com/watchtower +# Discord: discord://token@id +# Slack: slack://hook-url +# Gotify: gotify://hostname/token +# +# Full list: https://containrrr.dev/shoutrrr/services/overview/ +NOTIFICATION_URL=$NTFY_URL +WT_ENV + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$WT_DIR" 2>/dev/null || true + + echo "" + echo "✓ Watchtower installed at $WT_DIR" + echo " Start: cd $WT_DIR && docker compose up -d" + echo " Mode: $([ "$MONITOR_ONLY" = "true" ] && echo "Monitor only" || echo "Auto-update")" + echo "" + echo " Checks for updates daily at 4 AM." + if [ -n "$NTFY_URL" ]; then + echo " Notifications: $NTFY_URL" + else + echo " Configure NOTIFICATION_URL in .env for alerts." + fi + echo "" + echo " To exclude a container from Watchtower:" + echo " Add label: com.centurylinklabs.watchtower.enable=false" + echo "" + fi + fi + # ============================================================================ # KOPIA BACKUP FOR DOCKER CONTAINERS # ============================================================================