diff --git a/README.md b/README.md index b3efa44..65df06c 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,20 @@ Automated setup script for Ubuntu 24.04 Desktop that installs essential tools, c - Requires a MeshCentral server (self-hosted or public) - Web-based remote desktop, terminal, and file transfer +### Self-Hosted Docker Applications (Optional) +Install containerized applications to `~/docker/{appname}/`: + +- **Immich** - Self-hosted photo & video backup (like Google Photos) +- **Audiobookshelf** - Audiobook & podcast server with progress sync +- **Emby** - Media server for movies, TV, and music +- **A.R.M.** - Automatic Ripping Machine for DVDs/Blu-rays/CDs +- **Filebrowser** - Web-based file manager +- **Magic Mirror** - Smart mirror dashboard (up to 3 instances) +- **Lyrion Music Server** - Music streaming to Squeezebox/Chromecast +- **Mealie** - Recipe manager & meal planner +- **Minecraft Server** - Fabric server with configurable RAM limit +- **linux-to-sync** - Private repository setup + ### Backup System (Optional) **Local Backup (rsync):** @@ -191,6 +205,10 @@ The script shows current system status and asks: - **TeamViewer**: Install TeamViewer remote desktop? (y/n) - **MeshCentral**: Install MeshCentral agent? (y/n) - If yes: Provide MeshCentral server agent URL +- **Docker Apps**: Install self-hosted applications? (each individually) + - Immich, Audiobookshelf, Emby, A.R.M., Filebrowser + - Magic Mirror (1-3 instances), Lyrion, Mealie, Minecraft + - linux-to-sync (private repo) - **Local Backup**: Set up local backup with rsync? (y/n) - If yes: Configure drive names, mount drives, fstab configuration - **Cloud Backup**: Set up encrypted cloud backup? (y/n) @@ -502,6 +520,59 @@ To manually install/reinstall: 2. Go to "My Devices" → "Add Agent" 3. Download and run the Linux agent installer +## Docker Applications + +Self-hosted applications are installed to `~/docker/{appname}/` with docker-compose. + +### Managing Docker Apps + +```bash +# Start an application +cd ~/docker/{appname} +docker compose up -d + +# View logs +docker compose logs -f + +# Stop an application +docker compose down + +# Update an application +docker compose pull +docker compose up -d +``` + +### Application Ports + +| Application | Port | URL | +|-------------|------|-----| +| Immich | 2283 | http://localhost:2283 | +| Audiobookshelf | 13378 | http://localhost:13378 | +| Emby | 8096 | http://localhost:8096 | +| A.R.M. | 8080 | http://localhost:8080 | +| Filebrowser | 8085 | http://localhost:8085 | +| Magic Mirror | 8081-8083 | http://localhost:808X | +| Lyrion (LMS) | 9000 | http://localhost:9000 | +| Mealie | 9925 | http://localhost:9925 | +| Minecraft | 25565 | localhost:25565 | + +### Private Repository (linux-to-sync) + +To clone a private GitHub repository, you need authentication: + +**Option 1: SSH Key (Recommended)** +```bash +# Your SSH key must be added to GitHub +cat ~/.ssh/id_rsa.pub +# Add at: https://github.com/settings/keys +``` + +**Option 2: Personal Access Token** +```bash +# Create token at: https://github.com/settings/tokens/new +# Select 'repo' scope +``` + ## Samba File Sharing If you chose to install Samba, it shares your **entire primary drive** via SMB/CIFS. @@ -829,6 +900,18 @@ This script is provided as-is for Ubuntu 24.04 Desktop installations. ## Changelog +- **v2.4**: Self-hosted Docker applications + - Added Immich (photo/video backup) + - Added Audiobookshelf (audiobook server) + - Added Emby (media server) + - Added A.R.M. (automatic ripping machine) + - Added Filebrowser (web file manager) + - Added Magic Mirror (up to 3 instances) + - Added Lyrion Music Server (LMS) + - Added Mealie (recipe manager) + - Added Minecraft Server (Fabric, RAM-limited) + - Added linux-to-sync private repo setup + - All apps use docker-compose in ~/docker/{appname}/ - **v2.3**: Additional VPN and remote desktop options - Added WireGuard VPN installation - Added Tailscale VPN installation (with Tailscale SSH info) diff --git a/ubuntu-post-install.sh b/ubuntu-post-install.sh index 754b667..a3cd1bd 100644 --- a/ubuntu-post-install.sh +++ b/ubuntu-post-install.sh @@ -992,6 +992,826 @@ else echo "Skipping MeshCentral installation." fi +# ============================================================================ +# SELF-HOSTED DOCKER APPLICATIONS (Optional) +# ============================================================================ +# These applications run in Docker containers using docker-compose +# Each app is installed to ~/docker/{appname}/ + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "SELF-HOSTED DOCKER APPLICATIONS (Optional)" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "Install self-hosted applications using Docker Compose." +echo "Each app will be installed to ~/docker/{appname}/" +echo "" +echo "Note: Docker must be installed for these applications." +echo "" + +if ! is_docker_installed && [ "$INSTALL_DOCKER" != "y" ] && [ "$INSTALL_DOCKER" != "Y" ]; then + echo "Docker is not installed. Skipping self-hosted applications." + echo "Install Docker first, then rerun this script." +else + # Create docker apps directory + DOCKER_DIR="$ACTUAL_HOME/docker" + if [ "$DRY_RUN" != true ]; then + mkdir -p "$DOCKER_DIR" + chown "$ACTUAL_USER:$ACTUAL_USER" "$DOCKER_DIR" + fi + + # ---- IMMICH ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ IMMICH - Self-hosted photo & video backup │" + echo "│ Like Google Photos but private. Mobile app auto-uploads. │" + echo "│ Port: 2283 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Immich? (y/n):" "n" INSTALL_IMMICH + + if [ "$INSTALL_IMMICH" = "y" ] || [ "$INSTALL_IMMICH" = "Y" ]; then + echo "Installing Immich..." + IMMICH_DIR="$DOCKER_DIR/immich" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $IMMICH_DIR" + echo "[DRY-RUN] Would create docker-compose.yml and .env" + else + mkdir -p "$IMMICH_DIR" + cd "$IMMICH_DIR" + + # Create docker-compose.yml + 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 + + # 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 +# Immich Configuration +UPLOAD_LOCATION=./library +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 + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$IMMICH_DIR" + + echo "" + echo "✓ Immich configured at $IMMICH_DIR" + echo " Start with: cd $IMMICH_DIR && docker compose up -d" + echo " Access at: http://localhost:2283" + echo "" + fi + fi + + # ---- AUDIOBOOKSHELF ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ AUDIOBOOKSHELF - Audiobook & podcast server │" + echo "│ Stream audiobooks with progress sync across devices. │" + echo "│ Port: 13378 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Audiobookshelf? (y/n):" "n" INSTALL_AUDIOBOOKSHELF + + if [ "$INSTALL_AUDIOBOOKSHELF" = "y" ] || [ "$INSTALL_AUDIOBOOKSHELF" = "Y" ]; then + echo "Installing Audiobookshelf..." + ABS_DIR="$DOCKER_DIR/audiobookshelf" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $ABS_DIR" + else + mkdir -p "$ABS_DIR" + cd "$ABS_DIR" + + prompt_text "Path to audiobooks folder [default: $ACTUAL_HOME/drives/primary/audiobooks]:" "$ACTUAL_HOME/drives/primary/audiobooks" AUDIOBOOKS_PATH + + cat > docker-compose.yml << ABS_COMPOSE +name: audiobookshelf + +services: + audiobookshelf: + image: ghcr.io/advplyr/audiobookshelf:latest + container_name: audiobookshelf + hostname: audiobookshelf + restart: unless-stopped + environment: + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + volumes: + - ./config:/config + - ./metadata:/metadata + - ${AUDIOBOOKS_PATH}:/audiobooks + - ${PODCASTS_PATH:-./podcasts}:/podcasts + ports: + - "13378:80" +ABS_COMPOSE + + cat > .env << ABS_ENV +AUDIOBOOKS_PATH=$AUDIOBOOKS_PATH +PODCASTS_PATH=./podcasts +ABS_ENV + + mkdir -p config metadata podcasts + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ABS_DIR" + + echo "" + echo "✓ Audiobookshelf configured at $ABS_DIR" + echo " Start with: cd $ABS_DIR && docker compose up -d" + echo " Access at: http://localhost:13378" + echo "" + fi + fi + + # ---- EMBY ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ EMBY - Media server for movies, TV, music │" + echo "│ Stream your media library to any device. │" + echo "│ Port: 8096 (web), 8920 (https) │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Emby? (y/n):" "n" INSTALL_EMBY + + if [ "$INSTALL_EMBY" = "y" ] || [ "$INSTALL_EMBY" = "Y" ]; then + echo "Installing Emby..." + EMBY_DIR="$DOCKER_DIR/emby" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $EMBY_DIR" + else + mkdir -p "$EMBY_DIR" + cd "$EMBY_DIR" + + prompt_text "Path to media folder [default: $ACTUAL_HOME/drives/primary/media]:" "$ACTUAL_HOME/drives/primary/media" MEDIA_PATH + + cat > docker-compose.yml << EMBY_COMPOSE +name: emby + +services: + emby: + image: emby/embyserver:latest + container_name: emby + hostname: emby + restart: unless-stopped + environment: + - UID=$(id -u "$ACTUAL_USER") + - GID=$(id -g "$ACTUAL_USER") + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + volumes: + - ./config:/config + - ${MEDIA_PATH}:/media + ports: + - "8096:8096" + - "8920:8920" + # Uncomment for hardware transcoding (Intel/AMD) + # devices: + # - /dev/dri:/dev/dri +EMBY_COMPOSE + + cat > .env << EMBY_ENV +MEDIA_PATH=$MEDIA_PATH +EMBY_ENV + + mkdir -p config + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$EMBY_DIR" + + echo "" + echo "✓ Emby configured at $EMBY_DIR" + echo " Start with: cd $EMBY_DIR && docker compose up -d" + echo " Access at: http://localhost:8096" + echo "" + fi + fi + + # ---- A.R.M. (Automatic Ripping Machine) ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ A.R.M. - Automatic Ripping Machine │" + echo "│ Automatically rip DVDs, Blu-rays, and CDs. │" + echo "│ Port: 8080 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install A.R.M.? (y/n):" "n" INSTALL_ARM + + if [ "$INSTALL_ARM" = "y" ] || [ "$INSTALL_ARM" = "Y" ]; then + echo "Installing A.R.M...." + ARM_DIR="$DOCKER_DIR/arm" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $ARM_DIR" + else + mkdir -p "$ARM_DIR" + cd "$ARM_DIR" + + prompt_text "Path for ripped media output [default: $ACTUAL_HOME/drives/primary/ripped]:" "$ACTUAL_HOME/drives/primary/ripped" ARM_OUTPUT + + # Detect optical drives + echo "" + echo "Detecting optical drives..." + OPTICAL_DRIVES=$(ls /dev/sr* 2>/dev/null || echo "") + if [ -n "$OPTICAL_DRIVES" ]; then + echo "Found: $OPTICAL_DRIVES" + else + echo "No optical drives detected. You can add them later." + OPTICAL_DRIVES="/dev/sr0" + fi + + cat > docker-compose.yml << ARM_COMPOSE +name: arm + +services: + automatic-ripping-machine: + image: automaticrippingmachine/automatic-ripping-machine:latest + container_name: arm + hostname: arm + restart: unless-stopped + environment: + - ARM_UID=$(id -u "$ACTUAL_USER") + - ARM_GID=$(id -g "$ACTUAL_USER") + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + volumes: + - ./config:/etc/arm/config + - ./logs:/home/arm/logs + - ${ARM_OUTPUT}/movies:/home/arm/media/completed + - ${ARM_OUTPUT}/music:/home/arm/music + ports: + - "8080:8080" + devices: + - /dev/sr0:/dev/sr0 + # Add more drives as needed: + # - /dev/sr1:/dev/sr1 + privileged: true +ARM_COMPOSE + + cat > .env << ARM_ENV +ARM_OUTPUT=$ARM_OUTPUT +ARM_ENV + + mkdir -p config logs + mkdir -p "$ARM_OUTPUT/movies" "$ARM_OUTPUT/music" + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$ARM_DIR" + + echo "" + echo "✓ A.R.M. configured at $ARM_DIR" + echo " Start with: cd $ARM_DIR && docker compose up -d" + echo " Access at: http://localhost:8080" + echo " Note: Edit docker-compose.yml to add more optical drives" + echo "" + fi + fi + + # ---- FILEBROWSER ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ FILEBROWSER - Web-based file manager │" + echo "│ Browse, upload, download files via web interface. │" + echo "│ Port: 8085 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Filebrowser? (y/n):" "n" INSTALL_FILEBROWSER + + if [ "$INSTALL_FILEBROWSER" = "y" ] || [ "$INSTALL_FILEBROWSER" = "Y" ]; then + echo "Installing Filebrowser..." + FB_DIR="$DOCKER_DIR/filebrowser" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $FB_DIR" + else + mkdir -p "$FB_DIR" + cd "$FB_DIR" + + prompt_text "Path to browse [default: $ACTUAL_HOME/drives/primary]:" "$ACTUAL_HOME/drives/primary" FB_PATH + + cat > docker-compose.yml << FB_COMPOSE +name: filebrowser + +services: + filebrowser: + image: filebrowser/filebrowser:s6 + container_name: filebrowser + hostname: filebrowser + restart: unless-stopped + environment: + - PUID=$(id -u "$ACTUAL_USER") + - PGID=$(id -g "$ACTUAL_USER") + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + volumes: + - ${FB_PATH}:/srv + - ./database/filebrowser.db:/database/filebrowser.db + - ./config/settings.json:/config/settings.json + ports: + - "8085:80" +FB_COMPOSE + + cat > .env << FB_ENV +FB_PATH=$FB_PATH +FB_ENV + + mkdir -p database config + touch database/filebrowser.db + cat > config/settings.json << 'FB_SETTINGS' +{ + "port": 80, + "baseURL": "", + "address": "", + "log": "stdout", + "database": "/database/filebrowser.db", + "root": "/srv" +} +FB_SETTINGS + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FB_DIR" + + echo "" + echo "✓ Filebrowser configured at $FB_DIR" + echo " Start with: cd $FB_DIR && docker compose up -d" + echo " Access at: http://localhost:8085" + echo " Default login: admin / admin (change immediately!)" + echo "" + fi + fi + + # ---- MAGIC MIRROR ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ MAGIC MIRROR - Smart mirror / dashboard display │" + echo "│ Modular smart mirror platform. Run up to 3 instances. │" + echo "│ Ports: 8081, 8082, 8083 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Magic Mirror? (y/n):" "n" INSTALL_MAGICMIRROR + + if [ "$INSTALL_MAGICMIRROR" = "y" ] || [ "$INSTALL_MAGICMIRROR" = "Y" ]; then + echo "" + prompt_text "How many Magic Mirror instances? [1-3, default: 1]:" "1" MM_COUNT + MM_COUNT=${MM_COUNT:-1} + if [ "$MM_COUNT" -gt 3 ]; then MM_COUNT=3; fi + if [ "$MM_COUNT" -lt 1 ]; then MM_COUNT=1; fi + + echo "Installing $MM_COUNT Magic Mirror instance(s)..." + + for i in $(seq 1 $MM_COUNT); do + MM_PORT=$((8080 + i)) + MM_DIR="$DOCKER_DIR/magicmirror-$MM_PORT" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $MM_DIR (port $MM_PORT)" + else + mkdir -p "$MM_DIR" + cd "$MM_DIR" + + cat > docker-compose.yml << MM_COMPOSE +name: mm-$MM_PORT + +services: + magicmirror: + image: karsten13/magicmirror:latest + container_name: magicmirror-$MM_PORT + hostname: magicmirror-$MM_PORT + restart: unless-stopped + environment: + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + volumes: + - ./config:/opt/magic_mirror/config + - ./modules:/opt/magic_mirror/modules + - ./css:/opt/magic_mirror/css + ports: + - "$MM_PORT:8080" +MM_COMPOSE + + mkdir -p config modules css + + # Create basic config.js + cat > config/config.js << 'MM_CONFIG' +let config = { + address: "0.0.0.0", + port: 8080, + ipWhitelist: [], + language: "en", + timeFormat: 12, + units: "imperial", + modules: [ + { + module: "alert", + }, + { + module: "clock", + position: "top_left" + }, + { + module: "calendar", + header: "Calendar", + position: "top_left", + config: { + calendars: [ + { + symbol: "calendar-check", + url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" + } + ] + } + }, + { + module: "weather", + position: "top_right", + config: { + weatherProvider: "openmeteo", + type: "current", + lat: 40.7128, + lon: -74.0060 + } + }, + { + module: "weather", + position: "top_right", + header: "Weather Forecast", + config: { + weatherProvider: "openmeteo", + type: "forecast", + lat: 40.7128, + lon: -74.0060 + } + }, + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "BBC", + url: "https://feeds.bbci.co.uk/news/rss.xml" + } + ], + showSourceTitle: true, + showPublishDate: true, + broadcastNewsFeeds: true, + broadcastNewsUpdates: true + } + }, + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") {module.exports = config;} +MM_CONFIG + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MM_DIR" + + echo " ✓ Magic Mirror #$i configured at $MM_DIR (port $MM_PORT)" + fi + done + + if [ "$DRY_RUN" != true ]; then + echo "" + echo " Start with: cd ~/docker/magicmirror-808X && docker compose up -d" + echo " Access at: http://localhost:808X" + echo " Edit config: ~/docker/magicmirror-808X/config/config.js" + echo "" + fi + fi + + # ---- LYRION MUSIC SERVER ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ LYRION MUSIC SERVER (LMS) - Music streaming server │" + echo "│ Stream music to Squeezebox devices, apps, and Chromecast. │" + echo "│ Port: 9000 (web), 9090 (CLI), 3483 (players) │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Lyrion Music Server? (y/n):" "n" INSTALL_LMS + + if [ "$INSTALL_LMS" = "y" ] || [ "$INSTALL_LMS" = "Y" ]; then + echo "Installing Lyrion Music Server..." + LMS_DIR="$DOCKER_DIR/lyrion" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $LMS_DIR" + else + mkdir -p "$LMS_DIR" + cd "$LMS_DIR" + + prompt_text "Path to music folder [default: $ACTUAL_HOME/drives/primary/music]:" "$ACTUAL_HOME/drives/primary/music" MUSIC_PATH + + cat > docker-compose.yml << LMS_COMPOSE +name: lyrion + +services: + lyrion: + image: lmscommunity/lyrionmusicserver:stable + container_name: lyrion + hostname: lyrion + restart: unless-stopped + network_mode: host + environment: + - HTTP_PORT=9000 + - PUID=$(id -u "$ACTUAL_USER") + - PGID=$(id -g "$ACTUAL_USER") + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + volumes: + - ./config:/config:rw + - ${MUSIC_PATH}:/music:ro + - ./playlists:/playlists:rw + - /etc/localtime:/etc/localtime:ro +LMS_COMPOSE + + cat > .env << LMS_ENV +MUSIC_PATH=$MUSIC_PATH +LMS_ENV + + mkdir -p config playlists + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$LMS_DIR" + + echo "" + echo "✓ Lyrion Music Server configured at $LMS_DIR" + echo " Start with: cd $LMS_DIR && docker compose up -d" + echo " Access at: http://localhost:9000" + echo " Note: Uses host networking for Chromecast support" + echo "" + fi + fi + + # ---- MEALIE ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ MEALIE - Recipe manager & meal planner │" + echo "│ Save recipes, plan meals, generate shopping lists. │" + echo "│ Port: 9925 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Mealie? (y/n):" "n" INSTALL_MEALIE + + if [ "$INSTALL_MEALIE" = "y" ] || [ "$INSTALL_MEALIE" = "Y" ]; then + echo "Installing Mealie..." + MEALIE_DIR="$DOCKER_DIR/mealie" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $MEALIE_DIR" + else + mkdir -p "$MEALIE_DIR" + cd "$MEALIE_DIR" + + cat > docker-compose.yml << MEALIE_COMPOSE +name: mealie + +services: + mealie: + image: ghcr.io/mealie-recipes/mealie:latest + container_name: mealie + hostname: mealie + restart: unless-stopped + environment: + - PUID=$(id -u "$ACTUAL_USER") + - PGID=$(id -g "$ACTUAL_USER") + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + - ALLOW_SIGNUP=true + - MAX_WORKERS=1 + - WEB_CONCURRENCY=1 + - BASE_URL=http://localhost:9925 + volumes: + - ./data:/app/data + ports: + - "9925:9000" +MEALIE_COMPOSE + + mkdir -p data + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MEALIE_DIR" + + echo "" + echo "✓ Mealie configured at $MEALIE_DIR" + echo " Start with: cd $MEALIE_DIR && docker compose up -d" + echo " Access at: http://localhost:9925" + echo " Default: changeme@email.com / MyPassword" + echo "" + fi + fi + + # ---- MINECRAFT SERVER ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ MINECRAFT SERVER - Game server with RAM limit │" + echo "│ Fabric server with configurable memory allocation. │" + echo "│ Port: 25565 │" + echo "└─────────────────────────────────────────────────────────────────┘" + prompt_yn "Install Minecraft Server? (y/n):" "n" INSTALL_MINECRAFT + + if [ "$INSTALL_MINECRAFT" = "y" ] || [ "$INSTALL_MINECRAFT" = "Y" ]; then + echo "Installing Minecraft Server..." + MC_DIR="$DOCKER_DIR/minecraft" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would create $MC_DIR" + else + mkdir -p "$MC_DIR" + cd "$MC_DIR" + + echo "" + prompt_text "Maximum RAM for Minecraft (e.g., 2G, 4G) [default: 2G]:" "2G" MC_RAM + MC_RAM=${MC_RAM:-2G} + + cat > docker-compose.yml << MC_COMPOSE +name: minecraft + +services: + minecraft: + image: itzg/minecraft-server:latest + container_name: minecraft + hostname: minecraft + restart: unless-stopped + tty: true + stdin_open: true + environment: + - EULA=TRUE + - TYPE=FABRIC + - VERSION=LATEST + - MEMORY=${MC_RAM} + - TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC") + - OPS= + - MOTD=A Minecraft Server + - DIFFICULTY=normal + - MODE=survival + volumes: + - ./data:/data + ports: + - "25565:25565" + deploy: + resources: + limits: + memory: ${MC_RAM} +MC_COMPOSE + + cat > .env << MC_ENV +MC_RAM=$MC_RAM +MC_ENV + + mkdir -p data + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MC_DIR" + + echo "" + echo "✓ Minecraft Server configured at $MC_DIR" + echo " Start with: cd $MC_DIR && docker compose up -d" + echo " Connect: localhost:25565" + echo " RAM limit: $MC_RAM" + echo " Console: docker attach minecraft (Ctrl+P, Ctrl+Q to detach)" + echo "" + fi + fi + + # ---- LINUX-TO-SYNC (Private Repo) ---- + echo "" + echo "┌─────────────────────────────────────────────────────────────────┐" + echo "│ LINUX-TO-SYNC - Private sync repository │" + echo "│ Clone and set up your private linux-to-sync repository. │" + echo "└─────────────────────────────────────────────────────────────────┘" + echo "" + echo "Note: This requires access to github.com/outis1one/linux-to-sync" + echo "" + echo "To grant access, you need ONE of these:" + echo " 1. SSH key already added to your GitHub account" + echo " 2. GitHub Personal Access Token (PAT)" + echo " 3. GitHub CLI (gh) authenticated" + echo "" + prompt_yn "Set up linux-to-sync? (y/n):" "n" INSTALL_LINUXTOSYNC + + if [ "$INSTALL_LINUXTOSYNC" = "y" ] || [ "$INSTALL_LINUXTOSYNC" = "Y" ]; then + SYNC_DIR="$DOCKER_DIR/linux-to-sync" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would clone linux-to-sync to $SYNC_DIR" + else + echo "" + echo "Choose authentication method:" + echo " [1] SSH (if you have SSH key added to GitHub)" + echo " [2] HTTPS with token (requires Personal Access Token)" + echo "" + prompt_text "Enter 1 or 2 [default: 1]:" "1" AUTH_METHOD + + if [ "$AUTH_METHOD" = "2" ]; then + echo "" + echo "Create a Personal Access Token at:" + echo " https://github.com/settings/tokens/new" + echo " - Select 'repo' scope for full repository access" + echo "" + prompt_text "Enter your GitHub Personal Access Token:" "" GH_TOKEN + + if [ -n "$GH_TOKEN" ]; then + git clone "https://$GH_TOKEN@github.com/outis1one/linux-to-sync.git" "$SYNC_DIR" 2>/dev/null + if [ $? -eq 0 ]; then + # Remove token from remote URL for security + cd "$SYNC_DIR" + git remote set-url origin "https://github.com/outis1one/linux-to-sync.git" + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$SYNC_DIR" + echo "" + echo "✓ linux-to-sync cloned to $SYNC_DIR" + echo " Note: You'll need to enter token again for push/pull" + echo " Or set up: git config credential.helper store" + else + echo "✗ Clone failed. Check your token and try again." + fi + else + echo "No token provided. Skipping." + fi + else + echo "" + echo "Attempting SSH clone..." + echo "(Make sure your SSH key is added to GitHub)" + echo "" + git clone git@github.com:outis1one/linux-to-sync.git "$SYNC_DIR" 2>/dev/null + if [ $? -eq 0 ]; then + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$SYNC_DIR" + echo "" + echo "✓ linux-to-sync cloned to $SYNC_DIR" + else + echo "" + echo "✗ SSH clone failed." + echo "" + echo "To add your SSH key to GitHub:" + echo " 1. Copy your public key: cat ~/.ssh/id_rsa.pub" + echo " 2. Go to: https://github.com/settings/keys" + echo " 3. Click 'New SSH key' and paste your key" + echo "" + echo "Then retry this script or manually clone:" + echo " git clone git@github.com:outis1one/linux-to-sync.git ~/docker/linux-to-sync" + fi + fi + fi + fi + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "Docker applications configured in: $DOCKER_DIR" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo "To start an application:" + echo " cd ~/docker/{appname}" + echo " docker compose up -d" + echo "" + echo "To view logs:" + echo " docker compose logs -f" + echo "" + echo "To stop:" + echo " docker compose down" + echo "" +fi + # Backup System (Optional) echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"