From 2210dd4bed5bd3024fe777ae4f8cb584a7f1c049 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 3 Jun 2026 18:30:58 +0000 Subject: [PATCH] feat: add utilities + cameras batches (v0.9.11) Utilities (8 modules): - mealie: recipe manager, port 9925 - actualbudget: personal finance, port 5006 - traccar: GPS tracking, ports 8082 + 5000-5150 device protocols - fmd: Android FindMyDevice server, random admin password, port 8084 - ddclient: dynamic DNS, config template, default start=n - wg-easy: WireGuard VPN+UI, auto-detects public IP, random password - meshcentral: remote device management, hostname prompt, ports 4430+4433 - magicmirror: smart mirror, 1-3 instances, MMM-* module auto-clone Cameras (2 modules): - frigate: AI NVR, auto-enables /dev/dri, starter config.yml, default start=n - frigate-notify: Frigate push alerts, auto-detects local Frigate+ntfy, no web UI https://claude.ai/code/session_01Y4dMKtkqkpvmgDKoRdzhTG --- CHANGELOG.md | 35 ++++++++ VERSION | 2 +- services/actualbudget.sh | 87 +++++++++++++++++++ services/ddclient.sh | 131 ++++++++++++++++++++++++++++ services/fmd.sh | 96 +++++++++++++++++++++ services/frigate-notify.sh | 135 +++++++++++++++++++++++++++++ services/frigate.sh | 170 ++++++++++++++++++++++++++++++++++++ services/magicmirror.sh | 171 +++++++++++++++++++++++++++++++++++++ services/mealie.sh | 94 ++++++++++++++++++++ services/meshcentral.sh | 116 +++++++++++++++++++++++++ services/traccar.sh | 104 ++++++++++++++++++++++ services/wg-easy.sh | 116 +++++++++++++++++++++++++ 12 files changed, 1256 insertions(+), 1 deletion(-) create mode 100644 services/actualbudget.sh create mode 100644 services/ddclient.sh create mode 100644 services/fmd.sh create mode 100644 services/frigate-notify.sh create mode 100644 services/frigate.sh create mode 100644 services/magicmirror.sh create mode 100644 services/mealie.sh create mode 100644 services/meshcentral.sh create mode 100644 services/traccar.sh create mode 100644 services/wg-easy.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b87a6d..1039bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,41 @@ All notable changes to this project. Versions follow `MAJOR.MINOR.PATCH`. The project is pre-1.0 while the modular system reaches parity with the monolithic `ubuntu-post-install-*.sh` scripts. +## [0.9.11] - 2026-06-03 + +### Added +- **Utilities batch** — 8 service modules migrated from the monolith: + - `services/mealie.sh` *(utilities)* — Recipe manager & meal planner. PUID/PGID baked; + default creds noted (change immediately). Port 9925 → internal 9000. + - `services/actualbudget.sh` *(utilities)* — Open-source personal finance (Actual Budget). + Minimal container; bank sync via SimpleFIN optional. Port 5006. + - `services/traccar.sh` *(utilities)* — GPS tracking server for phones, vehicles, assets. + Ships a starter `config/traccar.xml` with H2 embedded DB. Port 8082 + 5000-5150 device + protocols (TCP+UDP). + - `services/fmd.sh` *(utilities)* — FindMyDevice server for Android. Generates a random + admin password; mobile app from F-Droid (not Play Store). Port 8084. + - `services/ddclient.sh` *(utilities)* — Dynamic DNS updater; no web UI. Ships a + `config/ddclient.conf` template covering Cloudflare, DuckDNS, No-IP. Default start + prompt is "n" — edit config first. + - `services/wg-easy.sh` *(utilities)* — WireGuard VPN with web UI. Auto-detects public + IP for `WG_HOST`; generates random password; requires `NET_ADMIN` + `SYS_MODULE` caps + and `ip_forward` sysctl. Ports 51820/udp (VPN) + 51821/tcp (web). + - `services/meshcentral.sh` *(utilities)* — Self-hosted remote device management server. + Prompts for hostname (domain/IP for agent connections). Ports 4430 (HTTPS) + 4433 (agent). + - `services/magicmirror.sh` *(utilities)* — Modular smart mirror / info dashboard. + Multi-instance (1-3, ports 8081-8083); each instance in `~/docker/magicmirror//`. + Optionally copies existing `config.js` and auto-clones `MMM-*` third-party modules + from GitHub (tries MichMich → bugsounet → MagicMirrorOrg org order). +- **Cameras batch** — 2 service modules: + - `services/frigate.sh` *(cameras)* — AI-powered NVR with object detection. Auto-enables + `/dev/dri/renderD128` for hardware-accelerated detection when present; ships a starter + `config/config.yml` with camera examples. `privileged: true` + 1 GB tmpfs cache. + Ports 5000 (web), 8554 (RTSP restream), 8555 (WebRTC). Default start prompt is "n" — + edit config first. + - `services/frigate-notify.sh` *(cameras)* — Push notification sidecar for Frigate events. + Auto-detects local Frigate and ntfy installs to pre-fill config defaults. Supports ntfy, + Pushover, Discord, Gotify, Telegram, and more. No web UI. + ## [0.9.10] - 2026-06-03 ### Added diff --git a/VERSION b/VERSION index 56f3151..8225a4b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.10 +0.9.11 diff --git a/services/actualbudget.sh b/services/actualbudget.sh new file mode 100644 index 0000000..8fa71f9 --- /dev/null +++ b/services/actualbudget.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# services/actualbudget.sh — Open-source personal finance / budgeting (Actual Budget). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- ACTUALBUDGET ----). +# Own ~/docker/actualbudget/ with a standalone docker-compose.yml. + +register_service actualbudget utilities "Open-source personal finance & budgeting (Actual Budget)" 5006 + +install_actualbudget() { + require_docker || return 1 + + local AB_DIR="$DOCKER_DIR/actualbudget" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Actual Budget would:" + echo " - Create $AB_DIR with docker-compose.yml (data/)" + echo " - Expose port 5006" + echo " - Offer a Caddy reverse proxy and to start the container" + return 0 + fi + + mkdir -p "$AB_DIR/data" + ensure_docker_dir_ownership "$AB_DIR" + cd "$AB_DIR" || return 1 + + local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC") + + cat > docker-compose.yml << 'AB_COMPOSE' +name: actualbudget + +services: + actualbudget: + image: actualbudget/actual-server:latest + container_name: actualbudget + restart: unless-stopped + ports: + - "5006:5006" + volumes: + - ./data:/data + env_file: + - .env +AB_COMPOSE + + cat > .env << AB_ENV +TZ=$TZ_VAL +AB_ENV + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$AB_DIR" + log_success "Actual Budget configured at $AB_DIR" + + configure_caddy_for_service "ActualBudget" "5006" "budget" + + write_readme "$AB_DIR" << MD +# Actual Budget + +Open-source personal finance and budgeting tool. Supports bank sync via +SimpleFIN (requires a SimpleFIN account at simplefin.org). + +- Web UI: http://localhost:5006 +- App data: \`data/\` + +## Manage +\`\`\`bash +cd $AB_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## Notes +- First launch: create a budget file or import an existing one. +- Bank sync requires a SimpleFIN bridge subscription (simplefin.org). +MD + + local START_AB="" + prompt_yn "Start Actual Budget now? (y/n):" "y" START_AB + if [ "$START_AB" = "y" ] || [ "$START_AB" = "Y" ]; then + docker compose up -d && log_success "Actual Budget started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Access at: http://localhost:5006" + echo " Bank sync: simplefin.org (optional, paid)" + echo "" +} diff --git a/services/ddclient.sh b/services/ddclient.sh new file mode 100644 index 0000000..49b9bd9 --- /dev/null +++ b/services/ddclient.sh @@ -0,0 +1,131 @@ +#!/bin/bash +# services/ddclient.sh — Dynamic DNS updater (ddclient). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- DDCLIENT ----). +# Own ~/docker/ddclient/ with a standalone docker-compose.yml + config. +# Supports Cloudflare, DuckDNS, No-IP, and many other providers. +# Edit config/ddclient.conf before starting — no web UI. + +register_service ddclient utilities "Dynamic DNS updater — keep your domain pointing at your home IP (ddclient)" + +install_ddclient() { + require_docker || return 1 + + local DDCLIENT_DIR="$DOCKER_DIR/ddclient" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] ddclient would:" + echo " - Create $DDCLIENT_DIR with docker-compose.yml + config/ddclient.conf template" + echo " - No web UI — edit config/ddclient.conf for your DNS provider before starting" + return 0 + fi + + mkdir -p "$DDCLIENT_DIR" + ensure_docker_dir_ownership "$DDCLIENT_DIR" + cd "$DDCLIENT_DIR" || return 1 + + local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC") + + cat > docker-compose.yml << 'DDCLIENT_COMPOSE' +name: ddclient + +services: + ddclient: + image: lscr.io/linuxserver/ddclient:latest + container_name: ddclient + hostname: ddclient + restart: unless-stopped + environment: + - PUID=1000 + - PGID=1000 + - TZ=${TZ} + volumes: + - ./config:/config +DDCLIENT_COMPOSE + + cat > .env << DDCLIENT_ENV +TZ=$TZ_VAL +DDCLIENT_ENV + + mkdir -p config + + cat > config/ddclient.conf << 'DDCLIENT_CONF' +# ddclient configuration +# Docs: https://ddclient.net/ +# +# ⚠️ YOU MUST EDIT THIS FILE before starting ddclient. +# Uncomment and fill in the block for your DNS provider. + +daemon=300 +syslog=yes +pid=/var/run/ddclient/ddclient.pid +ssl=yes + +# Cloudflare example: +# use=web, web=cloudflare +# protocol=cloudflare +# zone=example.com +# login=token +# password=your-api-token +# example.com + +# DuckDNS example: +# use=web +# protocol=duckdns +# password=your-duckdns-token +# yourdomain.duckdns.org + +# No-IP example: +# use=web +# protocol=noip +# login=your@email.com +# password=your-password +# yourhostname.ddns.net +DDCLIENT_CONF + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$DDCLIENT_DIR" + log_success "ddclient configured at $DDCLIENT_DIR" + + write_readme "$DDCLIENT_DIR" << MD +# ddclient + +Dynamic DNS client — keeps your domain pointing at your home IP address +even when your ISP changes it. No web UI; runs as a background daemon. + +- Config: \`config/ddclient.conf\` — **edit before starting** +- Supported providers: Cloudflare, DuckDNS, No-IP, FreeDNS, and more + +## Setup +1. Edit \`config/ddclient.conf\` for your DNS provider. +2. Start: \`docker compose up -d\` +3. Check logs: \`docker compose logs -f\` + +## Manage +\`\`\`bash +cd $DDCLIENT_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## Docs +- https://ddclient.net/ +- Cloudflare setup: https://ddclient.net/protocols/cloudflare.html +MD + + echo "" + log_warning "Edit config/ddclient.conf for your DNS provider before starting." + echo "" + local START_DDC="" + prompt_yn "Start ddclient now? (y/n):" "n" START_DDC + if [ "$START_DDC" = "y" ] || [ "$START_DDC" = "Y" ]; then + docker compose up -d && log_success "ddclient started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Config: $DDCLIENT_DIR/config/ddclient.conf" + echo " Docs: https://ddclient.net/" + echo "" +} diff --git a/services/fmd.sh b/services/fmd.sh new file mode 100644 index 0000000..d5ba5d5 --- /dev/null +++ b/services/fmd.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# services/fmd.sh — FindMyDevice server for Android device tracking (FMD). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- FINDMYDEVICE ----). +# Own ~/docker/fmd/ with a standalone docker-compose.yml + .env. +# Mobile app: "FindMyDevice" on F-Droid — not the Play Store version. + +register_service fmd utilities "Android device tracking — alternative to Google Find My Device (FMD)" 8084 + +install_fmd() { + require_docker || return 1 + + local FMD_DIR="$DOCKER_DIR/fmd" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] FindMyDevice would:" + echo " - Create $FMD_DIR with docker-compose.yml + .env (data/)" + echo " - Generate a random admin password" + echo " - Expose port 8084" + echo " - Offer a Caddy reverse proxy and to start the container" + return 0 + fi + + mkdir -p "$FMD_DIR" + ensure_docker_dir_ownership "$FMD_DIR" + cd "$FMD_DIR" || return 1 + + local FMD_PASS + FMD_PASS=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16) + + cat > docker-compose.yml << 'FMD_COMPOSE' +name: fmd + +services: + fmd: + image: nulide/findmydevice + container_name: fmd + hostname: fmd + restart: unless-stopped + environment: + - FMD_ADMIN_PASSWORD=${FMD_ADMIN_PASSWORD} + volumes: + - ./data:/fmd/data + ports: + - "8084:8080" +FMD_COMPOSE + + cat > .env << FMD_ENV +FMD_ADMIN_PASSWORD=$FMD_PASS +FMD_ENV + + mkdir -p data + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FMD_DIR" + log_success "FindMyDevice configured at $FMD_DIR" + + configure_caddy_for_service "FindMyDevice" "8084" "fmd" + + write_readme "$FMD_DIR" << MD +# FindMyDevice (FMD) + +Self-hosted Android device tracking — locate, lock, or wipe your device +from the web UI. Alternative to Google's Find My Device. + +- Web UI: http://localhost:8084 +- Admin password: stored in \`.env\` (\`FMD_ADMIN_PASSWORD\`) +- App data: \`data/\` + +## Manage +\`\`\`bash +cd $FMD_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## Mobile app +Install **FindMyDevice** from **F-Droid** (not the Play Store version): +1. Open the app → Settings → Server URL → \`http://YOUR-SERVER-IP:8084\` +2. Enter your admin password from \`.env\` +3. Grant location and accessibility permissions +MD + + local START_FMD="" + prompt_yn "Start FindMyDevice now? (y/n):" "y" START_FMD + if [ "$START_FMD" = "y" ] || [ "$START_FMD" = "Y" ]; then + docker compose up -d && log_success "FindMyDevice started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Access at: http://localhost:8084" + echo " Password: $FMD_PASS (saved in .env)" + echo " Mobile app: FindMyDevice on F-Droid" + echo "" +} diff --git a/services/frigate-notify.sh b/services/frigate-notify.sh new file mode 100644 index 0000000..49c2214 --- /dev/null +++ b/services/frigate-notify.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# services/frigate-notify.sh — Push notification sidecar for Frigate events. +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- FRIGATE-NOTIFY ----). +# Own ~/docker/frigate-notify/ with a standalone docker-compose.yml + config.yml. +# Supports ntfy, Pushover, Discord, Gotify, Telegram, and more. No web UI. +# Auto-detects local Frigate and ntfy installs to pre-fill config defaults. + +register_service frigate-notify cameras "Push alerts for Frigate detection events (Frigate-Notify)" + +install_frigate-notify() { + require_docker || return 1 + + local FN_DIR="$DOCKER_DIR/frigate-notify" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Frigate-Notify would:" + echo " - Create $FN_DIR with docker-compose.yml + config.yml" + echo " - Auto-detect local Frigate and ntfy installs" + echo " - No web UI — configure via config.yml" + return 0 + fi + + mkdir -p "$FN_DIR" + ensure_docker_dir_ownership "$FN_DIR" + cd "$FN_DIR" || return 1 + + cat > docker-compose.yml << 'FN_COMPOSE' +name: frigate-notify + +services: + frigate-notify: + image: ghcr.io/0x2142/frigate-notify:latest + container_name: frigate-notify + hostname: frigate-notify + restart: unless-stopped + volumes: + - ./config.yml:/app/config.yml:ro +FN_COMPOSE + + # Smart defaults based on what's installed + local FRIGATE_URL="http://frigate:5000" + local NTFY_URL="https://ntfy.sh" + local NTFY_TOPIC="frigate-alerts" + + if [ -d "$DOCKER_DIR/frigate" ]; then + log_success "Local Frigate detected — using http://frigate:5000" + else + log_warning "Frigate not found locally — using default URL (update config.yml if needed)" + fi + + if [ -d "$DOCKER_DIR/ntfy" ]; then + NTFY_URL="http://ntfy:80" + log_success "Local ntfy detected — using http://ntfy:80" + else + log_warning "Local ntfy not found — using ntfy.sh (update config.yml for self-hosted)" + fi + + echo "" + prompt_text "Frigate URL [$FRIGATE_URL]:" "$FRIGATE_URL" FRIGATE_URL + prompt_text "ntfy server URL [$NTFY_URL]:" "$NTFY_URL" NTFY_URL + prompt_text "ntfy topic [frigate-alerts]:" "frigate-alerts" NTFY_TOPIC + + cat > config.yml << FN_CONFIG +# Frigate-Notify Configuration +# Docs: https://frigate-notify.0x2142.com +# +# Edit this file if notifications don't arrive — check Frigate URL, +# ntfy server, and that containers share a Docker network. + +frigate: + server: $FRIGATE_URL + webapi: + enabled: true + interval: 30 + +alerts: + general: + send_startup_message: true + labels: + - person + - car + # - dog + # - package + +notifiers: + - name: ntfy + enabled: true + provider: ntfy + config: + server: $NTFY_URL + topic: $NTFY_TOPIC +FN_CONFIG + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FN_DIR" + log_success "Frigate-Notify configured at $FN_DIR" + + write_readme "$FN_DIR" << MD +# Frigate-Notify + +Push notification sidecar for Frigate — sends alerts when Frigate detects +people, cars, animals, or custom objects. Supports ntfy, Pushover, Discord, +Gotify, Telegram, and more. No web UI. + +- Config: \`config.yml\` — edit notification targets here +- Frigate events polled every 30 seconds by default +- Docs: https://frigate-notify.0x2142.com + +## Manage +\`\`\`bash +cd $FN_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # check for delivery errors +docker compose pull && docker compose up -d # update +\`\`\` + +## Adding more notifiers +Edit \`config.yml\` and add entries under \`notifiers:\`. Supported providers: +ntfy, Pushover, Discord (webhook), Gotify, Telegram, SMTP, and more. +See: https://frigate-notify.0x2142.com/configuration/alerts/ +MD + + local START_FN="" + prompt_yn "Start Frigate-Notify now? (y/n):" "y" START_FN + if [ "$START_FN" = "y" ] || [ "$START_FN" = "Y" ]; then + docker compose up -d && log_success "Frigate-Notify started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Config: $FN_DIR/config.yml" + echo " Docs: https://frigate-notify.0x2142.com" + echo "" +} diff --git a/services/frigate.sh b/services/frigate.sh new file mode 100644 index 0000000..836961b --- /dev/null +++ b/services/frigate.sh @@ -0,0 +1,170 @@ +#!/bin/bash +# services/frigate.sh — AI-powered NVR for security cameras (Frigate). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- FRIGATE NVR ----). +# Own ~/docker/frigate/ with a standalone docker-compose.yml + .env + config.yml. +# Auto-enables /dev/dri/renderD128 for hardware detection (Intel/AMD) when present. +# YOU MUST edit config/config.yml to add your camera RTSP streams before starting. + +register_service frigate cameras "AI-powered NVR — object detection on security cameras (Frigate)" 5000 + +install_frigate() { + require_docker || return 1 + + local FRIGATE_DIR="$DOCKER_DIR/frigate" + local DEFAULT_MEDIA="$ACTUAL_HOME/frigate" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Frigate would:" + echo " - Create $FRIGATE_DIR with docker-compose.yml + .env + config/config.yml" + echo " - Auto-enable /dev/dri/renderD128 for GPU-assisted detection if present" + echo " - Expose ports 5000 (web), 8554 (RTSP restream), 8555 (WebRTC)" + echo " - Write a starter config.yml — edit to add camera streams before starting" + echo " - Offer a Caddy reverse proxy and to start the container" + return 0 + fi + + local FRIGATE_MEDIA="" + prompt_text "Path for recordings/snapshots [$DEFAULT_MEDIA]:" "$DEFAULT_MEDIA" FRIGATE_MEDIA + FRIGATE_MEDIA="${FRIGATE_MEDIA/#\~/$ACTUAL_HOME}"; FRIGATE_MEDIA="${FRIGATE_MEDIA%/}" + + mkdir -p "$FRIGATE_DIR" + ensure_docker_dir_ownership "$FRIGATE_DIR" + cd "$FRIGATE_DIR" || return 1 + + local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC") + + # Hardware detection: include /dev/dri only when a render node exists + local DEVICE_BLOCK="" + if [ -e /dev/dri/renderD128 ]; then + DEVICE_BLOCK=" devices: + - /dev/dri/renderD128:/dev/dri/renderD128" + log_success "Render node found — enabling hardware-accelerated detection" + else + log_warning "No /dev/dri/renderD128 — Frigate will use CPU detection." + fi + + cat > docker-compose.yml << FRIGATE_COMPOSE +name: frigate + +services: + frigate: + image: ghcr.io/blakeblackshear/frigate:stable + container_name: frigate + hostname: frigate + restart: unless-stopped + privileged: true + shm_size: "256mb" + environment: + - TZ=$TZ_VAL +$DEVICE_BLOCK + volumes: + - ./config:/config + - \${FRIGATE_MEDIA}:/media/frigate + - type: tmpfs + target: /tmp/cache + tmpfs: + size: 1000000000 + ports: + - "5000:5000" + - "8554:8554" + - "8555:8555/tcp" + - "8555:8555/udp" +FRIGATE_COMPOSE + + cat > .env << FRIGATE_ENV +FRIGATE_MEDIA=$FRIGATE_MEDIA +FRIGATE_ENV + + mkdir -p config + mkdir -p "$FRIGATE_MEDIA" + + cat > config/config.yml << 'FRIGATE_CONFIG' +# Frigate Configuration — Docs: https://docs.frigate.video +# +# ⚠️ YOU MUST EDIT THIS FILE to add your cameras before starting Frigate. + +mqtt: + enabled: false # Set to true and configure if you use Home Assistant + +cameras: + # Example — replace with your camera details: + # front_door: + # ffmpeg: + # inputs: + # - path: rtsp://user:pass@192.168.1.100:554/stream + # roles: [detect, record] + # detect: + # width: 1280 + # height: 720 + # fps: 5 + +detectors: + default: + type: cpu # Change to 'edgetpu' for Coral TPU or 'openvino' for Intel GPU + +record: + enabled: true + retain: + days: 7 + mode: motion + +snapshots: + enabled: true + retain: + default: 7 +FRIGATE_CONFIG + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FRIGATE_DIR" + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$FRIGATE_MEDIA" 2>/dev/null || true + log_success "Frigate configured at $FRIGATE_DIR" + + configure_caddy_for_service "Frigate" "5000" "frigate" + + write_readme "$FRIGATE_DIR" << MD +# Frigate NVR + +AI-powered network video recorder with real-time object detection for +security cameras. Detects people, cars, animals, and more. + +- Web UI: http://localhost:5000 +- RTSP restream: port 8554 +- WebRTC: port 8555 +- Recordings: \`$FRIGATE_MEDIA\` +- Config: \`config/config.yml\` — **add your camera RTSP streams here** + +## Manage +\`\`\`bash +cd $FRIGATE_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## First steps +1. Edit \`config/config.yml\` — add your camera RTSP URLs under \`cameras:\` +2. Start Frigate: \`docker compose up -d\` +3. Open http://localhost:5000 to view cameras and configure detection zones + +## Hardware acceleration +- Intel/AMD GPU: uncomment the \`devices: [/dev/dri/renderD128]\` block +- Google Coral TPU: set \`detectors.default.type: edgetpu\` + add USB device +- Docs: https://docs.frigate.video/configuration/hardware_acceleration +MD + + echo "" + log_warning "Edit config/config.yml to add your camera RTSP streams before starting." + echo "" + local START_FRIGATE="" + prompt_yn "Start Frigate now anyway? (y/n):" "n" START_FRIGATE + if [ "$START_FRIGATE" = "y" ] || [ "$START_FRIGATE" = "Y" ]; then + docker compose up -d && log_success "Frigate started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Access at: http://localhost:5000" + echo " Config: $FRIGATE_DIR/config/config.yml (add cameras here)" + echo "" +} diff --git a/services/magicmirror.sh b/services/magicmirror.sh new file mode 100644 index 0000000..e9d0851 --- /dev/null +++ b/services/magicmirror.sh @@ -0,0 +1,171 @@ +#!/bin/bash +# services/magicmirror.sh — Modular smart mirror / info dashboard (MagicMirror²). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- MAGIC MIRROR ----). +# Supports 1-3 instances (ports 8081-8083) each in ~/docker/magicmirror//. +# If you provide an existing config.js, third-party MMM-* modules are detected +# and cloned from GitHub automatically. + +register_service magicmirror utilities "Modular smart mirror / info dashboard (MagicMirror²)" 8081 + +install_magicmirror() { + require_docker || return 1 + + local MM_BASE="$DOCKER_DIR/magicmirror" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] MagicMirror would:" + echo " - Ask how many instances (1-3, ports 8081-8083)" + echo " - Create $MM_BASE// for each instance" + echo " - Optionally copy your existing config.js + clone MMM-* modules" + echo " - Offer a Caddy reverse proxy (first instance) and to start" + return 0 + fi + + # Number of instances + local MM_COUNT="" + prompt_text "How many MagicMirror instances? [1-3, default: 1]:" "1" MM_COUNT + MM_COUNT="${MM_COUNT:-1}" + [ "$MM_COUNT" -gt 3 ] 2>/dev/null && MM_COUNT=3 + [ "$MM_COUNT" -lt 1 ] 2>/dev/null && MM_COUNT=1 + + mkdir -p "$MM_BASE" + chown "$ACTUAL_USER:$ACTUAL_USER" "$MM_BASE" + + local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC") + local i MM_PORT MM_DIR + + for i in $(seq 1 "$MM_COUNT"); do + MM_PORT=$((8080 + i)) + MM_DIR="$MM_BASE/$i" + + echo "" + echo "── Instance $i (port $MM_PORT) ──" + mkdir -p "$MM_DIR" + ensure_docker_dir_ownership "$MM_DIR" + cd "$MM_DIR" || continue + + 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=$TZ_VAL + 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 + + # Offer to copy an existing config.js + local MM_CONFIG_CHOICE="" + echo "" + echo " Config options:" + echo " [1] Use default config (basic built-in modules)" + echo " [2] Copy existing config.js from a path" + if [ "$UNATTENDED" = true ]; then + MM_CONFIG_CHOICE="1" + else + read -r -p " Choose [1]: " MM_CONFIG_CHOICE + MM_CONFIG_CHOICE="${MM_CONFIG_CHOICE:-1}" + fi + + if [ "$MM_CONFIG_CHOICE" = "2" ]; then + local MM_CONFIG_PATH="" + read -r -p " Path to config.js: " MM_CONFIG_PATH + if [ -f "$MM_CONFIG_PATH" ]; then + cp "$MM_CONFIG_PATH" config/config.js + log_success "Copied config from $MM_CONFIG_PATH" + + # Copy custom.css if it exists next to config.js + local MM_CSS_DIR="${MM_CONFIG_PATH%/*}" + [ -f "$MM_CSS_DIR/custom.css" ] && cp "$MM_CSS_DIR/custom.css" css/custom.css && log_success "Copied custom.css" + + # Detect MMM-* third-party modules referenced in config + local THIRD_PARTY_MODS + THIRD_PARTY_MODS=$(grep -oP "module:\s*[\"']MMM-[^\"']+[\"']" config/config.js 2>/dev/null \ + | sed "s/module:\s*[\"']//g" | sed "s/[\"']//g" | sort -u) + + if [ -n "$THIRD_PARTY_MODS" ]; then + echo "" + echo " Third-party modules found in config:" + echo "$THIRD_PARTY_MODS" | while read -r mod; do echo " - $mod"; done + echo "" + local MM_DL_MODS="" + prompt_yn " Download these modules from GitHub? (y/n):" "y" MM_DL_MODS + if [ "$MM_DL_MODS" = "y" ] || [ "$MM_DL_MODS" = "Y" ]; then + cd modules || true + echo "$THIRD_PARTY_MODS" | while read -r mod; do + [ -z "$mod" ] || [ -d "$mod" ] && continue + echo " Downloading $mod..." + git clone --depth 1 "https://github.com/MichMich/${mod}.git" 2>/dev/null || \ + git clone --depth 1 "https://github.com/bugsounet/${mod}.git" 2>/dev/null || \ + git clone --depth 1 "https://github.com/MagicMirrorOrg/${mod}.git" 2>/dev/null || \ + log_warning "Could not find $mod — search at https://github.com/topics/magicmirror" + done + cd "$MM_DIR" || true + fi + fi + else + log_warning "File not found: $MM_CONFIG_PATH — using default config" + fi + fi + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MM_DIR" + log_success "MagicMirror instance $i configured at $MM_DIR (port $MM_PORT)" + + # Offer Caddy only for first instance + [ "$i" -eq 1 ] && configure_caddy_for_service "MagicMirror" "$MM_PORT" "mirror" + + local START_MM="" + prompt_yn "Start instance $i now? (y/n):" "y" START_MM + if [ "$START_MM" = "y" ] || [ "$START_MM" = "Y" ]; then + docker compose up -d && log_success "MagicMirror instance $i started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo " Access at: http://localhost:$MM_PORT" + done + + write_readme "$MM_BASE" << MD +# MagicMirror² + +Modular smart mirror / info dashboard. Each instance has its own port and +independent config, modules, and CSS. + +| Instance | Port | Directory | +|----------|------|-----------| +$(for j in $(seq 1 "$MM_COUNT"); do echo "| $j | $((8080 + j)) | \`$MM_BASE/$j/\` |"; done) + +## Manage +\`\`\`bash +cd $MM_BASE/1 +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## Config +- Edit \`/config/config.js\` for layout and module settings. +- Add CSS overrides in \`/css/custom.css\`. +- Third-party modules go in \`/modules//\`. + Then run \`docker exec magicmirror-PORT sh -c 'cd /opt/magic_mirror/modules/ && npm install --production'\` + +## Finding modules +Browse: https://github.com/topics/magicmirror +MD + + echo "" + echo " MagicMirror config: $MM_BASE//config/config.js" + echo "" +} diff --git a/services/mealie.sh b/services/mealie.sh new file mode 100644 index 0000000..1db9eb3 --- /dev/null +++ b/services/mealie.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# services/mealie.sh — Recipe manager & meal planner (Mealie). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- MEALIE ----). +# Own ~/docker/mealie/ with a standalone docker-compose.yml. + +register_service mealie utilities "Recipe manager & meal planner (Mealie)" 9925 + +install_mealie() { + require_docker || return 1 + + local MEALIE_DIR="$DOCKER_DIR/mealie" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Mealie would:" + echo " - Create $MEALIE_DIR with docker-compose.yml (data/)" + echo " - Expose port 9925" + echo " - Default login: changeme@email.com / MyPassword (change immediately)" + echo " - Offer a Caddy reverse proxy and to start the container" + return 0 + fi + + mkdir -p "$MEALIE_DIR" + ensure_docker_dir_ownership "$MEALIE_DIR" + cd "$MEALIE_DIR" || return 1 + + local TZ_VAL UID_VAL GID_VAL + TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC") + UID_VAL=$(id -u "$ACTUAL_USER"); GID_VAL=$(id -g "$ACTUAL_USER") + + 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=$UID_VAL + - PGID=$GID_VAL + - TZ=$TZ_VAL + - 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" + log_success "Mealie configured at $MEALIE_DIR" + + configure_caddy_for_service "Mealie" "9925" "recipes" + + write_readme "$MEALIE_DIR" << MD +# Mealie + +Recipe manager and meal planner — import recipes from any URL, plan meals, +and generate shopping lists. Optional AI-powered recipe parsing. + +- Web UI: http://localhost:9925 +- Default login: changeme@email.com / MyPassword (change immediately!) +- App data: \`data/\` + +## Manage +\`\`\`bash +cd $MEALIE_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## Notes +- If using Caddy, update \`BASE_URL\` in \`docker-compose.yml\` to your domain. +MD + + local START_MEALIE="" + prompt_yn "Start Mealie now? (y/n):" "y" START_MEALIE + if [ "$START_MEALIE" = "y" ] || [ "$START_MEALIE" = "Y" ]; then + docker compose up -d && log_success "Mealie started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Access at: http://localhost:9925" + echo " Default: changeme@email.com / MyPassword (change immediately!)" + echo "" +} diff --git a/services/meshcentral.sh b/services/meshcentral.sh new file mode 100644 index 0000000..7dfb25e --- /dev/null +++ b/services/meshcentral.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# services/meshcentral.sh — Self-hosted remote device management server (MeshCentral). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- MESHCENTRAL SERVER ----). +# Own ~/docker/meshcentral/ with a standalone docker-compose.yml + .env. +# HTTPS on port 4430, agent listener on 4433. First visit: create admin account. + +register_service meshcentral utilities "Self-hosted remote device management server (MeshCentral)" 4430 + +install_meshcentral() { + require_docker || return 1 + + local MC_DIR="$DOCKER_DIR/meshcentral" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] MeshCentral would:" + echo " - Create $MC_DIR with docker-compose.yml + .env (data/ files/ backups/)" + echo " - Prompt for hostname (domain or IP for agent connections)" + echo " - Expose port 4430 (HTTPS web) and 4433 (agent)" + echo " - Offer a Caddy reverse proxy and to start the container" + return 0 + fi + + local MC_HOSTNAME="" + prompt_text "MeshCentral hostname (domain or IP) [localhost]:" "localhost" MC_HOSTNAME + MC_HOSTNAME="${MC_HOSTNAME:-localhost}" + + mkdir -p "$MC_DIR" + ensure_docker_dir_ownership "$MC_DIR" + cd "$MC_DIR" || return 1 + + cat > docker-compose.yml << 'MC_COMPOSE' +name: meshcentral + +services: + meshcentral: + image: ghcr.io/ylianst/meshcentral:latest + container_name: meshcentral + hostname: meshcentral + restart: unless-stopped + environment: + - NODE_ENV=production + - HOSTNAME=${MC_HOSTNAME:-localhost} + - REVERSE_PROXY=${MC_REVERSE_PROXY:-false} + - REVERSE_PROXY_TLS_PORT=${MC_TLS_PORT:-443} + - IFRAME=false + - ALLOW_NEW_ACCOUNTS=true + - WEBRTC=true + volumes: + - ./data:/opt/meshcentral/meshcentral-data + - ./files:/opt/meshcentral/meshcentral-files + - ./backups:/opt/meshcentral/meshcentral-backups + ports: + - "4430:443" + - "4433:4433" +MC_COMPOSE + + cat > .env << MC_ENV +MC_HOSTNAME=$MC_HOSTNAME +MC_REVERSE_PROXY=false +MC_TLS_PORT=443 +MC_ENV + + mkdir -p data files backups + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$MC_DIR" + log_success "MeshCentral configured at $MC_DIR" + + configure_caddy_for_service "MeshCentral" "4430" "mesh" + + write_readme "$MC_DIR" << MD +# MeshCentral + +Self-hosted remote device management — remotely access, manage, and monitor +all your computers from a single web interface. Install agents on each device. + +- Web UI: https://localhost:4430 (self-signed cert on first launch) +- Agent listener: port 4433 (devices connect here — forward this port if remote) +- Hostname: \`$MC_HOSTNAME\` (update \`MC_HOSTNAME\` in .env if it changes) +- App data: \`data/\`, \`files/\`, \`backups/\` + +## Manage +\`\`\`bash +cd $MC_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## First launch +1. Open https://localhost:4430 (accept the self-signed cert warning) +2. Create your admin account +3. Go to "My Devices" → "+ Add Device" → download the agent for each OS +4. Install the agent on every computer you want to manage + +## Remote access +For devices outside your LAN to connect: +- Forward **TCP port 4433** on your router to this server +- Set \`MC_HOSTNAME\` in \`.env\` to your public domain/IP, then restart + +## Docs +https://meshcentral.com/docs/ +MD + + local START_MC="" + prompt_yn "Start MeshCentral now? (y/n):" "y" START_MC + if [ "$START_MC" = "y" ] || [ "$START_MC" = "Y" ]; then + docker compose up -d && log_success "MeshCentral started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Access at: https://localhost:4430 (accept self-signed cert)" + echo " First visit: create your admin account" + echo "" +} diff --git a/services/traccar.sh b/services/traccar.sh new file mode 100644 index 0000000..0a03bdd --- /dev/null +++ b/services/traccar.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# services/traccar.sh — GPS tracking server (Traccar). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- TRACCAR ----). +# Own ~/docker/traccar/ with a standalone docker-compose.yml + config XML. + +register_service traccar utilities "GPS tracking server — phones, vehicles, assets (Traccar)" 8082 + +install_traccar() { + require_docker || return 1 + + local TRACCAR_DIR="$DOCKER_DIR/traccar" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Traccar would:" + echo " - Create $TRACCAR_DIR with docker-compose.yml + config/traccar.xml" + echo " - Expose port 8082 (web) and 5000-5150 (device protocols)" + echo " - Default login: admin@admin.com / admin (change immediately!)" + echo " - Offer a Caddy reverse proxy and to start the container" + return 0 + fi + + mkdir -p "$TRACCAR_DIR" + ensure_docker_dir_ownership "$TRACCAR_DIR" + cd "$TRACCAR_DIR" || return 1 + + cat > docker-compose.yml << 'TRACCAR_COMPOSE' +name: traccar + +services: + traccar: + image: traccar/traccar:latest + container_name: traccar + hostname: traccar + restart: unless-stopped + volumes: + - ./logs:/opt/traccar/logs:rw + - ./data:/opt/traccar/data:rw + - ./config/traccar.xml:/opt/traccar/conf/traccar.xml:ro + ports: + - "8082:8082" + - "5000-5150:5000-5150" + - "5000-5150:5000-5150/udp" +TRACCAR_COMPOSE + + mkdir -p logs data config + + cat > config/traccar.xml << 'TRACCAR_XML' + + + + + + ./conf/default.xml + org.h2.Driver + jdbc:h2:/opt/traccar/data/database + sa + + +TRACCAR_XML + + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$TRACCAR_DIR" + log_success "Traccar configured at $TRACCAR_DIR" + + configure_caddy_for_service "Traccar" "8082" "traccar" + + write_readme "$TRACCAR_DIR" << MD +# Traccar + +GPS tracking server. Track phones, vehicles, and assets via the Traccar +Android/iOS app, OwnTracks, or any of 200+ supported device protocols. + +- Web UI: http://localhost:8082 +- Default login: admin@admin.com / admin (change immediately!) +- Device protocols: ports 5000-5150 (TCP + UDP) +- Config: \`config/traccar.xml\` +- App data: \`data/\` and \`logs/\` + +## Manage +\`\`\`bash +cd $TRACCAR_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## Mobile apps +- Traccar Client (Android/iOS): set server to \`http://YOUR-IP:8082\` +- OwnTracks (Android/iOS): configure HTTP endpoint to Traccar +MD + + local START_TRACCAR="" + prompt_yn "Start Traccar now? (y/n):" "y" START_TRACCAR + if [ "$START_TRACCAR" = "y" ] || [ "$START_TRACCAR" = "Y" ]; then + docker compose up -d && log_success "Traccar started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Access at: http://localhost:8082" + echo " Default: admin@admin.com / admin (change immediately!)" + echo "" +} diff --git a/services/wg-easy.sh b/services/wg-easy.sh new file mode 100644 index 0000000..422ef03 --- /dev/null +++ b/services/wg-easy.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# services/wg-easy.sh — WireGuard VPN with a web management UI (wg-easy). +# Part of the modular post-install system (sourced by setup.sh). +# +# Ported from ubuntu-post-install-24.04-crowdsec.sh (# ---- WG-EASY ----). +# Own ~/docker/wg-easy/ with a standalone docker-compose.yml + .env. +# Requires cap_add: NET_ADMIN + SYS_MODULE and ip_forward sysctl. +# Forward UDP 51820 on your router to this server for external VPN access. + +register_service wg-easy utilities "WireGuard VPN with web management UI (wg-easy)" 51821 + +install_wg-easy() { + require_docker || return 1 + + local WGEASY_DIR="$DOCKER_DIR/wg-easy" + + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] wg-easy would:" + echo " - Create $WGEASY_DIR with docker-compose.yml + .env (config/)" + echo " - Auto-detect public IP for WG_HOST" + echo " - Generate a random web UI password" + echo " - Expose port 51821 (web UI) + 51820/udp (VPN)" + echo " - Require router port-forward: UDP 51820 → this server" + echo " - Offer a Caddy reverse proxy and to start the container" + return 0 + fi + + mkdir -p "$WGEASY_DIR" + ensure_docker_dir_ownership "$WGEASY_DIR" + cd "$WGEASY_DIR" || return 1 + + # Auto-detect public IP as default for WG_HOST + local PUBLIC_IP WG_HOST WG_PASSWORD + PUBLIC_IP=$(curl -s --connect-timeout 5 ifconfig.me 2>/dev/null || echo "your-public-ip") + WG_PASSWORD=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16) + + prompt_text "Public IP or hostname for VPN [$PUBLIC_IP]:" "$PUBLIC_IP" WG_HOST + + cat > docker-compose.yml << 'WGEASY_COMPOSE' +name: wg-easy + +services: + wg-easy: + image: ghcr.io/wg-easy/wg-easy:latest + container_name: wg-easy + hostname: wg-easy + restart: unless-stopped + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + environment: + - WG_HOST=${WG_HOST} + - PASSWORD=${WG_PASSWORD} + - WG_DEFAULT_DNS=1.1.1.1 + volumes: + - ./config:/etc/wireguard + ports: + - "51820:51820/udp" + - "51821:51821/tcp" +WGEASY_COMPOSE + + cat > .env << WGEASY_ENV +WG_HOST=$WG_HOST +WG_PASSWORD=$WG_PASSWORD +WGEASY_ENV + + mkdir -p config + chown -R "$ACTUAL_USER:$ACTUAL_USER" "$WGEASY_DIR" + log_success "wg-easy configured at $WGEASY_DIR" + + configure_caddy_for_service "wg-easy" "51821" "vpn" + + write_readme "$WGEASY_DIR" << MD +# wg-easy + +WireGuard VPN with a web UI for managing clients, generating QR codes, +and monitoring connections. + +- Web UI: http://localhost:51821 +- VPN: UDP port 51820 (forward this on your router) +- Password: stored in \`.env\` (\`WG_PASSWORD\`) +- VPN host: \`$WG_HOST\` (update \`WG_HOST\` in .env if your IP changes) +- Config: \`config/\` + +## Manage +\`\`\`bash +cd $WGEASY_DIR +docker compose up -d # start +docker compose down # stop +docker compose logs -f # logs +docker compose pull && docker compose up -d # update +\`\`\` + +## Router setup +Forward **UDP port 51820** to this server's LAN IP for external VPN access. + +## Adding clients +Open http://localhost:51821, log in with your password, click "+ New Client", +download or scan the QR code with the WireGuard app. +MD + + local START_WGEASY="" + prompt_yn "Start wg-easy now? (y/n):" "y" START_WGEASY + if [ "$START_WGEASY" = "y" ] || [ "$START_WGEASY" = "Y" ]; then + docker compose up -d && log_success "wg-easy started" || log_warning "Failed to start — check: docker compose logs" + fi + + echo "" + echo " Web UI: http://localhost:51821" + echo " Password: $WG_PASSWORD (saved in .env)" + echo " Router: forward UDP 51820 → this server for external VPN access" + echo "" +}