Merge pull request #13 from outis1one/claude/docker-selfhosted-apps-QFDqX
Add Immich photo library config, Watchtower, and backup docs
This commit is contained in:
@@ -104,10 +104,16 @@ Install containerized applications to `~/docker/{appname}/`:
|
|||||||
- **MeshCentral Server** - Self-hosted remote management server
|
- **MeshCentral Server** - Self-hosted remote management server
|
||||||
- **FindMyDevice** - Self-hosted Android device tracking
|
- **FindMyDevice** - Self-hosted Android device tracking
|
||||||
- **Frigate-Notify** - Push notifications for Frigate AI events
|
- **Frigate-Notify** - Push notifications for Frigate AI events
|
||||||
|
- **Watchtower** - Container update monitoring (notify-only by default)
|
||||||
|
|
||||||
### Container Backup & Restore (Kopia)
|
### 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.
|
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)
|
### Backup System (Optional)
|
||||||
|
|
||||||
**Local Backup (rsync):**
|
**Local Backup (rsync):**
|
||||||
@@ -1036,6 +1042,12 @@ This script is provided as-is for Ubuntu 24.04 Desktop installations.
|
|||||||
|
|
||||||
## Changelog
|
## 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
|
- **v2.8**: MeshCentral Server and improved recovery
|
||||||
- Added MeshCentral Server (self-hosted remote management, web-based RDP/terminal)
|
- Added MeshCentral Server (self-hosted remote management, web-based RDP/terminal)
|
||||||
- Recovery mode now installs core utilities first (openssh-server, git, curl, etc.)
|
- Recovery mode now installs core utilities first (openssh-server, git, curl, etc.)
|
||||||
|
|||||||
+187
-6
@@ -1442,14 +1442,34 @@ else
|
|||||||
echo "Installing Immich..."
|
echo "Installing Immich..."
|
||||||
IMMICH_DIR="$DOCKER_DIR/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
|
if [ "$DRY_RUN" = true ]; then
|
||||||
echo "[DRY-RUN] Would create $IMMICH_DIR"
|
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"
|
echo "[DRY-RUN] Would create docker-compose.yml and .env"
|
||||||
else
|
else
|
||||||
mkdir -p "$IMMICH_DIR"
|
mkdir -p "$IMMICH_DIR" 2>/dev/null || true
|
||||||
cd "$IMMICH_DIR"
|
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'
|
cat > docker-compose.yml << 'IMMICH_COMPOSE'
|
||||||
name: immich
|
name: immich
|
||||||
|
|
||||||
@@ -1458,7 +1478,10 @@ services:
|
|||||||
container_name: immich_server
|
container_name: immich_server
|
||||||
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
|
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
|
||||||
volumes:
|
volumes:
|
||||||
|
# Main photo storage - new uploads go here
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${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
|
- /etc/localtime:/etc/localtime:ro
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
@@ -1516,8 +1539,31 @@ IMMICH_COMPOSE
|
|||||||
|
|
||||||
# Create .env file
|
# Create .env file
|
||||||
cat > .env << IMMICH_ENV
|
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
|
DB_DATA_LOCATION=./postgres
|
||||||
|
|
||||||
IMMICH_VERSION=release
|
IMMICH_VERSION=release
|
||||||
@@ -1529,13 +1575,27 @@ DB_DATABASE_NAME=immich
|
|||||||
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
|
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
|
||||||
IMMICH_ENV
|
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 ""
|
||||||
echo "✓ Immich configured at $IMMICH_DIR"
|
echo "✓ Immich configured at $IMMICH_DIR"
|
||||||
|
echo " Photos stored at: $PHOTO_LOCATION"
|
||||||
|
echo ""
|
||||||
echo " Start with: cd $IMMICH_DIR && docker compose up -d"
|
echo " Start with: cd $IMMICH_DIR && docker compose up -d"
|
||||||
echo " Access at: http://localhost:2283"
|
echo " Access at: http://localhost:2283"
|
||||||
echo ""
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -3170,6 +3230,127 @@ FN_CONFIG
|
|||||||
fi
|
fi
|
||||||
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
|
# KOPIA BACKUP FOR DOCKER CONTAINERS
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user