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:
outis1one
2026-01-01 22:28:35 -05:00
committed by GitHub
2 changed files with 199 additions and 6 deletions
+12
View File
@@ -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.)
+187 -6
View File
@@ -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
# ============================================================================