diff --git a/README.md b/README.md index b48897e..54c237c 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,15 @@ as intuitive as Dockge, as capable as Portainer for Compose workflows. (`/proc/net/tcp[6]`) and running container bindings, with a confirm dialog. - **Resource limits**: CPU/memory sliders in the editor → `deploy.resources.limits`. - **Template library**: each template is a ready-to-run **stack folder** in git - (`backend/templates//` — `compose.yaml` + `.env.example` + `template.json`). - Bundled set: Jellyfin, Vaultwarden, Uptime-Kuma, Paperless-NGX, Gitea. "Pull" - copies the whole folder into a new stack (`.env.example` → `.env`) which you then - edit and deploy. Save any stack back as a custom template (stored under - `${DATA_DIR}/templates/`). Add your own by dropping a folder into the templates dir. + (`backend/templates//` — `compose.yaml` + `.env.example` + `template.json`, + plus any extra config the app needs, e.g. `prometheus.yml` or a `Caddyfile`). + 83 bundled homelab apps across media, *arr automation, networking, reverse + proxies, VPN, SSO, monitoring, dashboards, files/backup, notes and wikis, home + automation, dev tooling, databases, local AI and more — searchable and + filterable by tag on the Templates page. "Pull" copies the whole folder into a + new stack (`.env.example` → `.env`) which you then edit and deploy. Save any + stack back as a custom template (stored under `${DATA_DIR}/templates/`). Add + your own by dropping a folder into the templates dir. - **Healthcheck status** surfaced per container in the stack overview. ### Phase 4 — Operations diff --git a/backend/templates/actual-budget/.env.example b/backend/templates/actual-budget/.env.example new file mode 100644 index 0000000..09a0c6d --- /dev/null +++ b/backend/templates/actual-budget/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/actual +HTTP_PORT=5006 diff --git a/backend/templates/actual-budget/compose.yaml b/backend/templates/actual-budget/compose.yaml new file mode 100644 index 0000000..49ae6b3 --- /dev/null +++ b/backend/templates/actual-budget/compose.yaml @@ -0,0 +1,11 @@ +services: + actual: + image: ghcr.io/actualbudget/actual-server:latest + container_name: actual-budget + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-5006}:5006" + volumes: + - ${DATA_PATH:-/srv/actual}:/data diff --git a/backend/templates/actual-budget/template.json b/backend/templates/actual-budget/template.json new file mode 100644 index 0000000..2bfd8f4 --- /dev/null +++ b/backend/templates/actual-budget/template.json @@ -0,0 +1,9 @@ +{ + "name": "Actual Budget", + "description": "Local-first envelope budgeting with end-to-end encrypted sync across devices.", + "tags": [ + "finance", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/adguard-home/.env.example b/backend/templates/adguard-home/.env.example new file mode 100644 index 0000000..adc5bad --- /dev/null +++ b/backend/templates/adguard-home/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/adguardhome +DNS_PORT=53 +SETUP_PORT=3300 +HTTP_PORT=8082 diff --git a/backend/templates/adguard-home/compose.yaml b/backend/templates/adguard-home/compose.yaml new file mode 100644 index 0000000..5be2531 --- /dev/null +++ b/backend/templates/adguard-home/compose.yaml @@ -0,0 +1,14 @@ +services: + adguardhome: + image: adguard/adguardhome:latest + container_name: adguardhome + restart: unless-stopped + ports: + - "${DNS_PORT:-53}:53/tcp" + - "${DNS_PORT:-53}:53/udp" + # Setup wizard on first run; the UI moves to HTTP_PORT afterwards. + - "${SETUP_PORT:-3300}:3000/tcp" + - "${HTTP_PORT:-8082}:80/tcp" + volumes: + - ${DATA_PATH:-/srv/adguardhome}/work:/opt/adguardhome/work + - ${DATA_PATH:-/srv/adguardhome}/conf:/opt/adguardhome/conf diff --git a/backend/templates/adguard-home/template.json b/backend/templates/adguard-home/template.json new file mode 100644 index 0000000..21da9ec --- /dev/null +++ b/backend/templates/adguard-home/template.json @@ -0,0 +1,10 @@ +{ + "name": "AdGuard Home", + "description": "DNS server with ad and tracker blocking, DoH/DoT and per-client rules.", + "tags": [ + "network", + "dns", + "ad-blocking" + ], + "gpu": null +} diff --git a/backend/templates/audiobookshelf/.env.example b/backend/templates/audiobookshelf/.env.example new file mode 100644 index 0000000..97e504e --- /dev/null +++ b/backend/templates/audiobookshelf/.env.example @@ -0,0 +1,5 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/audiobookshelf +AUDIOBOOKS_PATH=/srv/media/audiobooks +PODCASTS_PATH=/srv/media/podcasts +HTTP_PORT=13378 diff --git a/backend/templates/audiobookshelf/compose.yaml b/backend/templates/audiobookshelf/compose.yaml new file mode 100644 index 0000000..3c99de1 --- /dev/null +++ b/backend/templates/audiobookshelf/compose.yaml @@ -0,0 +1,14 @@ +services: + audiobookshelf: + image: ghcr.io/advplyr/audiobookshelf:latest + container_name: audiobookshelf + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-13378}:80" + volumes: + - ${DATA_PATH:-/srv/audiobookshelf}/config:/config + - ${DATA_PATH:-/srv/audiobookshelf}/metadata:/metadata + - ${AUDIOBOOKS_PATH:-/srv/media/audiobooks}:/audiobooks + - ${PODCASTS_PATH:-/srv/media/podcasts}:/podcasts diff --git a/backend/templates/audiobookshelf/template.json b/backend/templates/audiobookshelf/template.json new file mode 100644 index 0000000..435ffc1 --- /dev/null +++ b/backend/templates/audiobookshelf/template.json @@ -0,0 +1,10 @@ +{ + "name": "Audiobookshelf", + "description": "Audiobook and podcast server that keeps progress in sync across devices.", + "tags": [ + "media", + "books", + "streaming" + ], + "gpu": null +} diff --git a/backend/templates/authelia/.env.example b/backend/templates/authelia/.env.example new file mode 100644 index 0000000..ce9fcc4 --- /dev/null +++ b/backend/templates/authelia/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/authelia +HTTP_PORT=9091 diff --git a/backend/templates/authelia/compose.yaml b/backend/templates/authelia/compose.yaml new file mode 100644 index 0000000..7e270b7 --- /dev/null +++ b/backend/templates/authelia/compose.yaml @@ -0,0 +1,13 @@ +services: + authelia: + image: authelia/authelia:latest + container_name: authelia + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-9091}:9091" + volumes: + - ./configuration.yml:/config/configuration.yml:ro + - ./users_database.yml:/config/users_database.yml + - ${DATA_PATH:-/srv/authelia}:/config/db diff --git a/backend/templates/authelia/configuration.yml b/backend/templates/authelia/configuration.yml new file mode 100644 index 0000000..072ab2e --- /dev/null +++ b/backend/templates/authelia/configuration.yml @@ -0,0 +1,34 @@ +# Minimal Authelia config. Replace every "change-me" and the example domains. +theme: dark + +identity_validation: + reset_password: + jwt_secret: change-me-jwt-secret + +server: + address: tcp://0.0.0.0:9091 + +authentication_backend: + file: + path: /config/users_database.yml + +access_control: + default_policy: deny + rules: + - domain: "*.example.com" + policy: two_factor + +session: + secret: change-me-session-secret + cookies: + - domain: example.com + authelia_url: https://auth.example.com + +storage: + encryption_key: change-me-encryption-key-at-least-20-chars + local: + path: /config/db/db.sqlite3 + +notifier: + filesystem: + filename: /config/db/notification.txt diff --git a/backend/templates/authelia/template.json b/backend/templates/authelia/template.json new file mode 100644 index 0000000..dd0ef12 --- /dev/null +++ b/backend/templates/authelia/template.json @@ -0,0 +1,10 @@ +{ + "name": "Authelia", + "description": "Lightweight authentication and 2FA portal for reverse-proxy forward auth.", + "tags": [ + "security", + "identity", + "sso" + ], + "gpu": null +} diff --git a/backend/templates/authelia/users_database.yml b/backend/templates/authelia/users_database.yml new file mode 100644 index 0000000..1febf05 --- /dev/null +++ b/backend/templates/authelia/users_database.yml @@ -0,0 +1,10 @@ +# Generate a password hash with: +# docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword' +users: + admin: + disabled: false + displayname: "Admin" + password: "$argon2id$v=19$m=65536,t=3,p=4$REPLACE_ME" + email: admin@example.com + groups: + - admins diff --git a/backend/templates/authentik/.env.example b/backend/templates/authentik/.env.example new file mode 100644 index 0000000..72c4742 --- /dev/null +++ b/backend/templates/authentik/.env.example @@ -0,0 +1,7 @@ +DATA_PATH=/srv/authentik +AUTHENTIK_TAG=2026.8.0 +HTTP_PORT=9200 +HTTPS_PORT=9243 +# Both required. Generate with: openssl rand -base64 36 +PG_PASS=change-me +AUTHENTIK_SECRET_KEY=change-me diff --git a/backend/templates/authentik/compose.yaml b/backend/templates/authentik/compose.yaml new file mode 100644 index 0000000..5a4909e --- /dev/null +++ b/backend/templates/authentik/compose.yaml @@ -0,0 +1,61 @@ +services: + postgresql: + image: postgres:16-alpine + container_name: authentik-db + restart: unless-stopped + environment: + - POSTGRES_DB=authentik + - POSTGRES_USER=authentik + - POSTGRES_PASSWORD=${PG_PASS:?database password required} + healthcheck: + test: ["CMD-SHELL", "pg_isready -d authentik -U authentik"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 20s + volumes: + - ${DATA_PATH:-/srv/authentik}/database:/var/lib/postgresql/data + + server: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.8.0} + container_name: authentik-server + command: server + restart: unless-stopped + shm_size: 512mb + depends_on: + postgresql: + condition: service_healthy + environment: + - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY:?secret key required} + - AUTHENTIK_POSTGRESQL__HOST=postgresql + - AUTHENTIK_POSTGRESQL__NAME=authentik + - AUTHENTIK_POSTGRESQL__USER=authentik + - AUTHENTIK_POSTGRESQL__PASSWORD=${PG_PASS} + ports: + - "${HTTP_PORT:-9200}:9000" + - "${HTTPS_PORT:-9243}:9443" + volumes: + - ${DATA_PATH:-/srv/authentik}/data:/data + - ${DATA_PATH:-/srv/authentik}/custom-templates:/templates + + worker: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.8.0} + container_name: authentik-worker + command: worker + restart: unless-stopped + shm_size: 512mb + user: root + depends_on: + postgresql: + condition: service_healthy + environment: + - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY} + - AUTHENTIK_POSTGRESQL__HOST=postgresql + - AUTHENTIK_POSTGRESQL__NAME=authentik + - AUTHENTIK_POSTGRESQL__USER=authentik + - AUTHENTIK_POSTGRESQL__PASSWORD=${PG_PASS} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${DATA_PATH:-/srv/authentik}/data:/data + - ${DATA_PATH:-/srv/authentik}/certs:/certs + - ${DATA_PATH:-/srv/authentik}/custom-templates:/templates diff --git a/backend/templates/authentik/template.json b/backend/templates/authentik/template.json new file mode 100644 index 0000000..a814da2 --- /dev/null +++ b/backend/templates/authentik/template.json @@ -0,0 +1,10 @@ +{ + "name": "authentik", + "description": "Identity provider and SSO gateway (OAuth2, SAML, LDAP, forward auth). Finish setup at /if/flow/initial-setup/.", + "tags": [ + "security", + "identity", + "sso" + ], + "gpu": null +} diff --git a/backend/templates/bazarr/.env.example b/backend/templates/bazarr/.env.example new file mode 100644 index 0000000..6a82641 --- /dev/null +++ b/backend/templates/bazarr/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/bazarr +MEDIA_PATH=/srv/media +HTTP_PORT=6767 diff --git a/backend/templates/bazarr/compose.yaml b/backend/templates/bazarr/compose.yaml new file mode 100644 index 0000000..46fd88d --- /dev/null +++ b/backend/templates/bazarr/compose.yaml @@ -0,0 +1,14 @@ +services: + bazarr: + image: lscr.io/linuxserver/bazarr:latest + container_name: bazarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-6767}:6767" + volumes: + - ${DATA_PATH:-/srv/bazarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media diff --git a/backend/templates/bazarr/template.json b/backend/templates/bazarr/template.json new file mode 100644 index 0000000..4094c23 --- /dev/null +++ b/backend/templates/bazarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Bazarr", + "description": "Companion to Sonarr and Radarr that downloads matching subtitles.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/beszel/.env.example b/backend/templates/beszel/.env.example new file mode 100644 index 0000000..54eff02 --- /dev/null +++ b/backend/templates/beszel/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/beszel +HTTP_PORT=8090 +AGENT_PORT=45876 +AGENT_KEY= diff --git a/backend/templates/beszel/compose.yaml b/backend/templates/beszel/compose.yaml new file mode 100644 index 0000000..0ea37f1 --- /dev/null +++ b/backend/templates/beszel/compose.yaml @@ -0,0 +1,22 @@ +services: + beszel: + image: henrygd/beszel:latest + container_name: beszel + restart: unless-stopped + ports: + - "${HTTP_PORT:-8090}:8090" + volumes: + - ${DATA_PATH:-/srv/beszel}/data:/beszel_data + + beszel-agent: + image: henrygd/beszel-agent:latest + container_name: beszel-agent + restart: unless-stopped + network_mode: host + environment: + - LISTEN=${AGENT_PORT:-45876} + # Copy this from the "add system" dialog in the Beszel UI. + - KEY=${AGENT_KEY:-} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ${DATA_PATH:-/srv/beszel}/agent:/var/lib/beszel-agent diff --git a/backend/templates/beszel/template.json b/backend/templates/beszel/template.json new file mode 100644 index 0000000..762baa5 --- /dev/null +++ b/backend/templates/beszel/template.json @@ -0,0 +1,10 @@ +{ + "name": "Beszel", + "description": "Lightweight server monitoring with historical charts, alerts and Docker stats.", + "tags": [ + "monitoring", + "metrics", + "docker" + ], + "gpu": null +} diff --git a/backend/templates/bookstack/.env.example b/backend/templates/bookstack/.env.example new file mode 100644 index 0000000..4ae0beb --- /dev/null +++ b/backend/templates/bookstack/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/bookstack +HTTP_PORT=6875 +APP_URL=http://localhost:6875 +# Required, must be "base64:..." — generate with: echo "base64:$(openssl rand -base64 32)" +APP_KEY= +DB_PASSWORD=change-me +DB_ROOT_PASSWORD=change-me diff --git a/backend/templates/bookstack/compose.yaml b/backend/templates/bookstack/compose.yaml new file mode 100644 index 0000000..b8ec2e5 --- /dev/null +++ b/backend/templates/bookstack/compose.yaml @@ -0,0 +1,38 @@ +services: + bookstack: + image: lscr.io/linuxserver/bookstack:latest + container_name: bookstack + restart: unless-stopped + depends_on: + - bookstack-db + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - APP_URL=${APP_URL:-http://localhost:6875} + # Required. Generate with: openssl rand -base64 32 + - APP_KEY=${APP_KEY:?app key required} + - DB_HOST=bookstack-db + - DB_PORT=3306 + - DB_DATABASE=bookstackapp + - DB_USERNAME=bookstack + - DB_PASSWORD=${DB_PASSWORD:-bookstack} + ports: + - "${HTTP_PORT:-6875}:80" + volumes: + - ${DATA_PATH:-/srv/bookstack}/config:/config + + bookstack-db: + image: lscr.io/linuxserver/mariadb:latest + container_name: bookstack-db + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-bookstack} + - MYSQL_DATABASE=bookstackapp + - MYSQL_USER=bookstack + - MYSQL_PASSWORD=${DB_PASSWORD:-bookstack} + volumes: + - ${DATA_PATH:-/srv/bookstack}/db:/config diff --git a/backend/templates/bookstack/template.json b/backend/templates/bookstack/template.json new file mode 100644 index 0000000..28bf788 --- /dev/null +++ b/backend/templates/bookstack/template.json @@ -0,0 +1,10 @@ +{ + "name": "BookStack", + "description": "Documentation platform organised into shelves, books, chapters and pages. First login: admin@admin.com / password.", + "tags": [ + "documents", + "wiki", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/caddy/.env.example b/backend/templates/caddy/.env.example new file mode 100644 index 0000000..857334f --- /dev/null +++ b/backend/templates/caddy/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/caddy +HTTP_PORT=80 +HTTPS_PORT=443 diff --git a/backend/templates/caddy/Caddyfile b/backend/templates/caddy/Caddyfile new file mode 100644 index 0000000..b0d29a6 --- /dev/null +++ b/backend/templates/caddy/Caddyfile @@ -0,0 +1,11 @@ +# Caddy issues and renews TLS certificates automatically for any real hostname. +# Replace the examples below with your own, then restart the stack. + +app.example.com { + reverse_proxy host.docker.internal:8080 +} + +# Local-only site on plain HTTP (no certificate needed): +# http://nas.lan { +# reverse_proxy 192.168.1.10:5000 +# } diff --git a/backend/templates/caddy/compose.yaml b/backend/templates/caddy/compose.yaml new file mode 100644 index 0000000..16a7088 --- /dev/null +++ b/backend/templates/caddy/compose.yaml @@ -0,0 +1,13 @@ +services: + caddy: + image: caddy:2-alpine + container_name: caddy + restart: unless-stopped + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + - "${HTTPS_PORT:-443}:443/udp" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ${DATA_PATH:-/srv/caddy}/data:/data + - ${DATA_PATH:-/srv/caddy}/config:/config diff --git a/backend/templates/caddy/template.json b/backend/templates/caddy/template.json new file mode 100644 index 0000000..bd39158 --- /dev/null +++ b/backend/templates/caddy/template.json @@ -0,0 +1,9 @@ +{ + "name": "Caddy", + "description": "Reverse proxy with automatic HTTPS, configured through a short Caddyfile.", + "tags": [ + "network", + "proxy" + ], + "gpu": null +} diff --git a/backend/templates/calibre-web/.env.example b/backend/templates/calibre-web/.env.example new file mode 100644 index 0000000..76f671b --- /dev/null +++ b/backend/templates/calibre-web/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/calibre-web +LIBRARY_PATH=/srv/media/books +HTTP_PORT=8083 diff --git a/backend/templates/calibre-web/compose.yaml b/backend/templates/calibre-web/compose.yaml new file mode 100644 index 0000000..de48c33 --- /dev/null +++ b/backend/templates/calibre-web/compose.yaml @@ -0,0 +1,16 @@ +services: + calibre-web: + image: lscr.io/linuxserver/calibre-web:latest + container_name: calibre-web + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + # Adds ebook format conversion (downloads Calibre on first start) + - DOCKER_MODS=linuxserver/mods:universal-calibre + ports: + - "${HTTP_PORT:-8083}:8083" + volumes: + - ${DATA_PATH:-/srv/calibre-web}:/config + - ${LIBRARY_PATH:-/srv/media/books}:/books diff --git a/backend/templates/calibre-web/template.json b/backend/templates/calibre-web/template.json new file mode 100644 index 0000000..0ed99c3 --- /dev/null +++ b/backend/templates/calibre-web/template.json @@ -0,0 +1,9 @@ +{ + "name": "Calibre-Web", + "description": "Web front-end for a Calibre ebook library, with OPDS and send-to-Kindle.", + "tags": [ + "media", + "books" + ], + "gpu": null +} diff --git a/backend/templates/changedetection-io/.env.example b/backend/templates/changedetection-io/.env.example new file mode 100644 index 0000000..78c675a --- /dev/null +++ b/backend/templates/changedetection-io/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/changedetection +HTTP_PORT=5000 +BASE_URL=http://localhost:5000 diff --git a/backend/templates/changedetection-io/compose.yaml b/backend/templates/changedetection-io/compose.yaml new file mode 100644 index 0000000..3ea38d3 --- /dev/null +++ b/backend/templates/changedetection-io/compose.yaml @@ -0,0 +1,25 @@ +services: + changedetection: + image: ghcr.io/dgtlmoon/changedetection.io:latest + container_name: changedetection + restart: unless-stopped + environment: + - BASE_URL=${BASE_URL:-http://localhost:5000} + - PLAYWRIGHT_DRIVER_URL=ws://browser:3000 + ports: + - "${HTTP_PORT:-5000}:5000" + volumes: + - ${DATA_PATH:-/srv/changedetection}:/datastore + depends_on: + - browser + + browser: + image: dgtlmoon/sockpuppetbrowser:latest + container_name: changedetection-browser + restart: unless-stopped + cap_add: + - SYS_ADMIN + environment: + - SCREEN_WIDTH=1920 + - SCREEN_HEIGHT=1024 + - MAX_CONCURRENT_CHROME_PROCESSES=10 diff --git a/backend/templates/changedetection-io/template.json b/backend/templates/changedetection-io/template.json new file mode 100644 index 0000000..af296fe --- /dev/null +++ b/backend/templates/changedetection-io/template.json @@ -0,0 +1,9 @@ +{ + "name": "changedetection.io", + "description": "Watches web pages for changes and notifies you — with a headless browser for JS-heavy sites.", + "tags": [ + "monitoring", + "automation" + ], + "gpu": null +} diff --git a/backend/templates/code-server/.env.example b/backend/templates/code-server/.env.example new file mode 100644 index 0000000..870f282 --- /dev/null +++ b/backend/templates/code-server/.env.example @@ -0,0 +1,9 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/code-server +PROJECTS_PATH=/srv/projects +HTTP_PORT=8443 +# Required — this is the web login password. +PASSWORD= +SUDO_PASSWORD= diff --git a/backend/templates/code-server/compose.yaml b/backend/templates/code-server/compose.yaml new file mode 100644 index 0000000..a6b6dfd --- /dev/null +++ b/backend/templates/code-server/compose.yaml @@ -0,0 +1,17 @@ +services: + code-server: + image: lscr.io/linuxserver/code-server:latest + container_name: code-server + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - PASSWORD=${PASSWORD:?password required} + - SUDO_PASSWORD=${SUDO_PASSWORD:-} + - DEFAULT_WORKSPACE=/config/workspace + ports: + - "${HTTP_PORT:-8443}:8443" + volumes: + - ${DATA_PATH:-/srv/code-server}:/config + - ${PROJECTS_PATH:-/srv/projects}:/config/workspace diff --git a/backend/templates/code-server/template.json b/backend/templates/code-server/template.json new file mode 100644 index 0000000..dcc15fe --- /dev/null +++ b/backend/templates/code-server/template.json @@ -0,0 +1,8 @@ +{ + "name": "code-server", + "description": "VS Code in the browser, running on the server where your files already are.", + "tags": [ + "dev" + ], + "gpu": null +} diff --git a/backend/templates/dashy/.env.example b/backend/templates/dashy/.env.example new file mode 100644 index 0000000..630755f --- /dev/null +++ b/backend/templates/dashy/.env.example @@ -0,0 +1,2 @@ +DATA_PATH=/srv/dashy +HTTP_PORT=4000 diff --git a/backend/templates/dashy/compose.yaml b/backend/templates/dashy/compose.yaml new file mode 100644 index 0000000..ad87352 --- /dev/null +++ b/backend/templates/dashy/compose.yaml @@ -0,0 +1,12 @@ +services: + dashy: + image: lissy93/dashy:latest + container_name: dashy + restart: unless-stopped + environment: + - NODE_ENV=production + ports: + - "${HTTP_PORT:-4000}:8080" + volumes: + # Dashy writes conf.yml back into this directory when you edit in the UI. + - ${DATA_PATH:-/srv/dashy}:/app/user-data diff --git a/backend/templates/dashy/template.json b/backend/templates/dashy/template.json new file mode 100644 index 0000000..8ababb7 --- /dev/null +++ b/backend/templates/dashy/template.json @@ -0,0 +1,8 @@ +{ + "name": "Dashy", + "description": "Highly themeable dashboard with status checks, editable from the browser.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/docker-registry/.env.example b/backend/templates/docker-registry/.env.example new file mode 100644 index 0000000..32ed92d --- /dev/null +++ b/backend/templates/docker-registry/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/registry +HTTP_PORT=5050 +UI_PORT=5080 +REGISTRY_TITLE=Homelab Registry diff --git a/backend/templates/docker-registry/compose.yaml b/backend/templates/docker-registry/compose.yaml new file mode 100644 index 0000000..98e93d3 --- /dev/null +++ b/backend/templates/docker-registry/compose.yaml @@ -0,0 +1,29 @@ +services: + registry: + image: registry:2 + container_name: registry + restart: unless-stopped + environment: + - REGISTRY_STORAGE_DELETE_ENABLED=true + ports: + - "${HTTP_PORT:-5050}:5000" + volumes: + - ${DATA_PATH:-/srv/registry}:/var/lib/registry + # Basic auth. Create it with: + # docker run --rm httpd:alpine htpasswd -Bbn user pass > htpasswd + # then uncomment the mount and the three REGISTRY_AUTH_* vars above. + # - ./htpasswd:/auth/htpasswd:ro + + registry-ui: + image: joxit/docker-registry-ui:latest + container_name: registry-ui + restart: unless-stopped + depends_on: + - registry + environment: + - SINGLE_REGISTRY=true + - REGISTRY_TITLE=${REGISTRY_TITLE:-Homelab Registry} + - NGINX_PROXY_PASS_URL=http://registry:5000 + - DELETE_IMAGES=true + ports: + - "${UI_PORT:-5080}:80" diff --git a/backend/templates/docker-registry/template.json b/backend/templates/docker-registry/template.json new file mode 100644 index 0000000..02e0c71 --- /dev/null +++ b/backend/templates/docker-registry/template.json @@ -0,0 +1,9 @@ +{ + "name": "Docker Registry", + "description": "Private container registry with a browsable web UI. Add auth and TLS before exposing it.", + "tags": [ + "docker", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/dockge/.env.example b/backend/templates/dockge/.env.example new file mode 100644 index 0000000..55a0ad4 --- /dev/null +++ b/backend/templates/dockge/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/dockge +STACKS_DIR=/opt/stacks +HTTP_PORT=5001 diff --git a/backend/templates/dockge/compose.yaml b/backend/templates/dockge/compose.yaml new file mode 100644 index 0000000..979b0b4 --- /dev/null +++ b/backend/templates/dockge/compose.yaml @@ -0,0 +1,14 @@ +services: + dockge: + image: louislam/dockge:1 + container_name: dockge + restart: unless-stopped + environment: + - DOCKGE_STACKS_DIR=${STACKS_DIR:-/opt/stacks} + ports: + - "${HTTP_PORT:-5001}:5001" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${DATA_PATH:-/srv/dockge}:/app/data + # Host path and container path must match, or compose lookups break. + - ${STACKS_DIR:-/opt/stacks}:${STACKS_DIR:-/opt/stacks} diff --git a/backend/templates/dockge/template.json b/backend/templates/dockge/template.json new file mode 100644 index 0000000..8a755b3 --- /dev/null +++ b/backend/templates/dockge/template.json @@ -0,0 +1,9 @@ +{ + "name": "Dockge", + "description": "Compose-stack manager with a live editor and terminal output.", + "tags": [ + "docker", + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/dozzle/.env.example b/backend/templates/dozzle/.env.example new file mode 100644 index 0000000..46bb51b --- /dev/null +++ b/backend/templates/dozzle/.env.example @@ -0,0 +1 @@ +HTTP_PORT=8888 diff --git a/backend/templates/dozzle/compose.yaml b/backend/templates/dozzle/compose.yaml new file mode 100644 index 0000000..b1b716f --- /dev/null +++ b/backend/templates/dozzle/compose.yaml @@ -0,0 +1,12 @@ +services: + dozzle: + image: amir20/dozzle:latest + container_name: dozzle + restart: unless-stopped + environment: + - DOZZLE_LEVEL=info + - DOZZLE_NO_ANALYTICS=true + ports: + - "${HTTP_PORT:-8888}:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/dozzle/template.json b/backend/templates/dozzle/template.json new file mode 100644 index 0000000..e5dd542 --- /dev/null +++ b/backend/templates/dozzle/template.json @@ -0,0 +1,10 @@ +{ + "name": "Dozzle", + "description": "Live container log viewer in the browser — no log shipping, no storage.", + "tags": [ + "monitoring", + "docker", + "logs" + ], + "gpu": null +} diff --git a/backend/templates/duplicati/.env.example b/backend/templates/duplicati/.env.example new file mode 100644 index 0000000..9ef1670 --- /dev/null +++ b/backend/templates/duplicati/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/duplicati +BACKUPS_PATH=/srv/backups +SOURCE_PATH=/srv +HTTP_PORT=8201 +WEB_PASSWORD=change-me +# Required. Generate with: openssl rand -hex 32 +SETTINGS_ENCRYPTION_KEY= diff --git a/backend/templates/duplicati/compose.yaml b/backend/templates/duplicati/compose.yaml new file mode 100644 index 0000000..451d004 --- /dev/null +++ b/backend/templates/duplicati/compose.yaml @@ -0,0 +1,17 @@ +services: + duplicati: + image: lscr.io/linuxserver/duplicati:latest + container_name: duplicati + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - SETTINGS_ENCRYPTION_KEY=${SETTINGS_ENCRYPTION_KEY:?encryption key required} + - DUPLICATI__WEBSERVICE_PASSWORD=${WEB_PASSWORD:-change-me} + ports: + - "${HTTP_PORT:-8201}:8200" + volumes: + - ${DATA_PATH:-/srv/duplicati}:/config + - ${BACKUPS_PATH:-/srv/backups}:/backups + - ${SOURCE_PATH:-/srv}:/source:ro diff --git a/backend/templates/duplicati/template.json b/backend/templates/duplicati/template.json new file mode 100644 index 0000000..cf299cd --- /dev/null +++ b/backend/templates/duplicati/template.json @@ -0,0 +1,9 @@ +{ + "name": "Duplicati", + "description": "Scheduled, encrypted, deduplicated backups to local disks or any cloud target.", + "tags": [ + "backup", + "files" + ], + "gpu": null +} diff --git a/backend/templates/emby/.env.example b/backend/templates/emby/.env.example new file mode 100644 index 0000000..52e600c --- /dev/null +++ b/backend/templates/emby/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +CONFIG_PATH=/srv/emby/config +MEDIA_PATH=/srv/media +HTTP_PORT=8105 diff --git a/backend/templates/emby/compose.yaml b/backend/templates/emby/compose.yaml new file mode 100644 index 0000000..176789a --- /dev/null +++ b/backend/templates/emby/compose.yaml @@ -0,0 +1,17 @@ +services: + emby: + image: lscr.io/linuxserver/emby:latest + container_name: emby + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8105}:8096" + volumes: + - ${CONFIG_PATH:-/srv/emby/config}:/config + - ${MEDIA_PATH:-/srv/media}:/media + # Uncomment for Intel/AMD hardware transcoding (Emby Premiere required): + # devices: + # - /dev/dri:/dev/dri diff --git a/backend/templates/emby/template.json b/backend/templates/emby/template.json new file mode 100644 index 0000000..635f3f4 --- /dev/null +++ b/backend/templates/emby/template.json @@ -0,0 +1,9 @@ +{ + "name": "Emby", + "description": "Media server for movies, TV and music, with live TV and DVR support.", + "tags": [ + "media", + "streaming" + ], + "gpu": "NVIDIA / Intel optional" +} diff --git a/backend/templates/esphome/.env.example b/backend/templates/esphome/.env.example new file mode 100644 index 0000000..3632e96 --- /dev/null +++ b/backend/templates/esphome/.env.example @@ -0,0 +1,6 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/esphome +# Optional basic auth for the dashboard. +USERNAME= +PASSWORD= +# Runs on host networking — the UI is on port 6052 of the host itself. diff --git a/backend/templates/esphome/compose.yaml b/backend/templates/esphome/compose.yaml new file mode 100644 index 0000000..e22b288 --- /dev/null +++ b/backend/templates/esphome/compose.yaml @@ -0,0 +1,15 @@ +services: + esphome: + image: ghcr.io/esphome/esphome:latest + container_name: esphome + restart: unless-stopped + # Host networking is needed for OTA discovery of your devices. + network_mode: host + privileged: true + environment: + - TZ=${TZ:-Europe/Berlin} + - USERNAME=${USERNAME:-} + - PASSWORD=${PASSWORD:-} + volumes: + - ${DATA_PATH:-/srv/esphome}:/config + - /etc/localtime:/etc/localtime:ro diff --git a/backend/templates/esphome/template.json b/backend/templates/esphome/template.json new file mode 100644 index 0000000..efd72c9 --- /dev/null +++ b/backend/templates/esphome/template.json @@ -0,0 +1,10 @@ +{ + "name": "ESPHome", + "description": "Build and flash firmware for ESP32/ESP8266 sensors straight from the browser.", + "tags": [ + "home-automation", + "iot", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/filebrowser/.env.example b/backend/templates/filebrowser/.env.example new file mode 100644 index 0000000..15864b1 --- /dev/null +++ b/backend/templates/filebrowser/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/filebrowser +FILES_PATH=/srv +HTTP_PORT=8088 diff --git a/backend/templates/filebrowser/compose.yaml b/backend/templates/filebrowser/compose.yaml new file mode 100644 index 0000000..9528b35 --- /dev/null +++ b/backend/templates/filebrowser/compose.yaml @@ -0,0 +1,16 @@ +services: + filebrowser: + # Pinned to the v2 line — v3 changes the on-disk layout. + image: filebrowser/filebrowser:v2 + container_name: filebrowser + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - FB_ROOT=/srv + - FB_DATABASE=/database/filebrowser.db + - FB_PORT=80 + ports: + - "${HTTP_PORT:-8088}:80" + volumes: + - ${FILES_PATH:-/srv}:/srv + - ${DATA_PATH:-/srv/filebrowser}/database:/database diff --git a/backend/templates/filebrowser/template.json b/backend/templates/filebrowser/template.json new file mode 100644 index 0000000..e2597e6 --- /dev/null +++ b/backend/templates/filebrowser/template.json @@ -0,0 +1,8 @@ +{ + "name": "File Browser", + "description": "Web file manager for a directory tree. Default login admin/admin — change it immediately.", + "tags": [ + "files" + ], + "gpu": null +} diff --git a/backend/templates/firefly-iii/.env.example b/backend/templates/firefly-iii/.env.example new file mode 100644 index 0000000..676bd87 --- /dev/null +++ b/backend/templates/firefly-iii/.env.example @@ -0,0 +1,7 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/firefly-iii +HTTP_PORT=8104 +APP_URL=http://localhost:8104 +# Required, exactly 32 characters. Generate with: openssl rand -hex 16 +APP_KEY= +DB_PASSWORD=change-me diff --git a/backend/templates/firefly-iii/compose.yaml b/backend/templates/firefly-iii/compose.yaml new file mode 100644 index 0000000..8877fc9 --- /dev/null +++ b/backend/templates/firefly-iii/compose.yaml @@ -0,0 +1,37 @@ +services: + app: + image: fireflyiii/core:latest + container_name: firefly-iii + restart: unless-stopped + depends_on: + - db + environment: + - TZ=${TZ:-Europe/Berlin} + - APP_ENV=production + - APP_URL=${APP_URL:-http://localhost:8095} + # Required, exactly 32 characters. Generate with: openssl rand -hex 16 + - APP_KEY=${APP_KEY:?app key required, exactly 32 characters} + - TRUSTED_PROXIES=** + - DB_CONNECTION=mysql + - DB_HOST=db + - DB_PORT=3306 + - DB_DATABASE=firefly + - DB_USERNAME=firefly + - DB_PASSWORD=${DB_PASSWORD:-firefly} + ports: + - "${HTTP_PORT:-8104}:8080" + volumes: + - ${DATA_PATH:-/srv/firefly-iii}/upload:/var/www/html/storage/upload + + db: + image: mariadb:11 + container_name: firefly-iii-db + restart: unless-stopped + environment: + - MARIADB_AUTO_UPGRADE=1 + - MYSQL_DATABASE=firefly + - MYSQL_USER=firefly + - MYSQL_PASSWORD=${DB_PASSWORD:-firefly} + - MYSQL_RANDOM_ROOT_PASSWORD=yes + volumes: + - ${DATA_PATH:-/srv/firefly-iii}/db:/var/lib/mysql diff --git a/backend/templates/firefly-iii/template.json b/backend/templates/firefly-iii/template.json new file mode 100644 index 0000000..4068ca5 --- /dev/null +++ b/backend/templates/firefly-iii/template.json @@ -0,0 +1,9 @@ +{ + "name": "Firefly III", + "description": "Double-entry personal finance manager with budgets, rules and reports.", + "tags": [ + "finance", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/forgejo/.env.example b/backend/templates/forgejo/.env.example new file mode 100644 index 0000000..bffbaac --- /dev/null +++ b/backend/templates/forgejo/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/forgejo +HTTP_PORT=3301 +SSH_PORT=2223 +ROOT_URL=http://localhost:3000/ +DOMAIN=localhost diff --git a/backend/templates/forgejo/compose.yaml b/backend/templates/forgejo/compose.yaml new file mode 100644 index 0000000..095d176 --- /dev/null +++ b/backend/templates/forgejo/compose.yaml @@ -0,0 +1,17 @@ +services: + forgejo: + image: codeberg.org/forgejo/forgejo:16 + container_name: forgejo + restart: unless-stopped + environment: + - USER_UID=${PUID:-1000} + - USER_GID=${PGID:-1000} + - FORGEJO__server__ROOT_URL=${ROOT_URL:-http://localhost:3000/} + - FORGEJO__server__DOMAIN=${DOMAIN:-localhost} + ports: + - "${HTTP_PORT:-3301}:3000" + - "${SSH_PORT:-2223}:22" + volumes: + - ${DATA_PATH:-/srv/forgejo}:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro diff --git a/backend/templates/forgejo/template.json b/backend/templates/forgejo/template.json new file mode 100644 index 0000000..d381d66 --- /dev/null +++ b/backend/templates/forgejo/template.json @@ -0,0 +1,9 @@ +{ + "name": "Forgejo", + "description": "Community-run Git forge with CI (Forgejo Actions) — the Gitea fork.", + "tags": [ + "git", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/freshrss/.env.example b/backend/templates/freshrss/.env.example new file mode 100644 index 0000000..adffcd0 --- /dev/null +++ b/backend/templates/freshrss/.env.example @@ -0,0 +1,4 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/freshrss +HTTP_PORT=8093 +CRON_MIN=*/20 diff --git a/backend/templates/freshrss/compose.yaml b/backend/templates/freshrss/compose.yaml new file mode 100644 index 0000000..8e81018 --- /dev/null +++ b/backend/templates/freshrss/compose.yaml @@ -0,0 +1,13 @@ +services: + freshrss: + image: freshrss/freshrss:latest + container_name: freshrss + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - CRON_MIN=${CRON_MIN:-*/20} + ports: + - "${HTTP_PORT:-8093}:80" + volumes: + - ${DATA_PATH:-/srv/freshrss}/data:/var/www/FreshRSS/data + - ${DATA_PATH:-/srv/freshrss}/extensions:/var/www/FreshRSS/extensions diff --git a/backend/templates/freshrss/template.json b/backend/templates/freshrss/template.json new file mode 100644 index 0000000..d027205 --- /dev/null +++ b/backend/templates/freshrss/template.json @@ -0,0 +1,9 @@ +{ + "name": "FreshRSS", + "description": "Self-hosted RSS aggregator with an API for mobile readers, SQLite by default.", + "tags": [ + "rss", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/frigate/.env.example b/backend/templates/frigate/.env.example new file mode 100644 index 0000000..53e38a5 --- /dev/null +++ b/backend/templates/frigate/.env.example @@ -0,0 +1,8 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/frigate +MEDIA_PATH=/srv/frigate/media +HTTP_PORT=8971 +RTSP_PORT=8554 +WEBRTC_PORT=8555 +SHM_SIZE=256mb +RTSP_PASSWORD=change-me diff --git a/backend/templates/frigate/compose.yaml b/backend/templates/frigate/compose.yaml new file mode 100644 index 0000000..6d88267 --- /dev/null +++ b/backend/templates/frigate/compose.yaml @@ -0,0 +1,32 @@ +services: + frigate: + image: ghcr.io/blakeblackshear/frigate:stable + container_name: frigate + restart: unless-stopped + privileged: true + shm_size: ${SHM_SIZE:-256mb} + environment: + - TZ=${TZ:-Europe/Berlin} + - FRIGATE_RTSP_PASSWORD=${RTSP_PASSWORD:-change-me} + ports: + - "${HTTP_PORT:-8971}:8971" + - "${RTSP_PORT:-8554}:8554" + - "${WEBRTC_PORT:-8555}:8555/tcp" + - "${WEBRTC_PORT:-8555}:8555/udp" + volumes: + # The config lives with the stack so you can edit it here; the directory + # underneath holds Frigate's database and model cache. + - ${DATA_PATH:-/srv/frigate}/config:/config + - ./config.yml:/config/config.yml + - ${MEDIA_PATH:-/srv/frigate/media}:/media/frigate + - /etc/localtime:/etc/localtime:ro + - type: tmpfs + target: /tmp/cache + tmpfs: + size: 1000000000 + # Uncomment what your host actually has: + # devices: + # # Google Coral USB accelerator + # - /dev/bus/usb:/dev/bus/usb + # # Intel iGPU for hardware decoding + # - /dev/dri/renderD128:/dev/dri/renderD128 diff --git a/backend/templates/frigate/config.yml b/backend/templates/frigate/config.yml new file mode 100644 index 0000000..0619e38 --- /dev/null +++ b/backend/templates/frigate/config.yml @@ -0,0 +1,38 @@ +mqtt: + enabled: false + +detectors: + cpu1: + type: cpu + # Swap for a Coral TPU once you have one: + # coral: + # type: edgetpu + # device: usb + +cameras: + front_door: + ffmpeg: + inputs: + - path: rtsp://user:{FRIGATE_RTSP_PASSWORD}@192.168.1.50:554/stream1 + roles: + - detect + - record + detect: + width: 1280 + height: 720 + fps: 5 + objects: + track: + - person + - car + +record: + enabled: true + retain: + days: 7 + mode: motion + +snapshots: + enabled: true + retain: + default: 14 diff --git a/backend/templates/frigate/template.json b/backend/templates/frigate/template.json new file mode 100644 index 0000000..cdff524 --- /dev/null +++ b/backend/templates/frigate/template.json @@ -0,0 +1,10 @@ +{ + "name": "Frigate NVR", + "description": "Network video recorder with real-time object detection. Edit config.yml with your cameras before deploying.", + "tags": [ + "home-automation", + "video", + "monitoring" + ], + "gpu": "Coral TPU / Intel / NVIDIA optional" +} diff --git a/backend/templates/gotify/.env.example b/backend/templates/gotify/.env.example new file mode 100644 index 0000000..19ddcc6 --- /dev/null +++ b/backend/templates/gotify/.env.example @@ -0,0 +1,6 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/gotify +HTTP_PORT=8098 +ADMIN_USER=admin +# Required. +ADMIN_PASSWORD= diff --git a/backend/templates/gotify/compose.yaml b/backend/templates/gotify/compose.yaml new file mode 100644 index 0000000..51189cb --- /dev/null +++ b/backend/templates/gotify/compose.yaml @@ -0,0 +1,13 @@ +services: + gotify: + image: gotify/server:latest + container_name: gotify + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - GOTIFY_DEFAULTUSER_NAME=${ADMIN_USER:-admin} + - GOTIFY_DEFAULTUSER_PASS=${ADMIN_PASSWORD:?admin password required} + ports: + - "${HTTP_PORT:-8098}:80" + volumes: + - ${DATA_PATH:-/srv/gotify}:/app/data diff --git a/backend/templates/gotify/template.json b/backend/templates/gotify/template.json new file mode 100644 index 0000000..ec68ccb --- /dev/null +++ b/backend/templates/gotify/template.json @@ -0,0 +1,9 @@ +{ + "name": "Gotify", + "description": "Simple push notification server with per-application tokens and an Android app.", + "tags": [ + "notifications", + "monitoring" + ], + "gpu": null +} diff --git a/backend/templates/grafana/.env.example b/backend/templates/grafana/.env.example new file mode 100644 index 0000000..8e6b570 --- /dev/null +++ b/backend/templates/grafana/.env.example @@ -0,0 +1,8 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/grafana +HTTP_PORT=3030 +ROOT_URL=http://localhost:3000 +ADMIN_USER=admin +ADMIN_PASSWORD=change-me +INSTALL_PLUGINS= diff --git a/backend/templates/grafana/compose.yaml b/backend/templates/grafana/compose.yaml new file mode 100644 index 0000000..f782cfd --- /dev/null +++ b/backend/templates/grafana/compose.yaml @@ -0,0 +1,15 @@ +services: + grafana: + image: grafana/grafana:latest + container_name: grafana + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - GF_SECURITY_ADMIN_USER=${ADMIN_USER:-admin} + - GF_SECURITY_ADMIN_PASSWORD=${ADMIN_PASSWORD:-change-me} + - GF_SERVER_ROOT_URL=${ROOT_URL:-http://localhost:3000} + - GF_INSTALL_PLUGINS=${INSTALL_PLUGINS:-} + ports: + - "${HTTP_PORT:-3030}:3000" + volumes: + - ${DATA_PATH:-/srv/grafana}:/var/lib/grafana diff --git a/backend/templates/grafana/template.json b/backend/templates/grafana/template.json new file mode 100644 index 0000000..c330a4a --- /dev/null +++ b/backend/templates/grafana/template.json @@ -0,0 +1,10 @@ +{ + "name": "Grafana", + "description": "Dashboards and alerting on top of Prometheus, InfluxDB, Loki and friends.", + "tags": [ + "monitoring", + "metrics", + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/grocy/.env.example b/backend/templates/grocy/.env.example new file mode 100644 index 0000000..12f248d --- /dev/null +++ b/backend/templates/grocy/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/grocy +HTTP_PORT=9283 diff --git a/backend/templates/grocy/compose.yaml b/backend/templates/grocy/compose.yaml new file mode 100644 index 0000000..5d86f97 --- /dev/null +++ b/backend/templates/grocy/compose.yaml @@ -0,0 +1,13 @@ +services: + grocy: + image: lscr.io/linuxserver/grocy:latest + container_name: grocy + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-9283}:80" + volumes: + - ${DATA_PATH:-/srv/grocy}:/config diff --git a/backend/templates/grocy/template.json b/backend/templates/grocy/template.json new file mode 100644 index 0000000..00a1e7e --- /dev/null +++ b/backend/templates/grocy/template.json @@ -0,0 +1,9 @@ +{ + "name": "Grocy", + "description": "Groceries, stock, chores and household ERP with barcode scanning. First login: admin / admin.", + "tags": [ + "productivity", + "recipes" + ], + "gpu": null +} diff --git a/backend/templates/heimdall/.env.example b/backend/templates/heimdall/.env.example new file mode 100644 index 0000000..331718f --- /dev/null +++ b/backend/templates/heimdall/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/heimdall +HTTP_PORT=8102 +HTTPS_PORT=8103 diff --git a/backend/templates/heimdall/compose.yaml b/backend/templates/heimdall/compose.yaml new file mode 100644 index 0000000..24cfbcc --- /dev/null +++ b/backend/templates/heimdall/compose.yaml @@ -0,0 +1,14 @@ +services: + heimdall: + image: lscr.io/linuxserver/heimdall:latest + container_name: heimdall + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8102}:80" + - "${HTTPS_PORT:-8103}:443" + volumes: + - ${DATA_PATH:-/srv/heimdall}:/config diff --git a/backend/templates/heimdall/template.json b/backend/templates/heimdall/template.json new file mode 100644 index 0000000..77635ed --- /dev/null +++ b/backend/templates/heimdall/template.json @@ -0,0 +1,8 @@ +{ + "name": "Heimdall", + "description": "Simple application dashboard with enhanced tiles for common services.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/homarr/.env.example b/backend/templates/homarr/.env.example new file mode 100644 index 0000000..4155c08 --- /dev/null +++ b/backend/templates/homarr/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/homarr +HTTP_PORT=7575 +# Required. Generate with: openssl rand -hex 32 +SECRET_ENCRYPTION_KEY= diff --git a/backend/templates/homarr/compose.yaml b/backend/templates/homarr/compose.yaml new file mode 100644 index 0000000..28b3e8c --- /dev/null +++ b/backend/templates/homarr/compose.yaml @@ -0,0 +1,14 @@ +services: + homarr: + image: ghcr.io/homarr-labs/homarr:latest + container_name: homarr + restart: unless-stopped + environment: + # Required. Generate with: openssl rand -hex 32 + - SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY:?encryption key required} + ports: + - "${HTTP_PORT:-7575}:7575" + volumes: + - ${DATA_PATH:-/srv/homarr}:/appdata + # Optional: lets Homarr show container status + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/homarr/template.json b/backend/templates/homarr/template.json new file mode 100644 index 0000000..2865324 --- /dev/null +++ b/backend/templates/homarr/template.json @@ -0,0 +1,8 @@ +{ + "name": "Homarr", + "description": "Drag-and-drop dashboard with integrations for the *arr apps, Docker and more.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/home-assistant/.env.example b/backend/templates/home-assistant/.env.example new file mode 100644 index 0000000..a621ab9 --- /dev/null +++ b/backend/templates/home-assistant/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/homeassistant +# Runs on host networking — the UI is on port 8123 of the host itself. diff --git a/backend/templates/home-assistant/compose.yaml b/backend/templates/home-assistant/compose.yaml new file mode 100644 index 0000000..95d10f9 --- /dev/null +++ b/backend/templates/home-assistant/compose.yaml @@ -0,0 +1,17 @@ +services: + homeassistant: + image: ghcr.io/home-assistant/home-assistant:stable + container_name: homeassistant + restart: unless-stopped + # Host networking lets HA discover devices via mDNS, SSDP and DHCP. + # Swap it for a ports mapping if you only use cloud/MQTT integrations. + network_mode: host + privileged: true + environment: + - TZ=${TZ:-Europe/Berlin} + volumes: + - ${DATA_PATH:-/srv/homeassistant}:/config + - /run/dbus:/run/dbus:ro + # Uncomment to pass through a Zigbee/Z-Wave USB stick: + # devices: + # - /dev/ttyUSB0:/dev/ttyUSB0 diff --git a/backend/templates/home-assistant/template.json b/backend/templates/home-assistant/template.json new file mode 100644 index 0000000..cec95d0 --- /dev/null +++ b/backend/templates/home-assistant/template.json @@ -0,0 +1,9 @@ +{ + "name": "Home Assistant", + "description": "Home automation hub with thousands of integrations. Runs on host networking for device discovery.", + "tags": [ + "home-automation", + "iot" + ], + "gpu": null +} diff --git a/backend/templates/homepage/.env.example b/backend/templates/homepage/.env.example new file mode 100644 index 0000000..afb3da6 --- /dev/null +++ b/backend/templates/homepage/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/homepage +HTTP_PORT=3005 +ALLOWED_HOSTS=* diff --git a/backend/templates/homepage/compose.yaml b/backend/templates/homepage/compose.yaml new file mode 100644 index 0000000..e9fb7e5 --- /dev/null +++ b/backend/templates/homepage/compose.yaml @@ -0,0 +1,15 @@ +services: + homepage: + image: ghcr.io/gethomepage/homepage:latest + container_name: homepage + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + # Comma-separated hostnames the UI may be reached under, or "*" to allow all. + - HOMEPAGE_ALLOWED_HOSTS=${ALLOWED_HOSTS:-*} + ports: + - "${HTTP_PORT:-3005}:3000" + volumes: + - ${DATA_PATH:-/srv/homepage}:/app/config + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/homepage/template.json b/backend/templates/homepage/template.json new file mode 100644 index 0000000..7023a9c --- /dev/null +++ b/backend/templates/homepage/template.json @@ -0,0 +1,8 @@ +{ + "name": "Homepage", + "description": "YAML-configured start page with live widgets for dozens of self-hosted services.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/immich/.env.example b/backend/templates/immich/.env.example new file mode 100644 index 0000000..5e49a8f --- /dev/null +++ b/backend/templates/immich/.env.example @@ -0,0 +1,11 @@ +TZ=Europe/Berlin +IMMICH_VERSION=release +DATA_PATH=/srv/immich +# Where uploaded photos and videos live. Network shares are fine here. +UPLOAD_LOCATION=/srv/immich/library +# The database must sit on local storage — never on NFS/SMB. +DB_DATA_LOCATION=/srv/immich/postgres +DB_USERNAME=postgres +DB_PASSWORD=change-me +DB_DATABASE_NAME=immich +HTTP_PORT=2283 diff --git a/backend/templates/immich/compose.yaml b/backend/templates/immich/compose.yaml new file mode 100644 index 0000000..8f7c530 --- /dev/null +++ b/backend/templates/immich/compose.yaml @@ -0,0 +1,51 @@ +services: + immich-server: + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} + container_name: immich-server + restart: unless-stopped + depends_on: + - redis + - database + environment: + - TZ=${TZ:-Europe/Berlin} + - DB_HOSTNAME=database + - DB_USERNAME=${DB_USERNAME:-postgres} + - DB_PASSWORD=${DB_PASSWORD:-postgres} + - DB_DATABASE_NAME=${DB_DATABASE_NAME:-immich} + - REDIS_HOSTNAME=redis + ports: + - "${HTTP_PORT:-2283}:2283" + volumes: + - ${UPLOAD_LOCATION:-/srv/immich/library}:/data + - /etc/localtime:/etc/localtime:ro + # Uncomment for Intel/AMD hardware transcoding: + # devices: + # - /dev/dri:/dev/dri + + immich-machine-learning: + # Add -cuda / -openvino / -rocm to the tag for accelerated inference. + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} + container_name: immich-machine-learning + restart: unless-stopped + volumes: + - ${DATA_PATH:-/srv/immich}/model-cache:/cache + + redis: + image: valkey/valkey:8-alpine + container_name: immich-redis + restart: unless-stopped + healthcheck: + test: redis-cli ping | grep -q PONG || exit 1 + + database: + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 + container_name: immich-postgres + restart: unless-stopped + shm_size: 128mb + environment: + - POSTGRES_USER=${DB_USERNAME:-postgres} + - POSTGRES_PASSWORD=${DB_PASSWORD:-postgres} + - POSTGRES_DB=${DB_DATABASE_NAME:-immich} + - POSTGRES_INITDB_ARGS=--data-checksums + volumes: + - ${DB_DATA_LOCATION:-/srv/immich/postgres}:/var/lib/postgresql/data diff --git a/backend/templates/immich/template.json b/backend/templates/immich/template.json new file mode 100644 index 0000000..451ead7 --- /dev/null +++ b/backend/templates/immich/template.json @@ -0,0 +1,10 @@ +{ + "name": "Immich", + "description": "Self-hosted photo and video backup with mobile apps and AI search.", + "tags": [ + "media", + "photos", + "backup" + ], + "gpu": "NVIDIA / Intel optional" +} diff --git a/backend/templates/it-tools/.env.example b/backend/templates/it-tools/.env.example new file mode 100644 index 0000000..fbe97ce --- /dev/null +++ b/backend/templates/it-tools/.env.example @@ -0,0 +1 @@ +HTTP_PORT=8100 diff --git a/backend/templates/it-tools/compose.yaml b/backend/templates/it-tools/compose.yaml new file mode 100644 index 0000000..2144b27 --- /dev/null +++ b/backend/templates/it-tools/compose.yaml @@ -0,0 +1,7 @@ +services: + it-tools: + image: corentinth/it-tools:latest + container_name: it-tools + restart: unless-stopped + ports: + - "${HTTP_PORT:-8100}:80" diff --git a/backend/templates/it-tools/template.json b/backend/templates/it-tools/template.json new file mode 100644 index 0000000..32da26d --- /dev/null +++ b/backend/templates/it-tools/template.json @@ -0,0 +1,9 @@ +{ + "name": "IT Tools", + "description": "Offline collection of developer utilities — hashes, JWTs, cron parsing, converters and more.", + "tags": [ + "utilities", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/jellyseerr/.env.example b/backend/templates/jellyseerr/.env.example new file mode 100644 index 0000000..5b099d4 --- /dev/null +++ b/backend/templates/jellyseerr/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/jellyseerr +HTTP_PORT=5055 diff --git a/backend/templates/jellyseerr/compose.yaml b/backend/templates/jellyseerr/compose.yaml new file mode 100644 index 0000000..51b681a --- /dev/null +++ b/backend/templates/jellyseerr/compose.yaml @@ -0,0 +1,12 @@ +services: + jellyseerr: + image: fallenbagel/jellyseerr:latest + container_name: jellyseerr + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - LOG_LEVEL=info + ports: + - "${HTTP_PORT:-5055}:5055" + volumes: + - ${DATA_PATH:-/srv/jellyseerr}:/app/config diff --git a/backend/templates/jellyseerr/template.json b/backend/templates/jellyseerr/template.json new file mode 100644 index 0000000..100d196 --- /dev/null +++ b/backend/templates/jellyseerr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Jellyseerr", + "description": "Request portal for Jellyfin, Emby and Plex that feeds Sonarr and Radarr.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/kavita/.env.example b/backend/templates/kavita/.env.example new file mode 100644 index 0000000..337ee76 --- /dev/null +++ b/backend/templates/kavita/.env.example @@ -0,0 +1,4 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/kavita +LIBRARY_PATH=/srv/media/books +HTTP_PORT=5010 diff --git a/backend/templates/kavita/compose.yaml b/backend/templates/kavita/compose.yaml new file mode 100644 index 0000000..c53ba62 --- /dev/null +++ b/backend/templates/kavita/compose.yaml @@ -0,0 +1,12 @@ +services: + kavita: + image: jvmilazz0/kavita:latest + container_name: kavita + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-5010}:5000" + volumes: + - ${DATA_PATH:-/srv/kavita}:/kavita/config + - ${LIBRARY_PATH:-/srv/media/books}:/books diff --git a/backend/templates/kavita/template.json b/backend/templates/kavita/template.json new file mode 100644 index 0000000..fa1a63b --- /dev/null +++ b/backend/templates/kavita/template.json @@ -0,0 +1,9 @@ +{ + "name": "Kavita", + "description": "Fast reading server for ebooks, manga and comics.", + "tags": [ + "media", + "books" + ], + "gpu": null +} diff --git a/backend/templates/komga/.env.example b/backend/templates/komga/.env.example new file mode 100644 index 0000000..ea2c2b4 --- /dev/null +++ b/backend/templates/komga/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/komga +COMICS_PATH=/srv/media/comics +HTTP_PORT=25600 diff --git a/backend/templates/komga/compose.yaml b/backend/templates/komga/compose.yaml new file mode 100644 index 0000000..2600598 --- /dev/null +++ b/backend/templates/komga/compose.yaml @@ -0,0 +1,13 @@ +services: + komga: + image: gotson/komga:latest + container_name: komga + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-25600}:25600" + volumes: + - ${DATA_PATH:-/srv/komga}:/config + - ${COMICS_PATH:-/srv/media/comics}:/data diff --git a/backend/templates/komga/template.json b/backend/templates/komga/template.json new file mode 100644 index 0000000..2925989 --- /dev/null +++ b/backend/templates/komga/template.json @@ -0,0 +1,9 @@ +{ + "name": "Komga", + "description": "Comic, manga and BD server with a web reader and OPDS feed.", + "tags": [ + "media", + "books" + ], + "gpu": null +} diff --git a/backend/templates/lidarr/.env.example b/backend/templates/lidarr/.env.example new file mode 100644 index 0000000..6b46ef2 --- /dev/null +++ b/backend/templates/lidarr/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/lidarr +MEDIA_PATH=/srv/media +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=8686 diff --git a/backend/templates/lidarr/compose.yaml b/backend/templates/lidarr/compose.yaml new file mode 100644 index 0000000..8f4551a --- /dev/null +++ b/backend/templates/lidarr/compose.yaml @@ -0,0 +1,15 @@ +services: + lidarr: + image: lscr.io/linuxserver/lidarr:latest + container_name: lidarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8686}:8686" + volumes: + - ${DATA_PATH:-/srv/lidarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/lidarr/template.json b/backend/templates/lidarr/template.json new file mode 100644 index 0000000..7813a96 --- /dev/null +++ b/backend/templates/lidarr/template.json @@ -0,0 +1,11 @@ +{ + "name": "Lidarr", + "description": "Automates finding, downloading and organising music.", + "tags": [ + "media", + "music", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/linkwarden/.env.example b/backend/templates/linkwarden/.env.example new file mode 100644 index 0000000..8e13a49 --- /dev/null +++ b/backend/templates/linkwarden/.env.example @@ -0,0 +1,7 @@ +DATA_PATH=/srv/linkwarden +HTTP_PORT=3010 +NEXTAUTH_URL=http://localhost:3010 +POSTGRES_PASSWORD=change-me +# Both required. Generate each with: openssl rand -hex 32 +NEXTAUTH_SECRET= +MEILI_MASTER_KEY= diff --git a/backend/templates/linkwarden/compose.yaml b/backend/templates/linkwarden/compose.yaml new file mode 100644 index 0000000..6adecf2 --- /dev/null +++ b/backend/templates/linkwarden/compose.yaml @@ -0,0 +1,38 @@ +services: + linkwarden: + image: ghcr.io/linkwarden/linkwarden:latest + container_name: linkwarden + restart: unless-stopped + depends_on: + - postgres + - meilisearch + environment: + - DATABASE_URL=postgresql://linkwarden:${POSTGRES_PASSWORD:-linkwarden}@postgres:5432/linkwarden + - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3010}/api/v1/auth + - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:?auth secret required} + - MEILI_HOST=http://meilisearch:7700 + - MEILI_MASTER_KEY=${MEILI_MASTER_KEY:?meilisearch key required} + ports: + - "${HTTP_PORT:-3010}:3000" + volumes: + - ${DATA_PATH:-/srv/linkwarden}/data:/data/data + + postgres: + image: postgres:16-alpine + container_name: linkwarden-db + restart: unless-stopped + environment: + - POSTGRES_DB=linkwarden + - POSTGRES_USER=linkwarden + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-linkwarden} + volumes: + - ${DATA_PATH:-/srv/linkwarden}/pgdata:/var/lib/postgresql/data + + meilisearch: + image: getmeili/meilisearch:v1.13 + container_name: linkwarden-meili + restart: unless-stopped + environment: + - MEILI_MASTER_KEY=${MEILI_MASTER_KEY} + volumes: + - ${DATA_PATH:-/srv/linkwarden}/meili:/meili_data diff --git a/backend/templates/linkwarden/template.json b/backend/templates/linkwarden/template.json new file mode 100644 index 0000000..ac805ef --- /dev/null +++ b/backend/templates/linkwarden/template.json @@ -0,0 +1,9 @@ +{ + "name": "Linkwarden", + "description": "Bookmark manager that archives pages as screenshots and PDFs, with full-text search.", + "tags": [ + "productivity", + "bookmarks" + ], + "gpu": null +} diff --git a/backend/templates/mariadb/.env.example b/backend/templates/mariadb/.env.example new file mode 100644 index 0000000..9a31cae --- /dev/null +++ b/backend/templates/mariadb/.env.example @@ -0,0 +1,9 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/mariadb +DB_PORT=3306 +ADMINER_PORT=8091 +MYSQL_DATABASE=app +MYSQL_USER=app +# Both required. +MYSQL_PASSWORD= +MYSQL_ROOT_PASSWORD= diff --git a/backend/templates/mariadb/compose.yaml b/backend/templates/mariadb/compose.yaml new file mode 100644 index 0000000..09e890b --- /dev/null +++ b/backend/templates/mariadb/compose.yaml @@ -0,0 +1,28 @@ +services: + mariadb: + image: mariadb:11 + container_name: mariadb + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED + environment: + - MARIADB_AUTO_UPGRADE=1 + - MARIADB_DATABASE=${MYSQL_DATABASE:-app} + - MARIADB_USER=${MYSQL_USER:-app} + - MARIADB_PASSWORD=${MYSQL_PASSWORD:?database password required} + - MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:?root password required} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${DB_PORT:-3306}:3306" + volumes: + - ${DATA_PATH:-/srv/mariadb}:/var/lib/mysql + + adminer: + image: adminer:latest + container_name: mariadb-adminer + restart: unless-stopped + depends_on: + - mariadb + environment: + - ADMINER_DEFAULT_SERVER=mariadb + ports: + - "${ADMINER_PORT:-8091}:8080" diff --git a/backend/templates/mariadb/template.json b/backend/templates/mariadb/template.json new file mode 100644 index 0000000..38fcef9 --- /dev/null +++ b/backend/templates/mariadb/template.json @@ -0,0 +1,9 @@ +{ + "name": "MariaDB + Adminer", + "description": "Standalone MySQL-compatible database for your own apps, with Adminer for browsing it.", + "tags": [ + "database", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/mealie/.env.example b/backend/templates/mealie/.env.example new file mode 100644 index 0000000..43054d6 --- /dev/null +++ b/backend/templates/mealie/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/mealie +HTTP_PORT=9925 +BASE_URL=http://localhost:9925 diff --git a/backend/templates/mealie/compose.yaml b/backend/templates/mealie/compose.yaml new file mode 100644 index 0000000..ce908d3 --- /dev/null +++ b/backend/templates/mealie/compose.yaml @@ -0,0 +1,19 @@ +services: + mealie: + image: ghcr.io/mealie-recipes/mealie:latest + container_name: mealie + restart: unless-stopped + deploy: + resources: + limits: + memory: 1G + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - BASE_URL=${BASE_URL:-http://localhost:9925} + - ALLOW_SIGNUP=false + ports: + - "${HTTP_PORT:-9925}:9000" + volumes: + - ${DATA_PATH:-/srv/mealie}:/app/data diff --git a/backend/templates/mealie/template.json b/backend/templates/mealie/template.json new file mode 100644 index 0000000..fd4ea71 --- /dev/null +++ b/backend/templates/mealie/template.json @@ -0,0 +1,9 @@ +{ + "name": "Mealie", + "description": "Recipe manager and meal planner that imports recipes from any URL. First login: changeme@example.com / MyPassword.", + "tags": [ + "recipes", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/memos/.env.example b/backend/templates/memos/.env.example new file mode 100644 index 0000000..80cb2db --- /dev/null +++ b/backend/templates/memos/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/memos +HTTP_PORT=5230 diff --git a/backend/templates/memos/compose.yaml b/backend/templates/memos/compose.yaml new file mode 100644 index 0000000..aaf8187 --- /dev/null +++ b/backend/templates/memos/compose.yaml @@ -0,0 +1,11 @@ +services: + memos: + image: neosmemo/memos:stable + container_name: memos + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-5230}:5230" + volumes: + - ${DATA_PATH:-/srv/memos}:/var/opt/memos diff --git a/backend/templates/memos/template.json b/backend/templates/memos/template.json new file mode 100644 index 0000000..e9c63bf --- /dev/null +++ b/backend/templates/memos/template.json @@ -0,0 +1,9 @@ +{ + "name": "Memos", + "description": "Lightweight, Markdown-first note taking — a self-hosted take on microblogging your thoughts.", + "tags": [ + "notes", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/minecraft-server/.env.example b/backend/templates/minecraft-server/.env.example new file mode 100644 index 0000000..1319733 --- /dev/null +++ b/backend/templates/minecraft-server/.env.example @@ -0,0 +1,11 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/minecraft +GAME_PORT=25565 +# VANILLA, PAPER, FABRIC, FORGE, ... +TYPE=PAPER +VERSION=LATEST +MEMORY=4G +DIFFICULTY=normal +MOTD=Homelab Minecraft +# Comma-separated player names granted operator rights. +OPS= diff --git a/backend/templates/minecraft-server/compose.yaml b/backend/templates/minecraft-server/compose.yaml new file mode 100644 index 0000000..50217d4 --- /dev/null +++ b/backend/templates/minecraft-server/compose.yaml @@ -0,0 +1,20 @@ +services: + minecraft: + image: itzg/minecraft-server:latest + container_name: minecraft + restart: unless-stopped + stdin_open: true + tty: true + environment: + - EULA=TRUE + - TZ=${TZ:-Europe/Berlin} + - TYPE=${TYPE:-PAPER} + - VERSION=${VERSION:-LATEST} + - MEMORY=${MEMORY:-4G} + - DIFFICULTY=${DIFFICULTY:-normal} + - MOTD=${MOTD:-Homelab Minecraft} + - OPS=${OPS:-} + ports: + - "${GAME_PORT:-25565}:25565" + volumes: + - ${DATA_PATH:-/srv/minecraft}:/data diff --git a/backend/templates/minecraft-server/template.json b/backend/templates/minecraft-server/template.json new file mode 100644 index 0000000..45cfb4d --- /dev/null +++ b/backend/templates/minecraft-server/template.json @@ -0,0 +1,8 @@ +{ + "name": "Minecraft Server", + "description": "Java Edition server (Paper, Fabric, Forge or vanilla) with automatic version downloads.", + "tags": [ + "games" + ], + "gpu": null +} diff --git a/backend/templates/miniflux/.env.example b/backend/templates/miniflux/.env.example new file mode 100644 index 0000000..d96a52a --- /dev/null +++ b/backend/templates/miniflux/.env.example @@ -0,0 +1,6 @@ +DATA_PATH=/srv/miniflux +HTTP_PORT=8094 +DB_PASSWORD=change-me +ADMIN_USER=admin +# Required, at least 6 characters. +ADMIN_PASSWORD= diff --git a/backend/templates/miniflux/compose.yaml b/backend/templates/miniflux/compose.yaml new file mode 100644 index 0000000..307b938 --- /dev/null +++ b/backend/templates/miniflux/compose.yaml @@ -0,0 +1,32 @@ +services: + miniflux: + image: miniflux/miniflux:latest + container_name: miniflux + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + - DATABASE_URL=postgres://miniflux:${DB_PASSWORD:-miniflux}@db/miniflux?sslmode=disable + - RUN_MIGRATIONS=1 + - CREATE_ADMIN=1 + - ADMIN_USERNAME=${ADMIN_USER:-admin} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:?admin password required, min 6 chars} + ports: + - "${HTTP_PORT:-8094}:8080" + + db: + image: postgres:16-alpine + container_name: miniflux-db + restart: unless-stopped + environment: + - POSTGRES_DB=miniflux + - POSTGRES_USER=miniflux + - POSTGRES_PASSWORD=${DB_PASSWORD:-miniflux} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U miniflux"] + interval: 10s + timeout: 5s + retries: 10 + volumes: + - ${DATA_PATH:-/srv/miniflux}/db:/var/lib/postgresql/data diff --git a/backend/templates/miniflux/template.json b/backend/templates/miniflux/template.json new file mode 100644 index 0000000..720bb99 --- /dev/null +++ b/backend/templates/miniflux/template.json @@ -0,0 +1,9 @@ +{ + "name": "Miniflux", + "description": "Minimalist, very fast RSS reader with a Google Reader compatible API.", + "tags": [ + "rss", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/minio/.env.example b/backend/templates/minio/.env.example new file mode 100644 index 0000000..3686b2f --- /dev/null +++ b/backend/templates/minio/.env.example @@ -0,0 +1,7 @@ +DATA_PATH=/srv/minio +API_PORT=9010 +CONSOLE_PORT=9011 +CONSOLE_URL=http://localhost:9011 +ROOT_USER=minioadmin +# Required, at least 8 characters. +ROOT_PASSWORD= diff --git a/backend/templates/minio/compose.yaml b/backend/templates/minio/compose.yaml new file mode 100644 index 0000000..0d4abae --- /dev/null +++ b/backend/templates/minio/compose.yaml @@ -0,0 +1,15 @@ +services: + minio: + image: minio/minio:latest + container_name: minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + - MINIO_ROOT_USER=${ROOT_USER:-minioadmin} + - MINIO_ROOT_PASSWORD=${ROOT_PASSWORD:?root password required} + - MINIO_BROWSER_REDIRECT_URL=${CONSOLE_URL:-http://localhost:9011} + ports: + - "${API_PORT:-9010}:9000" + - "${CONSOLE_PORT:-9011}:9001" + volumes: + - ${DATA_PATH:-/srv/minio}:/data diff --git a/backend/templates/minio/template.json b/backend/templates/minio/template.json new file mode 100644 index 0000000..8a1caf3 --- /dev/null +++ b/backend/templates/minio/template.json @@ -0,0 +1,10 @@ +{ + "name": "MinIO", + "description": "S3-compatible object storage — a handy backup target for Restic, Duplicati and friends.", + "tags": [ + "storage", + "backup", + "files" + ], + "gpu": null +} diff --git a/backend/templates/mosquitto/.env.example b/backend/templates/mosquitto/.env.example new file mode 100644 index 0000000..712db9f --- /dev/null +++ b/backend/templates/mosquitto/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/mosquitto +MQTT_PORT=1883 +WS_PORT=9001 diff --git a/backend/templates/mosquitto/compose.yaml b/backend/templates/mosquitto/compose.yaml new file mode 100644 index 0000000..c2b97d7 --- /dev/null +++ b/backend/templates/mosquitto/compose.yaml @@ -0,0 +1,12 @@ +services: + mosquitto: + image: eclipse-mosquitto:2 + container_name: mosquitto + restart: unless-stopped + ports: + - "${MQTT_PORT:-1883}:1883" + - "${WS_PORT:-9001}:9001" + volumes: + - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro + - ${DATA_PATH:-/srv/mosquitto}/data:/mosquitto/data + - ${DATA_PATH:-/srv/mosquitto}/log:/mosquitto/log diff --git a/backend/templates/mosquitto/mosquitto.conf b/backend/templates/mosquitto/mosquitto.conf new file mode 100644 index 0000000..ac66092 --- /dev/null +++ b/backend/templates/mosquitto/mosquitto.conf @@ -0,0 +1,13 @@ +persistence true +persistence_location /mosquitto/data/ +log_dest stdout + +listener 1883 +listener 9001 +protocol websockets + +# Anonymous access is fine on a trusted LAN. To require credentials: +# 1. set allow_anonymous false +# 2. add "password_file /mosquitto/config/passwd" +# 3. docker compose exec mosquitto mosquitto_passwd -c /mosquitto/config/passwd +allow_anonymous true diff --git a/backend/templates/mosquitto/template.json b/backend/templates/mosquitto/template.json new file mode 100644 index 0000000..0d11966 --- /dev/null +++ b/backend/templates/mosquitto/template.json @@ -0,0 +1,10 @@ +{ + "name": "Mosquitto", + "description": "MQTT broker — the message bus behind Zigbee2MQTT, ESPHome and Home Assistant.", + "tags": [ + "home-automation", + "iot", + "mqtt" + ], + "gpu": null +} diff --git a/backend/templates/n8n/.env.example b/backend/templates/n8n/.env.example new file mode 100644 index 0000000..6b3561f --- /dev/null +++ b/backend/templates/n8n/.env.example @@ -0,0 +1,8 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/n8n +HTTP_PORT=5678 +N8N_HOST=localhost +N8N_PROTOCOL=http +WEBHOOK_URL=http://localhost:5678/ +# Required. Generate with: openssl rand -hex 32 +N8N_ENCRYPTION_KEY= diff --git a/backend/templates/n8n/compose.yaml b/backend/templates/n8n/compose.yaml new file mode 100644 index 0000000..bd2df6c --- /dev/null +++ b/backend/templates/n8n/compose.yaml @@ -0,0 +1,19 @@ +services: + n8n: + image: n8nio/n8n:latest + container_name: n8n + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - GENERIC_TIMEZONE=${TZ:-Europe/Berlin} + - N8N_HOST=${N8N_HOST:-localhost} + - N8N_PORT=5678 + - N8N_PROTOCOL=${N8N_PROTOCOL:-http} + - WEBHOOK_URL=${WEBHOOK_URL:-http://localhost:5678/} + # Required. Generate with: openssl rand -hex 32 + - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY:?encryption key required} + - N8N_RUNNERS_ENABLED=true + ports: + - "${HTTP_PORT:-5678}:5678" + volumes: + - ${DATA_PATH:-/srv/n8n}:/home/node/.n8n diff --git a/backend/templates/n8n/template.json b/backend/templates/n8n/template.json new file mode 100644 index 0000000..b382854 --- /dev/null +++ b/backend/templates/n8n/template.json @@ -0,0 +1,10 @@ +{ + "name": "n8n", + "description": "Workflow automation with 400+ integrations — the self-hosted answer to Zapier.", + "tags": [ + "automation", + "dev", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/navidrome/.env.example b/backend/templates/navidrome/.env.example new file mode 100644 index 0000000..1aee1dd --- /dev/null +++ b/backend/templates/navidrome/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/navidrome +MUSIC_PATH=/srv/media/music +HTTP_PORT=4533 diff --git a/backend/templates/navidrome/compose.yaml b/backend/templates/navidrome/compose.yaml new file mode 100644 index 0000000..39e8126 --- /dev/null +++ b/backend/templates/navidrome/compose.yaml @@ -0,0 +1,15 @@ +services: + navidrome: + image: deluan/navidrome:latest + container_name: navidrome + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - ND_LOGLEVEL=info + - ND_SCANSCHEDULE=1h + - ND_SESSIONTIMEOUT=24h + ports: + - "${HTTP_PORT:-4533}:4533" + volumes: + - ${DATA_PATH:-/srv/navidrome}:/data + - ${MUSIC_PATH:-/srv/media/music}:/music:ro diff --git a/backend/templates/navidrome/template.json b/backend/templates/navidrome/template.json new file mode 100644 index 0000000..e0b2a7d --- /dev/null +++ b/backend/templates/navidrome/template.json @@ -0,0 +1,10 @@ +{ + "name": "Navidrome", + "description": "Music streaming server compatible with any Subsonic client.", + "tags": [ + "media", + "music", + "streaming" + ], + "gpu": null +} diff --git a/backend/templates/netdata/.env.example b/backend/templates/netdata/.env.example new file mode 100644 index 0000000..165b3a6 --- /dev/null +++ b/backend/templates/netdata/.env.example @@ -0,0 +1,3 @@ +NETDATA_HOSTNAME=homelab +DATA_PATH=/srv/netdata +HTTP_PORT=19999 diff --git a/backend/templates/netdata/compose.yaml b/backend/templates/netdata/compose.yaml new file mode 100644 index 0000000..8c38f90 --- /dev/null +++ b/backend/templates/netdata/compose.yaml @@ -0,0 +1,26 @@ +services: + netdata: + image: netdata/netdata:latest + container_name: netdata + restart: unless-stopped + hostname: ${NETDATA_HOSTNAME:-homelab} + pid: host + cap_add: + - SYS_PTRACE + - SYS_ADMIN + security_opt: + - apparmor:unconfined + ports: + - "${HTTP_PORT:-19999}:19999" + volumes: + - ${DATA_PATH:-/srv/netdata}/config:/etc/netdata + - ${DATA_PATH:-/srv/netdata}/lib:/var/lib/netdata + - ${DATA_PATH:-/srv/netdata}/cache:/var/cache/netdata + - /etc/passwd:/host/etc/passwd:ro + - /etc/group:/host/etc/group:ro + - /etc/localtime:/etc/localtime:ro + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /etc/os-release:/host/etc/os-release:ro + - /var/log:/host/var/log:ro + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/netdata/template.json b/backend/templates/netdata/template.json new file mode 100644 index 0000000..19d735b --- /dev/null +++ b/backend/templates/netdata/template.json @@ -0,0 +1,9 @@ +{ + "name": "Netdata", + "description": "Per-second metrics for the host and every container, with zero configuration.", + "tags": [ + "monitoring", + "metrics" + ], + "gpu": null +} diff --git a/backend/templates/nextcloud/.env.example b/backend/templates/nextcloud/.env.example new file mode 100644 index 0000000..380a012 --- /dev/null +++ b/backend/templates/nextcloud/.env.example @@ -0,0 +1,9 @@ +DATA_PATH=/srv/nextcloud +FILES_PATH=/srv/nextcloud/data +HTTP_PORT=8087 +TRUSTED_DOMAINS=localhost +UPLOAD_LIMIT=16G +ADMIN_USER=admin +ADMIN_PASSWORD=change-me +DB_PASSWORD=change-me +DB_ROOT_PASSWORD=change-me diff --git a/backend/templates/nextcloud/compose.yaml b/backend/templates/nextcloud/compose.yaml new file mode 100644 index 0000000..a250b94 --- /dev/null +++ b/backend/templates/nextcloud/compose.yaml @@ -0,0 +1,42 @@ +services: + nextcloud: + image: nextcloud:latest + container_name: nextcloud + restart: unless-stopped + depends_on: + - db + - redis + environment: + - MYSQL_HOST=db + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=${DB_PASSWORD:-nextcloud} + - REDIS_HOST=redis + - NEXTCLOUD_ADMIN_USER=${ADMIN_USER:-admin} + - NEXTCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD:-change-me} + - NEXTCLOUD_TRUSTED_DOMAINS=${TRUSTED_DOMAINS:-localhost} + - PHP_UPLOAD_LIMIT=${UPLOAD_LIMIT:-16G} + ports: + - "${HTTP_PORT:-8087}:80" + volumes: + - ${DATA_PATH:-/srv/nextcloud}/html:/var/www/html + - ${FILES_PATH:-/srv/nextcloud/data}:/var/www/html/data + + db: + image: mariadb:11 + container_name: nextcloud-db + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW + environment: + - MARIADB_AUTO_UPGRADE=1 + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=${DB_PASSWORD:-nextcloud} + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-nextcloud} + volumes: + - ${DATA_PATH:-/srv/nextcloud}/db:/var/lib/mysql + + redis: + image: redis:7-alpine + container_name: nextcloud-redis + restart: unless-stopped diff --git a/backend/templates/nextcloud/template.json b/backend/templates/nextcloud/template.json new file mode 100644 index 0000000..f8726d2 --- /dev/null +++ b/backend/templates/nextcloud/template.json @@ -0,0 +1,10 @@ +{ + "name": "Nextcloud", + "description": "File sync and share with calendar, contacts and an app store, backed by MariaDB and Redis.", + "tags": [ + "files", + "sync", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/nginx-proxy-manager/.env.example b/backend/templates/nginx-proxy-manager/.env.example new file mode 100644 index 0000000..57cc37c --- /dev/null +++ b/backend/templates/nginx-proxy-manager/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/nginx-proxy-manager +HTTP_PORT=80 +HTTPS_PORT=443 +ADMIN_PORT=81 diff --git a/backend/templates/nginx-proxy-manager/compose.yaml b/backend/templates/nginx-proxy-manager/compose.yaml new file mode 100644 index 0000000..4618f4d --- /dev/null +++ b/backend/templates/nginx-proxy-manager/compose.yaml @@ -0,0 +1,12 @@ +services: + npm: + image: jc21/nginx-proxy-manager:latest + container_name: nginx-proxy-manager + restart: unless-stopped + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + - "${ADMIN_PORT:-81}:81" + volumes: + - ${DATA_PATH:-/srv/nginx-proxy-manager}/data:/data + - ${DATA_PATH:-/srv/nginx-proxy-manager}/letsencrypt:/etc/letsencrypt diff --git a/backend/templates/nginx-proxy-manager/template.json b/backend/templates/nginx-proxy-manager/template.json new file mode 100644 index 0000000..2273659 --- /dev/null +++ b/backend/templates/nginx-proxy-manager/template.json @@ -0,0 +1,9 @@ +{ + "name": "Nginx Proxy Manager", + "description": "Reverse proxy with a click-through UI for hosts and Let's Encrypt certs. First login: admin@example.com / changeme.", + "tags": [ + "network", + "proxy" + ], + "gpu": null +} diff --git a/backend/templates/node-red/.env.example b/backend/templates/node-red/.env.example new file mode 100644 index 0000000..43f70e9 --- /dev/null +++ b/backend/templates/node-red/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/node-red +HTTP_PORT=1880 diff --git a/backend/templates/node-red/compose.yaml b/backend/templates/node-red/compose.yaml new file mode 100644 index 0000000..ebaf5ce --- /dev/null +++ b/backend/templates/node-red/compose.yaml @@ -0,0 +1,12 @@ +services: + node-red: + image: nodered/node-red:latest + container_name: node-red + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-1880}:1880" + volumes: + - ${DATA_PATH:-/srv/node-red}:/data diff --git a/backend/templates/node-red/template.json b/backend/templates/node-red/template.json new file mode 100644 index 0000000..316a6e8 --- /dev/null +++ b/backend/templates/node-red/template.json @@ -0,0 +1,10 @@ +{ + "name": "Node-RED", + "description": "Flow-based automation wiring together MQTT, HTTP, Home Assistant and custom logic.", + "tags": [ + "home-automation", + "automation", + "iot" + ], + "gpu": null +} diff --git a/backend/templates/ntfy/.env.example b/backend/templates/ntfy/.env.example new file mode 100644 index 0000000..e796863 --- /dev/null +++ b/backend/templates/ntfy/.env.example @@ -0,0 +1,6 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/ntfy +HTTP_PORT=8097 +BASE_URL=http://localhost:8097 +DEFAULT_ACCESS=deny-all +BEHIND_PROXY=false diff --git a/backend/templates/ntfy/compose.yaml b/backend/templates/ntfy/compose.yaml new file mode 100644 index 0000000..ae15b5d --- /dev/null +++ b/backend/templates/ntfy/compose.yaml @@ -0,0 +1,21 @@ +services: + ntfy: + image: binwiederhier/ntfy:latest + container_name: ntfy + restart: unless-stopped + command: serve + environment: + - TZ=${TZ:-Europe/Berlin} + - NTFY_BASE_URL=${BASE_URL:-http://localhost:8097} + # Set to true only when a reverse proxy sits in front (fixes client IPs). + - NTFY_BEHIND_PROXY=${BEHIND_PROXY:-false} + - NTFY_CACHE_FILE=/var/cache/ntfy/cache.db + - NTFY_AUTH_FILE=/var/lib/ntfy/user.db + # "read-write" is an open server; use "deny-all" plus `ntfy user add` for a private one. + - NTFY_AUTH_DEFAULT_ACCESS=${DEFAULT_ACCESS:-deny-all} + - NTFY_ENABLE_LOGIN=true + ports: + - "${HTTP_PORT:-8097}:80" + volumes: + - ${DATA_PATH:-/srv/ntfy}/cache:/var/cache/ntfy + - ${DATA_PATH:-/srv/ntfy}/lib:/var/lib/ntfy diff --git a/backend/templates/ntfy/template.json b/backend/templates/ntfy/template.json new file mode 100644 index 0000000..c89813f --- /dev/null +++ b/backend/templates/ntfy/template.json @@ -0,0 +1,9 @@ +{ + "name": "ntfy", + "description": "Push notifications to your phone from any script with a plain HTTP POST. Create users with: docker compose exec ntfy ntfy user add --role=admin ", + "tags": [ + "notifications", + "monitoring" + ], + "gpu": null +} diff --git a/backend/templates/ollama-open-webui/.env.example b/backend/templates/ollama-open-webui/.env.example new file mode 100644 index 0000000..5851360 --- /dev/null +++ b/backend/templates/ollama-open-webui/.env.example @@ -0,0 +1,5 @@ +DATA_PATH=/srv/ollama +HTTP_PORT=3020 +OLLAMA_PORT=11434 +# Required. Generate with: openssl rand -hex 32 +WEBUI_SECRET_KEY= diff --git a/backend/templates/ollama-open-webui/compose.yaml b/backend/templates/ollama-open-webui/compose.yaml new file mode 100644 index 0000000..f14ff35 --- /dev/null +++ b/backend/templates/ollama-open-webui/compose.yaml @@ -0,0 +1,31 @@ +services: + ollama: + image: ollama/ollama:latest + container_name: ollama + restart: unless-stopped + ports: + - "${OLLAMA_PORT:-11434}:11434" + volumes: + - ${DATA_PATH:-/srv/ollama}/models:/root/.ollama + # Uncomment for NVIDIA GPUs (needs the NVIDIA Container Toolkit on the host): + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + + open-webui: + image: ghcr.io/open-webui/open-webui:main + container_name: open-webui + restart: unless-stopped + depends_on: + - ollama + environment: + - OLLAMA_BASE_URL=http://ollama:11434 + - WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY:?secret key required} + ports: + - "${HTTP_PORT:-3020}:8080" + volumes: + - ${DATA_PATH:-/srv/ollama}/open-webui:/app/backend/data diff --git a/backend/templates/ollama-open-webui/template.json b/backend/templates/ollama-open-webui/template.json new file mode 100644 index 0000000..7f364fa --- /dev/null +++ b/backend/templates/ollama-open-webui/template.json @@ -0,0 +1,9 @@ +{ + "name": "Ollama + Open WebUI", + "description": "Local LLM runtime with a ChatGPT-style web front-end. Runs on CPU; far faster with a GPU.", + "tags": [ + "ai", + "llm" + ], + "gpu": "NVIDIA optional" +} diff --git a/backend/templates/photoprism/.env.example b/backend/templates/photoprism/.env.example new file mode 100644 index 0000000..8009645 --- /dev/null +++ b/backend/templates/photoprism/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/photoprism +ORIGINALS_PATH=/srv/media/photos +HTTP_PORT=2342 +SITE_URL=http://localhost:2342/ +ADMIN_USER=admin +ADMIN_PASSWORD=change-me +DB_PASSWORD=change-me +DB_ROOT_PASSWORD=change-me diff --git a/backend/templates/photoprism/compose.yaml b/backend/templates/photoprism/compose.yaml new file mode 100644 index 0000000..e841cb9 --- /dev/null +++ b/backend/templates/photoprism/compose.yaml @@ -0,0 +1,41 @@ +services: + photoprism: + image: photoprism/photoprism:latest + container_name: photoprism + restart: unless-stopped + depends_on: + - mariadb + security_opt: + - seccomp:unconfined + - apparmor:unconfined + environment: + - PHOTOPRISM_ADMIN_USER=${ADMIN_USER:-admin} + - PHOTOPRISM_ADMIN_PASSWORD=${ADMIN_PASSWORD:-change-me} + - PHOTOPRISM_SITE_URL=${SITE_URL:-http://localhost:2342/} + - PHOTOPRISM_ORIGINALS_LIMIT=5000 + - PHOTOPRISM_DATABASE_DRIVER=mysql + - PHOTOPRISM_DATABASE_SERVER=mariadb:3306 + - PHOTOPRISM_DATABASE_NAME=photoprism + - PHOTOPRISM_DATABASE_USER=photoprism + - PHOTOPRISM_DATABASE_PASSWORD=${DB_PASSWORD:-photoprism} + - PHOTOPRISM_UID=${PUID:-1000} + - PHOTOPRISM_GID=${PGID:-1000} + ports: + - "${HTTP_PORT:-2342}:2342" + volumes: + - ${ORIGINALS_PATH:-/srv/media/photos}:/photoprism/originals + - ${DATA_PATH:-/srv/photoprism}/storage:/photoprism/storage + + mariadb: + image: mariadb:11 + container_name: photoprism-db + restart: unless-stopped + command: --innodb-buffer-pool-size=512M --transaction-isolation=READ-COMMITTED + environment: + - MARIADB_AUTO_UPGRADE=1 + - MARIADB_DATABASE=photoprism + - MARIADB_USER=photoprism + - MARIADB_PASSWORD=${DB_PASSWORD:-photoprism} + - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-photoprism} + volumes: + - ${DATA_PATH:-/srv/photoprism}/mariadb:/var/lib/mysql diff --git a/backend/templates/photoprism/template.json b/backend/templates/photoprism/template.json new file mode 100644 index 0000000..2b47048 --- /dev/null +++ b/backend/templates/photoprism/template.json @@ -0,0 +1,9 @@ +{ + "name": "PhotoPrism", + "description": "AI-powered photo library that tags and indexes your originals.", + "tags": [ + "media", + "photos" + ], + "gpu": null +} diff --git a/backend/templates/pihole/.env.example b/backend/templates/pihole/.env.example new file mode 100644 index 0000000..2b9c441 --- /dev/null +++ b/backend/templates/pihole/.env.example @@ -0,0 +1,5 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/pihole +DNS_PORT=53 +HTTP_PORT=8081 +WEB_PASSWORD=change-me diff --git a/backend/templates/pihole/compose.yaml b/backend/templates/pihole/compose.yaml new file mode 100644 index 0000000..f983d76 --- /dev/null +++ b/backend/templates/pihole/compose.yaml @@ -0,0 +1,20 @@ +services: + pihole: + image: pihole/pihole:latest + container_name: pihole + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - FTLCONF_webserver_api_password=${WEB_PASSWORD:-change-me} + - FTLCONF_dns_listeningMode=all + ports: + - "${DNS_PORT:-53}:53/tcp" + - "${DNS_PORT:-53}:53/udp" + - "${HTTP_PORT:-8081}:80/tcp" + volumes: + - ${DATA_PATH:-/srv/pihole}/etc-pihole:/etc/pihole + cap_add: + # Only required when Pi-hole also serves DHCP + - NET_ADMIN + - SYS_TIME + - SYS_NICE diff --git a/backend/templates/pihole/template.json b/backend/templates/pihole/template.json new file mode 100644 index 0000000..747d2c5 --- /dev/null +++ b/backend/templates/pihole/template.json @@ -0,0 +1,10 @@ +{ + "name": "Pi-hole", + "description": "Network-wide DNS ad blocker. Port 53 must be free — disable systemd-resolved's stub listener first.", + "tags": [ + "network", + "dns", + "ad-blocking" + ], + "gpu": null +} diff --git a/backend/templates/plex/.env.example b/backend/templates/plex/.env.example new file mode 100644 index 0000000..b43a153 --- /dev/null +++ b/backend/templates/plex/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +CONFIG_PATH=/srv/plex/config +MEDIA_PATH=/srv/media +HTTP_PORT=32400 +PLEX_CLAIM= diff --git a/backend/templates/plex/compose.yaml b/backend/templates/plex/compose.yaml new file mode 100644 index 0000000..524ab4a --- /dev/null +++ b/backend/templates/plex/compose.yaml @@ -0,0 +1,20 @@ +services: + plex: + image: lscr.io/linuxserver/plex:latest + container_name: plex + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - VERSION=docker + # Grab a claim token at https://plex.tv/claim (valid 4 minutes) + - PLEX_CLAIM=${PLEX_CLAIM:-} + ports: + - "${HTTP_PORT:-32400}:32400" + volumes: + - ${CONFIG_PATH:-/srv/plex/config}:/config + - ${MEDIA_PATH:-/srv/media}:/media + # Uncomment for Intel/AMD hardware transcoding (Plex Pass required): + # devices: + # - /dev/dri:/dev/dri diff --git a/backend/templates/plex/template.json b/backend/templates/plex/template.json new file mode 100644 index 0000000..e19c72d --- /dev/null +++ b/backend/templates/plex/template.json @@ -0,0 +1,9 @@ +{ + "name": "Plex", + "description": "Media server for movies, TV and music with polished client apps.", + "tags": [ + "media", + "streaming" + ], + "gpu": "NVIDIA / Intel optional" +} diff --git a/backend/templates/portainer/.env.example b/backend/templates/portainer/.env.example new file mode 100644 index 0000000..a9e2d4f --- /dev/null +++ b/backend/templates/portainer/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/portainer +HTTP_PORT=9000 +HTTPS_PORT=9443 diff --git a/backend/templates/portainer/compose.yaml b/backend/templates/portainer/compose.yaml new file mode 100644 index 0000000..4f4eae9 --- /dev/null +++ b/backend/templates/portainer/compose.yaml @@ -0,0 +1,11 @@ +services: + portainer: + image: portainer/portainer-ce:latest + container_name: portainer + restart: unless-stopped + ports: + - "${HTTPS_PORT:-9443}:9443" + - "${HTTP_PORT:-9000}:9000" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${DATA_PATH:-/srv/portainer}:/data diff --git a/backend/templates/portainer/template.json b/backend/templates/portainer/template.json new file mode 100644 index 0000000..9502e72 --- /dev/null +++ b/backend/templates/portainer/template.json @@ -0,0 +1,9 @@ +{ + "name": "Portainer CE", + "description": "Docker management UI. The admin account must be created within a few minutes of first start.", + "tags": [ + "docker", + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/postgres/.env.example b/backend/templates/postgres/.env.example new file mode 100644 index 0000000..9914925 --- /dev/null +++ b/backend/templates/postgres/.env.example @@ -0,0 +1,8 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/postgres +DB_PORT=5432 +ADMINER_PORT=8089 +POSTGRES_DB=app +POSTGRES_USER=app +# Required. +POSTGRES_PASSWORD= diff --git a/backend/templates/postgres/compose.yaml b/backend/templates/postgres/compose.yaml new file mode 100644 index 0000000..d45354a --- /dev/null +++ b/backend/templates/postgres/compose.yaml @@ -0,0 +1,30 @@ +services: + postgres: + image: postgres:16-alpine + container_name: postgres + restart: unless-stopped + environment: + - POSTGRES_DB=${POSTGRES_DB:-app} + - POSTGRES_USER=${POSTGRES_USER:-app} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?database password required} + - TZ=${TZ:-Europe/Berlin} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-app}"] + interval: 10s + timeout: 5s + retries: 5 + ports: + - "${DB_PORT:-5432}:5432" + volumes: + - ${DATA_PATH:-/srv/postgres}:/var/lib/postgresql/data + + adminer: + image: adminer:latest + container_name: postgres-adminer + restart: unless-stopped + depends_on: + - postgres + environment: + - ADMINER_DEFAULT_SERVER=postgres + ports: + - "${ADMINER_PORT:-8089}:8080" diff --git a/backend/templates/postgres/template.json b/backend/templates/postgres/template.json new file mode 100644 index 0000000..63f78b6 --- /dev/null +++ b/backend/templates/postgres/template.json @@ -0,0 +1,9 @@ +{ + "name": "PostgreSQL + Adminer", + "description": "Standalone Postgres for your own apps, with Adminer for browsing it.", + "tags": [ + "database", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/prometheus/.env.example b/backend/templates/prometheus/.env.example new file mode 100644 index 0000000..bf80799 --- /dev/null +++ b/backend/templates/prometheus/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/prometheus +HTTP_PORT=9090 +RETENTION=30d diff --git a/backend/templates/prometheus/compose.yaml b/backend/templates/prometheus/compose.yaml new file mode 100644 index 0000000..626a1ae --- /dev/null +++ b/backend/templates/prometheus/compose.yaml @@ -0,0 +1,39 @@ +services: + prometheus: + image: prom/prometheus:latest + container_name: prometheus + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + command: + - --config.file=/etc/prometheus/prometheus.yml + - --storage.tsdb.path=/prometheus + - --storage.tsdb.retention.time=${RETENTION:-30d} + - --web.enable-lifecycle + ports: + - "${HTTP_PORT:-9090}:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + - ${DATA_PATH:-/srv/prometheus}:/prometheus + + node-exporter: + image: prom/node-exporter:latest + container_name: node-exporter + restart: unless-stopped + pid: host + command: + - --path.rootfs=/host + volumes: + - /:/host:ro,rslave + + cadvisor: + image: gcr.io/cadvisor/cadvisor:latest + container_name: cadvisor + restart: unless-stopped + privileged: true + devices: + - /dev/kmsg + volumes: + - /:/rootfs:ro + - /var/run:/var/run:ro + - /sys:/sys:ro + - /var/lib/docker/:/var/lib/docker:ro diff --git a/backend/templates/prometheus/prometheus.yml b/backend/templates/prometheus/prometheus.yml new file mode 100644 index 0000000..415d585 --- /dev/null +++ b/backend/templates/prometheus/prometheus.yml @@ -0,0 +1,16 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: ["localhost:9090"] + + - job_name: node + static_configs: + - targets: ["node-exporter:9100"] + + - job_name: cadvisor + static_configs: + - targets: ["cadvisor:8080"] diff --git a/backend/templates/prometheus/template.json b/backend/templates/prometheus/template.json new file mode 100644 index 0000000..0ba49a5 --- /dev/null +++ b/backend/templates/prometheus/template.json @@ -0,0 +1,9 @@ +{ + "name": "Prometheus", + "description": "Metrics database with node-exporter and cAdvisor already wired up for host and container stats.", + "tags": [ + "monitoring", + "metrics" + ], + "gpu": null +} diff --git a/backend/templates/prowlarr/.env.example b/backend/templates/prowlarr/.env.example new file mode 100644 index 0000000..68e6d16 --- /dev/null +++ b/backend/templates/prowlarr/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/prowlarr +HTTP_PORT=9696 diff --git a/backend/templates/prowlarr/compose.yaml b/backend/templates/prowlarr/compose.yaml new file mode 100644 index 0000000..aa6772e --- /dev/null +++ b/backend/templates/prowlarr/compose.yaml @@ -0,0 +1,13 @@ +services: + prowlarr: + image: lscr.io/linuxserver/prowlarr:latest + container_name: prowlarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-9696}:9696" + volumes: + - ${DATA_PATH:-/srv/prowlarr}:/config diff --git a/backend/templates/prowlarr/template.json b/backend/templates/prowlarr/template.json new file mode 100644 index 0000000..9ad645f --- /dev/null +++ b/backend/templates/prowlarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Prowlarr", + "description": "Indexer manager that feeds trackers and usenet indexers to the *arr apps.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/qbittorrent/.env.example b/backend/templates/qbittorrent/.env.example new file mode 100644 index 0000000..0323a26 --- /dev/null +++ b/backend/templates/qbittorrent/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/qbittorrent +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=8080 +TORRENT_PORT=6881 diff --git a/backend/templates/qbittorrent/compose.yaml b/backend/templates/qbittorrent/compose.yaml new file mode 100644 index 0000000..030c299 --- /dev/null +++ b/backend/templates/qbittorrent/compose.yaml @@ -0,0 +1,18 @@ +services: + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + container_name: qbittorrent + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - WEBUI_PORT=${HTTP_PORT:-8080} + - TORRENTING_PORT=${TORRENT_PORT:-6881} + ports: + - "${HTTP_PORT:-8080}:${HTTP_PORT:-8080}" + - "${TORRENT_PORT:-6881}:${TORRENT_PORT:-6881}" + - "${TORRENT_PORT:-6881}:${TORRENT_PORT:-6881}/udp" + volumes: + - ${DATA_PATH:-/srv/qbittorrent}:/config + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/qbittorrent/template.json b/backend/templates/qbittorrent/template.json new file mode 100644 index 0000000..8b52309 --- /dev/null +++ b/backend/templates/qbittorrent/template.json @@ -0,0 +1,9 @@ +{ + "name": "qBittorrent", + "description": "BitTorrent client with a web UI. The temporary admin password is printed to the container log on first start.", + "tags": [ + "downloads", + "media" + ], + "gpu": null +} diff --git a/backend/templates/radarr/.env.example b/backend/templates/radarr/.env.example new file mode 100644 index 0000000..0845023 --- /dev/null +++ b/backend/templates/radarr/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/radarr +MEDIA_PATH=/srv/media +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=7878 diff --git a/backend/templates/radarr/compose.yaml b/backend/templates/radarr/compose.yaml new file mode 100644 index 0000000..ad266a3 --- /dev/null +++ b/backend/templates/radarr/compose.yaml @@ -0,0 +1,15 @@ +services: + radarr: + image: lscr.io/linuxserver/radarr:latest + container_name: radarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-7878}:7878" + volumes: + - ${DATA_PATH:-/srv/radarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/radarr/template.json b/backend/templates/radarr/template.json new file mode 100644 index 0000000..bd52e4b --- /dev/null +++ b/backend/templates/radarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Radarr", + "description": "Automates finding, downloading and organising movies.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/redis/.env.example b/backend/templates/redis/.env.example new file mode 100644 index 0000000..e0a5254 --- /dev/null +++ b/backend/templates/redis/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/redis +REDIS_PORT=6379 +# Required. +REDIS_PASSWORD= diff --git a/backend/templates/redis/compose.yaml b/backend/templates/redis/compose.yaml new file mode 100644 index 0000000..ac9aa99 --- /dev/null +++ b/backend/templates/redis/compose.yaml @@ -0,0 +1,20 @@ +services: + redis: + image: redis:7-alpine + container_name: redis + restart: unless-stopped + environment: + - REDIS_PASSWORD=${REDIS_PASSWORD:?redis password required} + command: > + redis-server + --appendonly yes + --requirepass ${REDIS_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "redis-cli --no-auth-warning -a \"$$REDIS_PASSWORD\" ping | grep -q PONG"] + interval: 10s + timeout: 5s + retries: 5 + ports: + - "${REDIS_PORT:-6379}:6379" + volumes: + - ${DATA_PATH:-/srv/redis}:/data diff --git a/backend/templates/redis/template.json b/backend/templates/redis/template.json new file mode 100644 index 0000000..7620d11 --- /dev/null +++ b/backend/templates/redis/template.json @@ -0,0 +1,9 @@ +{ + "name": "Redis", + "description": "In-memory cache and queue with append-only persistence, for apps that need one.", + "tags": [ + "database", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/sabnzbd/.env.example b/backend/templates/sabnzbd/.env.example new file mode 100644 index 0000000..e729be9 --- /dev/null +++ b/backend/templates/sabnzbd/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/sabnzbd +DOWNLOADS_PATH=/srv/downloads +INCOMPLETE_PATH=/srv/downloads/incomplete +HTTP_PORT=8880 diff --git a/backend/templates/sabnzbd/compose.yaml b/backend/templates/sabnzbd/compose.yaml new file mode 100644 index 0000000..bfd55fb --- /dev/null +++ b/backend/templates/sabnzbd/compose.yaml @@ -0,0 +1,15 @@ +services: + sabnzbd: + image: lscr.io/linuxserver/sabnzbd:latest + container_name: sabnzbd + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8880}:8080" + volumes: + - ${DATA_PATH:-/srv/sabnzbd}:/config + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads + - ${INCOMPLETE_PATH:-/srv/downloads/incomplete}:/incomplete-downloads diff --git a/backend/templates/sabnzbd/template.json b/backend/templates/sabnzbd/template.json new file mode 100644 index 0000000..33042a2 --- /dev/null +++ b/backend/templates/sabnzbd/template.json @@ -0,0 +1,9 @@ +{ + "name": "SABnzbd", + "description": "Usenet binary downloader that hands finished jobs to the *arr apps.", + "tags": [ + "downloads", + "media" + ], + "gpu": null +} diff --git a/backend/templates/scrutiny/.env.example b/backend/templates/scrutiny/.env.example new file mode 100644 index 0000000..76f4b1e --- /dev/null +++ b/backend/templates/scrutiny/.env.example @@ -0,0 +1,2 @@ +DATA_PATH=/srv/scrutiny +HTTP_PORT=8085 diff --git a/backend/templates/scrutiny/compose.yaml b/backend/templates/scrutiny/compose.yaml new file mode 100644 index 0000000..8243283 --- /dev/null +++ b/backend/templates/scrutiny/compose.yaml @@ -0,0 +1,19 @@ +services: + scrutiny: + image: ghcr.io/analogj/scrutiny:master-omnibus + container_name: scrutiny + restart: unless-stopped + cap_add: + - SYS_RAWIO + ports: + - "${HTTP_PORT:-8085}:8080" + volumes: + - /run/udev:/run/udev:ro + - ${DATA_PATH:-/srv/scrutiny}/config:/opt/scrutiny/config + - ${DATA_PATH:-/srv/scrutiny}/influxdb:/opt/scrutiny/influxdb + devices: + # List every disk you want monitored. + - /dev/sda:/dev/sda + # - /dev/sdb:/dev/sdb + # NVMe drives additionally need: cap_add: [SYS_ADMIN] + # - /dev/nvme0:/dev/nvme0 diff --git a/backend/templates/scrutiny/template.json b/backend/templates/scrutiny/template.json new file mode 100644 index 0000000..13f0e6a --- /dev/null +++ b/backend/templates/scrutiny/template.json @@ -0,0 +1,9 @@ +{ + "name": "Scrutiny", + "description": "S.M.A.R.T. disk health dashboard with failure-rate context. Edit the devices list before deploying.", + "tags": [ + "monitoring", + "storage" + ], + "gpu": null +} diff --git a/backend/templates/searxng/.env.example b/backend/templates/searxng/.env.example new file mode 100644 index 0000000..7b19185 --- /dev/null +++ b/backend/templates/searxng/.env.example @@ -0,0 +1,5 @@ +DATA_PATH=/srv/searxng +HTTP_PORT=8099 +BASE_URL=http://localhost:8099 +# Required. Generate with: openssl rand -hex 32 +SEARXNG_SECRET= diff --git a/backend/templates/searxng/compose.yaml b/backend/templates/searxng/compose.yaml new file mode 100644 index 0000000..9109db5 --- /dev/null +++ b/backend/templates/searxng/compose.yaml @@ -0,0 +1,20 @@ +services: + searxng: + image: searxng/searxng:latest + container_name: searxng + restart: unless-stopped + environment: + - SEARXNG_BASE_URL=${BASE_URL:-http://localhost:8099}/ + - SEARXNG_SECRET=${SEARXNG_SECRET:?secret required} + - UWSGI_WORKERS=4 + - UWSGI_THREADS=4 + ports: + - "${HTTP_PORT:-8099}:8080" + volumes: + - ${DATA_PATH:-/srv/searxng}:/etc/searxng + cap_drop: + - ALL + cap_add: + - CHOWN + - SETGID + - SETUID diff --git a/backend/templates/searxng/template.json b/backend/templates/searxng/template.json new file mode 100644 index 0000000..20d3f26 --- /dev/null +++ b/backend/templates/searxng/template.json @@ -0,0 +1,9 @@ +{ + "name": "SearXNG", + "description": "Private meta search engine that queries dozens of engines without tracking you.", + "tags": [ + "search", + "privacy" + ], + "gpu": null +} diff --git a/backend/templates/sonarr/.env.example b/backend/templates/sonarr/.env.example new file mode 100644 index 0000000..1641fc7 --- /dev/null +++ b/backend/templates/sonarr/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/sonarr +MEDIA_PATH=/srv/media +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=8989 diff --git a/backend/templates/sonarr/compose.yaml b/backend/templates/sonarr/compose.yaml new file mode 100644 index 0000000..2d2c9e6 --- /dev/null +++ b/backend/templates/sonarr/compose.yaml @@ -0,0 +1,15 @@ +services: + sonarr: + image: lscr.io/linuxserver/sonarr:latest + container_name: sonarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8989}:8989" + volumes: + - ${DATA_PATH:-/srv/sonarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/sonarr/template.json b/backend/templates/sonarr/template.json new file mode 100644 index 0000000..7754695 --- /dev/null +++ b/backend/templates/sonarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Sonarr", + "description": "Automates finding, downloading and organising TV series.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/speedtest-tracker/.env.example b/backend/templates/speedtest-tracker/.env.example new file mode 100644 index 0000000..f53c1a7 --- /dev/null +++ b/backend/templates/speedtest-tracker/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/speedtest-tracker +HTTP_PORT=8086 +APP_URL=http://localhost:8086 +# Required. Generate with: openssl rand -base64 32 +APP_KEY= +SPEEDTEST_SCHEDULE=0 */6 * * * +PRUNE_DAYS=90 diff --git a/backend/templates/speedtest-tracker/compose.yaml b/backend/templates/speedtest-tracker/compose.yaml new file mode 100644 index 0000000..af49460 --- /dev/null +++ b/backend/templates/speedtest-tracker/compose.yaml @@ -0,0 +1,20 @@ +services: + speedtest-tracker: + image: lscr.io/linuxserver/speedtest-tracker:latest + container_name: speedtest-tracker + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + # Generate with: openssl rand -base64 32 + - APP_KEY=${APP_KEY:?app key required} + - APP_URL=${APP_URL:-http://localhost:8086} + - DB_CONNECTION=sqlite + - SPEEDTEST_SCHEDULE=${SPEEDTEST_SCHEDULE:-0 */6 * * *} + - DISPLAY_TIMEZONE=${TZ:-Europe/Berlin} + - PRUNE_RESULTS_OLDER_THAN=${PRUNE_DAYS:-90} + ports: + - "${HTTP_PORT:-8086}:80" + volumes: + - ${DATA_PATH:-/srv/speedtest-tracker}:/config diff --git a/backend/templates/speedtest-tracker/template.json b/backend/templates/speedtest-tracker/template.json new file mode 100644 index 0000000..9caa551 --- /dev/null +++ b/backend/templates/speedtest-tracker/template.json @@ -0,0 +1,9 @@ +{ + "name": "Speedtest Tracker", + "description": "Runs scheduled internet speed tests and charts the history. First login: admin@example.com / password.", + "tags": [ + "monitoring", + "network" + ], + "gpu": null +} diff --git a/backend/templates/stirling-pdf/.env.example b/backend/templates/stirling-pdf/.env.example new file mode 100644 index 0000000..920a06e --- /dev/null +++ b/backend/templates/stirling-pdf/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/stirling-pdf +HTTP_PORT=8092 +LANGS=en_GB,de_DE diff --git a/backend/templates/stirling-pdf/compose.yaml b/backend/templates/stirling-pdf/compose.yaml new file mode 100644 index 0000000..b90f442 --- /dev/null +++ b/backend/templates/stirling-pdf/compose.yaml @@ -0,0 +1,13 @@ +services: + stirling-pdf: + image: ghcr.io/stirling-tools/stirling-pdf:latest + container_name: stirling-pdf + restart: unless-stopped + environment: + - DOCKER_ENABLE_SECURITY=false + - LANGS=${LANGS:-en_GB,de_DE} + ports: + - "${HTTP_PORT:-8092}:8080" + volumes: + - ${DATA_PATH:-/srv/stirling-pdf}/configs:/configs + - ${DATA_PATH:-/srv/stirling-pdf}/tessdata:/usr/share/tessdata diff --git a/backend/templates/stirling-pdf/template.json b/backend/templates/stirling-pdf/template.json new file mode 100644 index 0000000..b3fcca2 --- /dev/null +++ b/backend/templates/stirling-pdf/template.json @@ -0,0 +1,9 @@ +{ + "name": "Stirling PDF", + "description": "Split, merge, OCR, sign and convert PDFs entirely in your own browser session.", + "tags": [ + "documents", + "utilities" + ], + "gpu": null +} diff --git a/backend/templates/syncthing/.env.example b/backend/templates/syncthing/.env.example new file mode 100644 index 0000000..0272e3a --- /dev/null +++ b/backend/templates/syncthing/.env.example @@ -0,0 +1,9 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +SYNC_HOSTNAME=homelab +DATA_PATH=/srv/syncthing +SYNC_PATH=/srv/sync +HTTP_PORT=8384 +SYNC_PORT=22000 +DISCOVERY_PORT=21027 diff --git a/backend/templates/syncthing/compose.yaml b/backend/templates/syncthing/compose.yaml new file mode 100644 index 0000000..806aff0 --- /dev/null +++ b/backend/templates/syncthing/compose.yaml @@ -0,0 +1,18 @@ +services: + syncthing: + image: lscr.io/linuxserver/syncthing:latest + container_name: syncthing + restart: unless-stopped + hostname: ${SYNC_HOSTNAME:-homelab} + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8384}:8384" + - "${SYNC_PORT:-22000}:22000/tcp" + - "${SYNC_PORT:-22000}:22000/udp" + - "${DISCOVERY_PORT:-21027}:21027/udp" + volumes: + - ${DATA_PATH:-/srv/syncthing}:/config + - ${SYNC_PATH:-/srv/sync}:/data diff --git a/backend/templates/syncthing/template.json b/backend/templates/syncthing/template.json new file mode 100644 index 0000000..0a979e5 --- /dev/null +++ b/backend/templates/syncthing/template.json @@ -0,0 +1,10 @@ +{ + "name": "Syncthing", + "description": "Continuous peer-to-peer file sync between your own devices — no server in the middle.", + "tags": [ + "files", + "sync", + "backup" + ], + "gpu": null +} diff --git a/backend/templates/tailscale/.env.example b/backend/templates/tailscale/.env.example new file mode 100644 index 0000000..f1873c2 --- /dev/null +++ b/backend/templates/tailscale/.env.example @@ -0,0 +1,5 @@ +DATA_PATH=/srv/tailscale +TS_HOSTNAME=homelab +TS_AUTHKEY= +TS_ROUTES= +TS_EXTRA_ARGS=--accept-dns=false diff --git a/backend/templates/tailscale/compose.yaml b/backend/templates/tailscale/compose.yaml new file mode 100644 index 0000000..f1bfc18 --- /dev/null +++ b/backend/templates/tailscale/compose.yaml @@ -0,0 +1,20 @@ +services: + tailscale: + image: tailscale/tailscale:latest + container_name: tailscale + restart: unless-stopped + hostname: ${TS_HOSTNAME:-homelab} + network_mode: host + environment: + # Generate at https://login.tailscale.com/admin/settings/keys + - TS_AUTHKEY=${TS_AUTHKEY:-} + - TS_STATE_DIR=/var/lib/tailscale + # Advertise your LAN so tailnet clients can reach it, e.g. 192.168.1.0/24 + - TS_ROUTES=${TS_ROUTES:-} + - TS_EXTRA_ARGS=${TS_EXTRA_ARGS:---accept-dns=false} + volumes: + - ${DATA_PATH:-/srv/tailscale}:/var/lib/tailscale + - /dev/net/tun:/dev/net/tun + cap_add: + - NET_ADMIN + - NET_RAW diff --git a/backend/templates/tailscale/template.json b/backend/templates/tailscale/template.json new file mode 100644 index 0000000..ea186b5 --- /dev/null +++ b/backend/templates/tailscale/template.json @@ -0,0 +1,10 @@ +{ + "name": "Tailscale", + "description": "Mesh VPN node, optionally advertising your LAN as a subnet router.", + "tags": [ + "network", + "vpn", + "security" + ], + "gpu": null +} diff --git a/backend/templates/traefik/.env.example b/backend/templates/traefik/.env.example new file mode 100644 index 0000000..25cacfd --- /dev/null +++ b/backend/templates/traefik/.env.example @@ -0,0 +1,6 @@ +DATA_PATH=/srv/traefik +HTTP_PORT=80 +HTTPS_PORT=443 +DASHBOARD_PORT=8180 +DASHBOARD_HOST=traefik.example.com +ACME_EMAIL=admin@example.com diff --git a/backend/templates/traefik/compose.yaml b/backend/templates/traefik/compose.yaml new file mode 100644 index 0000000..2c8f8cb --- /dev/null +++ b/backend/templates/traefik/compose.yaml @@ -0,0 +1,29 @@ +services: + traefik: + image: traefik:v3 + container_name: traefik + restart: unless-stopped + command: + - --api.dashboard=true + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --entrypoints.websecure.address=:443 + - --certificatesresolvers.le.acme.email=${ACME_EMAIL:-admin@example.com} + - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json + - --certificatesresolvers.le.acme.tlschallenge=true + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + - "${DASHBOARD_PORT:-8180}:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ${DATA_PATH:-/srv/traefik}/letsencrypt:/letsencrypt + labels: + - traefik.enable=true + - traefik.http.routers.dashboard.rule=Host(`${DASHBOARD_HOST:-traefik.example.com}`) + - traefik.http.routers.dashboard.service=api@internal + - traefik.http.routers.dashboard.entrypoints=websecure + - traefik.http.routers.dashboard.tls.certresolver=le diff --git a/backend/templates/traefik/template.json b/backend/templates/traefik/template.json new file mode 100644 index 0000000..354d0d5 --- /dev/null +++ b/backend/templates/traefik/template.json @@ -0,0 +1,9 @@ +{ + "name": "Traefik", + "description": "Reverse proxy that discovers containers via Docker labels and issues Let's Encrypt certificates.", + "tags": [ + "network", + "proxy" + ], + "gpu": null +} diff --git a/backend/templates/trilium/.env.example b/backend/templates/trilium/.env.example new file mode 100644 index 0000000..7a9e05d --- /dev/null +++ b/backend/templates/trilium/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/trilium +HTTP_PORT=8101 diff --git a/backend/templates/trilium/compose.yaml b/backend/templates/trilium/compose.yaml new file mode 100644 index 0000000..cdbfa77 --- /dev/null +++ b/backend/templates/trilium/compose.yaml @@ -0,0 +1,11 @@ +services: + trilium: + image: triliumnext/trilium:latest + container_name: trilium + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8101}:8080" + volumes: + - ${DATA_PATH:-/srv/trilium}:/home/node/trilium-data diff --git a/backend/templates/trilium/template.json b/backend/templates/trilium/template.json new file mode 100644 index 0000000..d1d98ff --- /dev/null +++ b/backend/templates/trilium/template.json @@ -0,0 +1,9 @@ +{ + "name": "Trilium Notes", + "description": "Hierarchical note taking for large personal knowledge bases, with sync and scripting.", + "tags": [ + "notes", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/vikunja/.env.example b/backend/templates/vikunja/.env.example new file mode 100644 index 0000000..710eb87 --- /dev/null +++ b/backend/templates/vikunja/.env.example @@ -0,0 +1,6 @@ +DATA_PATH=/srv/vikunja +HTTP_PORT=3456 +PUBLIC_URL=http://localhost:3456 +DB_PASSWORD=change-me +# Required. Generate with: openssl rand -hex 32 +SERVICE_SECRET= diff --git a/backend/templates/vikunja/compose.yaml b/backend/templates/vikunja/compose.yaml new file mode 100644 index 0000000..26dda03 --- /dev/null +++ b/backend/templates/vikunja/compose.yaml @@ -0,0 +1,36 @@ +services: + vikunja: + image: vikunja/vikunja:latest + container_name: vikunja + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + - VIKUNJA_SERVICE_PUBLICURL=${PUBLIC_URL:-http://localhost:3456} + - VIKUNJA_SERVICE_SECRET=${SERVICE_SECRET:?service secret required} + - VIKUNJA_DATABASE_TYPE=postgres + - VIKUNJA_DATABASE_HOST=db + - VIKUNJA_DATABASE_DATABASE=vikunja + - VIKUNJA_DATABASE_USER=vikunja + - VIKUNJA_DATABASE_PASSWORD=${DB_PASSWORD:-vikunja} + ports: + - "${HTTP_PORT:-3456}:3456" + volumes: + - ${DATA_PATH:-/srv/vikunja}/files:/app/vikunja/files + + db: + image: postgres:16-alpine + container_name: vikunja-db + restart: unless-stopped + environment: + - POSTGRES_DB=vikunja + - POSTGRES_USER=vikunja + - POSTGRES_PASSWORD=${DB_PASSWORD:-vikunja} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U vikunja"] + interval: 5s + timeout: 5s + retries: 10 + volumes: + - ${DATA_PATH:-/srv/vikunja}/db:/var/lib/postgresql/data diff --git a/backend/templates/vikunja/template.json b/backend/templates/vikunja/template.json new file mode 100644 index 0000000..68f1ecf --- /dev/null +++ b/backend/templates/vikunja/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vikunja", + "description": "To-do and project management with list, gantt, table and kanban views.", + "tags": [ + "productivity", + "tasks" + ], + "gpu": null +} diff --git a/backend/templates/watchtower/.env.example b/backend/templates/watchtower/.env.example new file mode 100644 index 0000000..569ab1d --- /dev/null +++ b/backend/templates/watchtower/.env.example @@ -0,0 +1,5 @@ +TZ=Europe/Berlin +# Six-field cron (seconds first). Default: every day at 04:00. +SCHEDULE=0 0 4 * * * +MONITOR_ONLY=false +NOTIFICATION_URL= diff --git a/backend/templates/watchtower/compose.yaml b/backend/templates/watchtower/compose.yaml new file mode 100644 index 0000000..228793b --- /dev/null +++ b/backend/templates/watchtower/compose.yaml @@ -0,0 +1,16 @@ +services: + watchtower: + image: containrrr/watchtower:latest + container_name: watchtower + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_INCLUDE_RESTARTING=true + - WATCHTOWER_SCHEDULE=${SCHEDULE:-0 0 4 * * *} + # Report only, never pull: set to true for a dry run. + - WATCHTOWER_MONITOR_ONLY=${MONITOR_ONLY:-false} + # e.g. shoutrrr URL for ntfy/Gotify/Discord notifications + - WATCHTOWER_NOTIFICATION_URL=${NOTIFICATION_URL:-} + volumes: + - /var/run/docker.sock:/var/run/docker.sock diff --git a/backend/templates/watchtower/template.json b/backend/templates/watchtower/template.json new file mode 100644 index 0000000..9d60e33 --- /dev/null +++ b/backend/templates/watchtower/template.json @@ -0,0 +1,9 @@ +{ + "name": "Watchtower", + "description": "Watches running containers and pulls new images on a schedule.", + "tags": [ + "docker", + "automation" + ], + "gpu": null +} diff --git a/backend/templates/wg-easy/.env.example b/backend/templates/wg-easy/.env.example new file mode 100644 index 0000000..9a4e9e7 --- /dev/null +++ b/backend/templates/wg-easy/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/wg-easy +WG_PORT=51820 +HTTP_PORT=51821 diff --git a/backend/templates/wg-easy/compose.yaml b/backend/templates/wg-easy/compose.yaml new file mode 100644 index 0000000..dd81308 --- /dev/null +++ b/backend/templates/wg-easy/compose.yaml @@ -0,0 +1,17 @@ +services: + wg-easy: + image: ghcr.io/wg-easy/wg-easy:15 + container_name: wg-easy + restart: unless-stopped + ports: + - "${WG_PORT:-51820}:51820/udp" + - "${HTTP_PORT:-51821}:51821/tcp" + volumes: + - ${DATA_PATH:-/srv/wg-easy}:/etc/wireguard + - /lib/modules:/lib/modules:ro + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 diff --git a/backend/templates/wg-easy/template.json b/backend/templates/wg-easy/template.json new file mode 100644 index 0000000..f9201dd --- /dev/null +++ b/backend/templates/wg-easy/template.json @@ -0,0 +1,10 @@ +{ + "name": "WG-Easy", + "description": "WireGuard VPN server with a web UI for clients and QR codes. Admin account is created on first visit.", + "tags": [ + "network", + "vpn", + "security" + ], + "gpu": null +} diff --git a/backend/templates/wiki-js/.env.example b/backend/templates/wiki-js/.env.example new file mode 100644 index 0000000..f784b68 --- /dev/null +++ b/backend/templates/wiki-js/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/wiki-js +HTTP_PORT=3080 +DB_PASSWORD=change-me diff --git a/backend/templates/wiki-js/compose.yaml b/backend/templates/wiki-js/compose.yaml new file mode 100644 index 0000000..4179a75 --- /dev/null +++ b/backend/templates/wiki-js/compose.yaml @@ -0,0 +1,27 @@ +services: + wiki: + image: ghcr.io/requarks/wiki:2 + container_name: wiki-js + restart: unless-stopped + depends_on: + - db + environment: + - DB_TYPE=postgres + - DB_HOST=db + - DB_PORT=5432 + - DB_NAME=wiki + - DB_USER=wiki + - DB_PASS=${DB_PASSWORD:-wiki} + ports: + - "${HTTP_PORT:-3080}:3000" + + db: + image: postgres:16-alpine + container_name: wiki-js-db + restart: unless-stopped + environment: + - POSTGRES_DB=wiki + - POSTGRES_USER=wiki + - POSTGRES_PASSWORD=${DB_PASSWORD:-wiki} + volumes: + - ${DATA_PATH:-/srv/wiki-js}/db:/var/lib/postgresql/data diff --git a/backend/templates/wiki-js/template.json b/backend/templates/wiki-js/template.json new file mode 100644 index 0000000..bd53d3b --- /dev/null +++ b/backend/templates/wiki-js/template.json @@ -0,0 +1,10 @@ +{ + "name": "Wiki.js", + "description": "Modern wiki with Markdown and WYSIWYG editors, git sync and fine-grained permissions.", + "tags": [ + "documents", + "wiki", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/zigbee2mqtt/.env.example b/backend/templates/zigbee2mqtt/.env.example new file mode 100644 index 0000000..5f2ae53 --- /dev/null +++ b/backend/templates/zigbee2mqtt/.env.example @@ -0,0 +1,4 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/zigbee2mqtt +HTTP_PORT=8095 +ZIGBEE_DEVICE=/dev/ttyUSB0 diff --git a/backend/templates/zigbee2mqtt/compose.yaml b/backend/templates/zigbee2mqtt/compose.yaml new file mode 100644 index 0000000..48a2e6a --- /dev/null +++ b/backend/templates/zigbee2mqtt/compose.yaml @@ -0,0 +1,18 @@ +services: + zigbee2mqtt: + image: koenkk/zigbee2mqtt:latest + container_name: zigbee2mqtt + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8095}:8080" + volumes: + # The config lives with the stack so you can edit it here; the directory + # underneath holds the device database Zigbee2MQTT builds as you pair. + - ${DATA_PATH:-/srv/zigbee2mqtt}:/app/data + - ./configuration.yaml:/app/data/configuration.yaml + - /run/udev:/run/udev:ro + devices: + # Your Zigbee coordinator. Find it with: ls -l /dev/serial/by-id/ + - ${ZIGBEE_DEVICE:-/dev/ttyUSB0}:/dev/ttyACM0 diff --git a/backend/templates/zigbee2mqtt/configuration.yaml b/backend/templates/zigbee2mqtt/configuration.yaml new file mode 100644 index 0000000..73d5d4e --- /dev/null +++ b/backend/templates/zigbee2mqtt/configuration.yaml @@ -0,0 +1,13 @@ +# Point `server` at your MQTT broker. Zigbee2MQTT also writes back to this file +# as you pair devices, so keep it in the stack folder. +homeassistant: + enabled: true +frontend: + port: 8080 +mqtt: + base_topic: zigbee2mqtt + server: mqtt://mosquitto:1883 +serial: + port: /dev/ttyACM0 +advanced: + log_level: info diff --git a/backend/templates/zigbee2mqtt/template.json b/backend/templates/zigbee2mqtt/template.json new file mode 100644 index 0000000..23eadfe --- /dev/null +++ b/backend/templates/zigbee2mqtt/template.json @@ -0,0 +1,10 @@ +{ + "name": "Zigbee2MQTT", + "description": "Bridges Zigbee devices to MQTT without a vendor hub. Point it at your broker and coordinator stick.", + "tags": [ + "home-automation", + "iot", + "mqtt" + ], + "gpu": null +} diff --git a/backend/version.py b/backend/version.py index 62ebe57..6a55ffd 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,3 +1,3 @@ """Single source of truth for the StackPilot release version.""" -APP_VERSION = "0.42.2" +APP_VERSION = "0.43.0" diff --git a/frontend/package.json b/frontend/package.json index 0c862f9..04068a4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "stackpilot-frontend", "private": true, - "version": "0.42.2", + "version": "0.43.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/Templates.tsx b/frontend/src/pages/Templates.tsx index 76ea29d..0394772 100644 --- a/frontend/src/pages/Templates.tsx +++ b/frontend/src/pages/Templates.tsx @@ -1,7 +1,7 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { LayoutTemplate, Cpu, Package, Trash2, FileCode } from "lucide-react"; +import { LayoutTemplate, Cpu, Package, Trash2, FileCode, Search } from "lucide-react"; import { Badge, Button, Card, Input, Spinner } from "@/components/ui"; import { ConfirmDialog } from "@/components/ui/ConfirmDialog"; import { templatesApi, type TemplateDetail, type TemplateSummary } from "@/api/templates"; @@ -18,8 +18,31 @@ export function Templates() { const queryClient = useQueryClient(); const [selected, setSelected] = useState(null); const [toDelete, setToDelete] = useState(null); + const [search, setSearch] = useState(""); + const [tag, setTag] = useState(null); const { data, isLoading } = useQuery({ queryKey: ["templates"], queryFn: templatesApi.list }); + // Tags sorted by how many templates carry them — the useful ones surface first. + const tags = useMemo(() => { + const counts = new Map(); + for (const t of data ?? []) { + for (const tg of t.tags) counts.set(tg, (counts.get(tg) ?? 0) + 1); + } + return [...counts.entries()] + .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) + .map(([name, count]) => ({ name, count })); + }, [data]); + + const shown = useMemo(() => { + const q = search.trim().toLowerCase(); + return (data ?? []).filter((t) => { + if (tag && !t.tags.includes(tag)) return false; + if (!q) return true; + const haystack = `${t.name} ${t.description ?? ""} ${t.tags.join(" ")}`.toLowerCase(); + return haystack.includes(q); + }); + }, [data, search, tag]); + const open = async (t: TemplateSummary) => { try { setSelected(await templatesApi.get(t.id)); @@ -50,8 +73,36 @@ export function Templates() { Templates are stored as ready-to-run stack folders. Pull one to copy it into a new stack, then edit it like any other.

+ +
+
+ + setSearch(e.target.value)} + /> +
+
+ setTag(null)} /> + {tags.map((tg) => ( + setTag(tag === tg.name ? null : tg.name)} + /> + ))} +
+
+ + {shown.length === 0 && ( +

No template matches that filter.

+ )} +
- {data?.map((t) => ( + {shown.map((t) => (
{t.source === "custom" ? ( @@ -64,10 +115,15 @@ export function Templates() {
{t.description &&

{t.description}

}
- {t.tags.map((tag) => ( - - {tag} - + {t.tags.map((tg) => ( + ))} {t.gpu && ( @@ -109,6 +165,30 @@ export function Templates() { ); } +function TagChip({ + label, + active, + onClick, +}: { + label: string; + active: boolean; + onClick: () => void; +}) { + return ( + + ); +} + function UseTemplateDialog({ template, onClose,