diff --git a/CHANGELOG.md b/CHANGELOG.md index fba81fa..99502ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,56 @@ +# v0.6.00-beta — 29 apps erbij, en een bug die zes bestaande apps onstartbaar maakte + +## 🐛 Named volumes verdwenen uit het compose-bestand + +Bij het bouwen van de nieuwe apps liep ik tegen iets aan dat er al langer zat. +`_drop_empty_mappings` ruimt sleutels op die een `<% if %>`-blok leeg +achterlaat. Maar een named volume wordt gedeclareerd als een kale sleutel zonder +waarde: + +```yaml +volumes: + immich_pgdata: +``` + +Die werd als "lege mapping" opgeruimd, waarna ook het bovenliggende `volumes:` +wegviel. De services verwezen vervolgens naar een volume dat nergens meer +gedeclareerd stond, en `docker compose up` weigert dat met *"refers to undefined +volume"*. + +**Immich, Ghost, Miniflux, Paperless-ngx, Unifi Network en Vikunja waren +daardoor niet installeerbaar via Server Up.** Alleen bekende compose-sleutels +worden nu opgeruimd; een volumedeclaratie blijft staan. + +## 📦 29 nieuwe apps + +**Domotica** — Zigbee2MQTT, Mosquitto MQTT, ESPHome, Node-RED, Z-Wave JS UI, +Homebridge, Scrypted, Frigate NVR + +**3D-printen** — Bambuddy (Bambu Lab, zonder cloud), OctoPrint, Spoolman + +**Gezin en huishouden** — Baby Buddy, Grocy, Tandoor Recipes, Firefly III + +**Media** — Audiobookshelf, Navidrome, Kavita, Calibre-Web, Jellyseerr + +**Kennis** — BookStack, FreshRSS, Wallabag + +**Netwerk en beheer** — WireGuard Easy, Homepage, SearXNG, Duplicati, +Watchtower, Technitium DNS + +Daarmee staat de teller op 65 apps. + +## ✅ Testsuite over de echte templates + +Nieuwe `tests/test_apps.py` rendert **elke** app met de echte engine en +controleert de uitkomst: geldige YAML, elke service heeft een image of build, +named volumes zijn gedeclareerd, geen onvervangen `<< variabelen >>`, en geen +onbedoeld dubbele standaardpoorten. Die laatste vond zeven botsingen tussen de +nieuwe apps en bestaande; die zijn rechtgezet. Botsingen die logisch zijn +(Traefik en Nginx Proxy Manager op 80/443, AdGuard en Pi-hole op 53) staan met +uitleg in een allowlist. + +--- + # v0.5.50-beta — Containers die nergens te vinden waren Het dashboard meldde 13 actieve containers en 17 images, terwijl je die nergens diff --git a/VERSION b/VERSION index 287a4da..465f593 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.50-beta +0.6.00-beta diff --git a/apps/audiobookshelf/files/compose.yaml b/apps/audiobookshelf/files/compose.yaml new file mode 100644 index 0000000..6219468 --- /dev/null +++ b/apps/audiobookshelf/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: ghcr.io/advplyr/audiobookshelf:latest + container_name: << service_name >> + ports: + - "<< port >>:80" + environment: + - TZ=<< timezone >> + volumes: + - << media_dir >>:/audiobooks + - << data_dir >>/<< service_name >>/config:/config + - << data_dir >>/<< service_name >>/metadata:/metadata + restart: unless-stopped diff --git a/apps/audiobookshelf/template.json b/apps/audiobookshelf/template.json new file mode 100644 index 0000000..ae44b9f --- /dev/null +++ b/apps/audiobookshelf/template.json @@ -0,0 +1,61 @@ +{ + "kind": "compose", + "metadata": { + "name": "Audiobookshelf", + "description": "Server voor luisterboeken en podcasts, met apps voor iOS en Android die je voortgang bijhouden.", + "tags": [ + "media", + "luisterboeken", + "podcast" + ], + "icon": { + "provider": "selfhst", + "id": "audiobookshelf" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "audiobookshelf", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 13378, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "media_dir", + "type": "str", + "title": "Map met luisterboeken", + "default": "/opt/serverup/media/audiobooks", + "required": true + } + ] + } + ] +} diff --git a/apps/baby-buddy/files/compose.yaml b/apps/baby-buddy/files/compose.yaml new file mode 100644 index 0000000..f0bd466 --- /dev/null +++ b/apps/baby-buddy/files/compose.yaml @@ -0,0 +1,16 @@ +services: + << service_name >>: + image: ghcr.io/babybuddy/babybuddy:latest + container_name: << service_name >> + ports: + - "<< port >>:8000" + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + - SECRET_KEY=<< secret_key >> + - ALLOWED_HOSTS=<< allowed_hosts >> + - CSRF_TRUSTED_ORIGINS=http://<< allowed_hosts >> + volumes: + - << data_dir >>/<< service_name >>/config:/app/data + restart: unless-stopped diff --git a/apps/baby-buddy/template.json b/apps/baby-buddy/template.json new file mode 100644 index 0000000..25c41d2 --- /dev/null +++ b/apps/baby-buddy/template.json @@ -0,0 +1,88 @@ +{ + "kind": "compose", + "metadata": { + "name": "Baby Buddy", + "description": "Houdt voedingen, slaap, luiers en groei van je kind bij. Handig om het overzicht te bewaren in de eerste maanden.", + "tags": [ + "gezin", + "gezondheid", + "logboek" + ], + "icon": { + "provider": "selfhst", + "id": "baby-buddy" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "baby-buddy", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8000, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + }, + { + "name": "secret_key", + "type": "str", + "title": "Geheime sleutel", + "default": "", + "required": true, + "description": "Willekeurige waarde voor sessieversleuteling; gebruik de dobbelsteenknop", + "config": { + "placeholder": "willekeurige-lange-string" + } + }, + { + "name": "allowed_hosts", + "type": "str", + "title": "Toegestane hostnamen", + "default": "*", + "required": true, + "description": "Bijvoorbeeld babybuddy.thuis.nl; * staat alles toe" + } + ] + } + ] +} diff --git a/apps/bambuddy/files/compose.yaml b/apps/bambuddy/files/compose.yaml new file mode 100644 index 0000000..79a59e8 --- /dev/null +++ b/apps/bambuddy/files/compose.yaml @@ -0,0 +1,18 @@ +services: + << service_name >>: + image: ghcr.io/maziggy/bambuddy:latest + container_name: << service_name >> + ports: + - "<< port >>:8000" + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + - PORT=8000 + volumes: + - << data_dir >>/<< service_name >>/data:/app/data + - << data_dir >>/<< service_name >>/logs:/app/logs + cap_add: + - NET_BIND_SERVICE + stop_grace_period: 30s + restart: unless-stopped diff --git a/apps/bambuddy/template.json b/apps/bambuddy/template.json new file mode 100644 index 0000000..16f13de --- /dev/null +++ b/apps/bambuddy/template.json @@ -0,0 +1,69 @@ +{ + "kind": "compose", + "metadata": { + "name": "Bambuddy", + "description": "Zelfgehost bedieningspaneel voor Bambu Lab-printers, van één A1 tot een hele printerfarm. Zonder cloud.", + "tags": [ + "3d-printen", + "bambu-lab", + "printerbeheer" + ], + "icon": { + "provider": "selfhst", + "id": "bambuddy" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "bambuddy", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8011, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + } + ] + } + ] +} diff --git a/apps/bookstack/files/compose.yaml b/apps/bookstack/files/compose.yaml new file mode 100644 index 0000000..aaa7e09 --- /dev/null +++ b/apps/bookstack/files/compose.yaml @@ -0,0 +1,36 @@ +services: + << service_name >>: + image: lscr.io/linuxserver/bookstack:latest + container_name: << service_name >> + ports: + - "<< port >>:80" + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + - APP_URL=<< app_url >> + - DB_HOST=<< service_name >>-db + - DB_PORT=3306 + - DB_USERNAME=bookstack + - DB_PASSWORD=<< db_password >> + - DB_DATABASE=bookstack + volumes: + - << data_dir >>/<< service_name >>/config:/config + depends_on: + - << service_name >>-db + restart: unless-stopped + + << service_name >>-db: + image: lscr.io/linuxserver/mariadb:latest + container_name: << service_name >>-db + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + - MYSQL_ROOT_PASSWORD=<< db_password >> + - MYSQL_DATABASE=bookstack + - MYSQL_USER=bookstack + - MYSQL_PASSWORD=<< db_password >> + volumes: + - << data_dir >>/<< service_name >>/db:/config + restart: unless-stopped diff --git a/apps/bookstack/template.json b/apps/bookstack/template.json new file mode 100644 index 0000000..4eaab7d --- /dev/null +++ b/apps/bookstack/template.json @@ -0,0 +1,88 @@ +{ + "kind": "compose", + "metadata": { + "name": "BookStack", + "description": "Wiki voor documentatie, geordend in boeken, hoofdstukken en pagina's. Prettig voor handleidingen thuis.", + "tags": [ + "documentatie", + "wiki", + "kennis" + ], + "icon": { + "provider": "selfhst", + "id": "bookstack" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "bookstack", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 6875, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + }, + { + "name": "db_password", + "type": "str", + "title": "Database wachtwoord", + "default": "", + "required": true, + "description": "Gebruik de dobbelsteenknop voor een willekeurige waarde", + "config": { + "placeholder": "sterk-wachtwoord" + } + }, + { + "name": "app_url", + "type": "str", + "title": "Basis-URL", + "default": "http://localhost:6875", + "required": true, + "description": "Het adres waarop je BookStack benadert; links kloppen anders niet" + } + ] + } + ] +} diff --git a/apps/calibre-web/files/compose.yaml b/apps/calibre-web/files/compose.yaml new file mode 100644 index 0000000..a55ede0 --- /dev/null +++ b/apps/calibre-web/files/compose.yaml @@ -0,0 +1,14 @@ +services: + << service_name >>: + image: lscr.io/linuxserver/calibre-web:latest + container_name: << service_name >> + ports: + - "<< port >>:8083" + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + volumes: + - << data_dir >>/<< service_name >>/config:/config + - << books_dir >>:/books + restart: unless-stopped diff --git a/apps/calibre-web/template.json b/apps/calibre-web/template.json new file mode 100644 index 0000000..cbb0a36 --- /dev/null +++ b/apps/calibre-web/template.json @@ -0,0 +1,77 @@ +{ + "kind": "compose", + "metadata": { + "name": "Calibre-Web", + "description": "Webinterface voor je Calibre-bibliotheek: bladeren, lezen en naar je e-reader sturen.", + "tags": [ + "media", + "boeken", + "ebooks" + ], + "icon": { + "provider": "selfhst", + "id": "calibre-web" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "calibre-web", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8083, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + }, + { + "name": "books_dir", + "type": "str", + "title": "Calibre-bibliotheek", + "default": "/opt/serverup/media/books", + "required": true, + "description": "Map met je bestaande metadata.db van Calibre" + } + ] + } + ] +} diff --git a/apps/duplicati/files/compose.yaml b/apps/duplicati/files/compose.yaml new file mode 100644 index 0000000..15f0903 --- /dev/null +++ b/apps/duplicati/files/compose.yaml @@ -0,0 +1,15 @@ +services: + << service_name >>: + image: lscr.io/linuxserver/duplicati:latest + container_name: << service_name >> + ports: + - "<< port >>:8200" + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + volumes: + - << data_dir >>/<< service_name >>/config:/config + - << source_dir >>:/source:ro + - << backup_dir >>:/backups + restart: unless-stopped diff --git a/apps/duplicati/template.json b/apps/duplicati/template.json new file mode 100644 index 0000000..af55536 --- /dev/null +++ b/apps/duplicati/template.json @@ -0,0 +1,83 @@ +{ + "kind": "compose", + "metadata": { + "name": "Duplicati", + "description": "Versleutelde back-ups naar een NAS, externe schijf of cloudopslag, met een planner.", + "tags": [ + "backup", + "opslag", + "beheer" + ], + "icon": { + "provider": "selfhst", + "id": "duplicati" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "duplicati", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8200, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + }, + { + "name": "source_dir", + "type": "str", + "title": "Map om te back-uppen", + "default": "/opt/serverup", + "required": true + }, + { + "name": "backup_dir", + "type": "str", + "title": "Bestemming", + "default": "/opt/serverup/backups", + "required": true + } + ] + } + ] +} diff --git a/apps/esphome/files/compose.yaml b/apps/esphome/files/compose.yaml new file mode 100644 index 0000000..8ed9a23 --- /dev/null +++ b/apps/esphome/files/compose.yaml @@ -0,0 +1,12 @@ +services: + << service_name >>: + image: ghcr.io/esphome/esphome:stable + container_name: << service_name >> + ports: + - "<< port >>:6052" + environment: + - TZ=<< timezone >> + volumes: + - << data_dir >>/<< service_name >>/config:/config + - /etc/localtime:/etc/localtime:ro + restart: unless-stopped diff --git a/apps/esphome/template.json b/apps/esphome/template.json new file mode 100644 index 0000000..73cad3d --- /dev/null +++ b/apps/esphome/template.json @@ -0,0 +1,54 @@ +{ + "kind": "compose", + "metadata": { + "name": "ESPHome", + "description": "Bouw en beheer firmware voor ESP32/ESP8266-sensoren via een webinterface. Levert kant-en-klare Home Assistant-integratie.", + "tags": [ + "domotica", + "esp32", + "diy" + ], + "icon": { + "provider": "selfhst", + "id": "esphome" + }, + "version": { + "name": "stable" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "esphome", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 6052, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + } + ] + } + ] +} diff --git a/apps/firefly-iii/files/compose.yaml b/apps/firefly-iii/files/compose.yaml new file mode 100644 index 0000000..7d5c49c --- /dev/null +++ b/apps/firefly-iii/files/compose.yaml @@ -0,0 +1,34 @@ +services: + << service_name >>: + image: fireflyiii/core:latest + container_name: << service_name >> + ports: + - "<< port >>:8080" + environment: + - TZ=<< timezone >> + - APP_KEY=<< app_key >> + - DB_CONNECTION=pgsql + - DB_HOST=<< service_name >>-db + - DB_PORT=5432 + - DB_DATABASE=firefly + - DB_USERNAME=firefly + - DB_PASSWORD=<< db_password >> + volumes: + - << data_dir >>/<< service_name >>/upload:/var/www/html/storage/upload + depends_on: + - << service_name >>-db + restart: unless-stopped + + << service_name >>-db: + image: postgres:16-alpine + container_name: << service_name >>-db + environment: + - POSTGRES_DB=firefly + - POSTGRES_USER=firefly + - POSTGRES_PASSWORD=<< db_password >> + volumes: + - << service_name >>_pgdata:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + << service_name >>_pgdata: diff --git a/apps/firefly-iii/template.json b/apps/firefly-iii/template.json new file mode 100644 index 0000000..f0dab55 --- /dev/null +++ b/apps/firefly-iii/template.json @@ -0,0 +1,76 @@ +{ + "kind": "compose", + "metadata": { + "name": "Firefly III", + "description": "Huishoudboekje met budgetten, rekeningen en rapportages. Kan bankafschriften importeren.", + "tags": [ + "financien", + "huishouden", + "budget" + ], + "icon": { + "provider": "selfhst", + "id": "firefly-iii" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "firefly-iii", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8093, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "db_password", + "type": "str", + "title": "Database wachtwoord", + "default": "", + "required": true, + "description": "Gebruik de dobbelsteenknop voor een willekeurige waarde", + "config": { + "placeholder": "sterk-wachtwoord" + } + }, + { + "name": "app_key", + "type": "str", + "title": "App-sleutel", + "default": "", + "required": true, + "description": "Exact 32 tekens; gebruik de dobbelsteenknop en kort zo nodig in", + "config": { + "placeholder": "32-tekens-lange-sleutel" + } + } + ] + } + ] +} diff --git a/apps/freshrss/files/compose.yaml b/apps/freshrss/files/compose.yaml new file mode 100644 index 0000000..f9bb928 --- /dev/null +++ b/apps/freshrss/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: freshrss/freshrss:latest + container_name: << service_name >> + ports: + - "<< port >>:80" + environment: + - TZ=<< timezone >> + - CRON_MIN=*/20 + volumes: + - << data_dir >>/<< service_name >>/data:/var/www/FreshRSS/data + - << data_dir >>/<< service_name >>/extensions:/var/www/FreshRSS/extensions + restart: unless-stopped diff --git a/apps/freshrss/template.json b/apps/freshrss/template.json new file mode 100644 index 0000000..b82ec11 --- /dev/null +++ b/apps/freshrss/template.json @@ -0,0 +1,69 @@ +{ + "kind": "compose", + "metadata": { + "name": "FreshRSS", + "description": "RSS-lezer die je nieuws en blogs op één plek verzamelt, zonder algoritme.", + "tags": [ + "nieuws", + "rss", + "lezen" + ], + "icon": { + "provider": "selfhst", + "id": "freshrss" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "freshrss", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8082, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + } + ] + } + ] +} diff --git a/apps/frigate/files/compose.yaml b/apps/frigate/files/compose.yaml new file mode 100644 index 0000000..0f830f3 --- /dev/null +++ b/apps/frigate/files/compose.yaml @@ -0,0 +1,20 @@ +services: + << service_name >>: + image: ghcr.io/blakeblackshear/frigate:stable + container_name: << service_name >> + shm_size: "<< shm_size >>" + ports: + - "<< port >>:8971" + - "<< rtsp_port >>:8554" + environment: + - TZ=<< timezone >> + volumes: + - << data_dir >>/<< service_name >>/config:/config + - << data_dir >>/<< service_name >>/media:/media/frigate + - /etc/localtime:/etc/localtime:ro + # Opnamebuffer in geheugen; voorkomt slijtage van je SSD + - type: tmpfs + target: /tmp/cache + tmpfs: + size: 1000000000 + restart: unless-stopped diff --git a/apps/frigate/template.json b/apps/frigate/template.json new file mode 100644 index 0000000..00ca1b4 --- /dev/null +++ b/apps/frigate/template.json @@ -0,0 +1,69 @@ +{ + "kind": "compose", + "metadata": { + "name": "Frigate NVR", + "description": "Camerabewaking met objectherkenning die lokaal draait. Werkt samen met Home Assistant en MQTT.", + "tags": [ + "camera", + "nvr", + "domotica" + ], + "icon": { + "provider": "selfhst", + "id": "frigate" + }, + "version": { + "name": "stable" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "frigate", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8971, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "rtsp_port", + "type": "int", + "title": "RTSP poort", + "default": 8554, + "required": true + }, + { + "name": "shm_size", + "type": "str", + "title": "Gedeeld geheugen", + "default": "128mb", + "required": true, + "description": "Verhoog dit bij meer dan twee camera's (ongeveer 64mb per camera)" + } + ] + } + ] +} diff --git a/apps/grocy/files/compose.yaml b/apps/grocy/files/compose.yaml new file mode 100644 index 0000000..05b9516 --- /dev/null +++ b/apps/grocy/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: lscr.io/linuxserver/grocy:latest + container_name: << service_name >> + ports: + - "<< port >>:80" + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + volumes: + - << data_dir >>/<< service_name >>/config:/config + restart: unless-stopped diff --git a/apps/grocy/template.json b/apps/grocy/template.json new file mode 100644 index 0000000..9b5ee2c --- /dev/null +++ b/apps/grocy/template.json @@ -0,0 +1,69 @@ +{ + "kind": "compose", + "metadata": { + "name": "Grocy", + "description": "Voorraadbeheer voor je huishouden: boodschappen, houdbaarheidsdata, klusjes en recepten.", + "tags": [ + "huishouden", + "voorraad", + "gezin" + ], + "icon": { + "provider": "selfhst", + "id": "grocy" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "grocy", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 9283, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + } + ] + } + ] +} diff --git a/apps/homebridge/files/compose.yaml b/apps/homebridge/files/compose.yaml new file mode 100644 index 0000000..85a9bb4 --- /dev/null +++ b/apps/homebridge/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: homebridge/homebridge:latest + container_name: << service_name >> + # HomeKit gebruikt mDNS voor het vinden van apparaten; dat werkt alleen + # betrouwbaar met het netwerk van de host, of met een eigen IP-adres. + network_mode: host + environment: + - TZ=<< timezone >> + - HOMEBRIDGE_CONFIG_UI_PORT=<< port >> + volumes: + - << data_dir >>/<< service_name >>:/homebridge + restart: unless-stopped diff --git a/apps/homebridge/template.json b/apps/homebridge/template.json new file mode 100644 index 0000000..8f7d5e7 --- /dev/null +++ b/apps/homebridge/template.json @@ -0,0 +1,54 @@ +{ + "kind": "compose", + "metadata": { + "name": "Homebridge", + "description": "Maakt apparaten die geen HomeKit ondersteunen alsnog zichtbaar in de Woning-app van Apple.", + "tags": [ + "domotica", + "homekit", + "apple" + ], + "icon": { + "provider": "selfhst", + "id": "homebridge" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "homebridge", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8581, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + } + ] + } + ] +} diff --git a/apps/homepage/files/compose.yaml b/apps/homepage/files/compose.yaml new file mode 100644 index 0000000..80b5a25 --- /dev/null +++ b/apps/homepage/files/compose.yaml @@ -0,0 +1,14 @@ +services: + << service_name >>: + image: ghcr.io/gethomepage/homepage:latest + container_name: << service_name >> + ports: + - "<< port >>:3000" + environment: + - TZ=<< timezone >> + - HOMEPAGE_ALLOWED_HOSTS=<< allowed_hosts >> + volumes: + - << data_dir >>/<< service_name >>/config:/app/config + # Alleen-lezen socket voor de Docker-widget die containers toont + - /var/run/docker.sock:/var/run/docker.sock:ro + restart: unless-stopped diff --git a/apps/homepage/template.json b/apps/homepage/template.json new file mode 100644 index 0000000..bef6048 --- /dev/null +++ b/apps/homepage/template.json @@ -0,0 +1,62 @@ +{ + "kind": "compose", + "metadata": { + "name": "Homepage", + "description": "Startpagina met tegels voor al je diensten, inclusief live status en systeemgegevens.", + "tags": [ + "dashboard", + "startpagina", + "overzicht" + ], + "icon": { + "provider": "selfhst", + "id": "homepage" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "homepage", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 3002, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "allowed_hosts", + "type": "str", + "title": "Toegestane hostnamen", + "default": "localhost:3001", + "required": true, + "description": "Komma-gescheiden; anders weigert Homepage het verzoek" + } + ] + } + ] +} diff --git a/apps/jellyseerr/files/compose.yaml b/apps/jellyseerr/files/compose.yaml new file mode 100644 index 0000000..fdccca4 --- /dev/null +++ b/apps/jellyseerr/files/compose.yaml @@ -0,0 +1,12 @@ +services: + << service_name >>: + image: fallenbagel/jellyseerr:latest + container_name: << service_name >> + ports: + - "<< port >>:5055" + environment: + - TZ=<< timezone >> + - LOG_LEVEL=info + volumes: + - << data_dir >>/<< service_name >>/config:/app/config + restart: unless-stopped diff --git a/apps/jellyseerr/template.json b/apps/jellyseerr/template.json new file mode 100644 index 0000000..31e516e --- /dev/null +++ b/apps/jellyseerr/template.json @@ -0,0 +1,54 @@ +{ + "kind": "compose", + "metadata": { + "name": "Jellyseerr", + "description": "Verzoeken voor films en series, gekoppeld aan Jellyfin of Plex en de *arr-stack.", + "tags": [ + "media", + "verzoeken", + "films" + ], + "icon": { + "provider": "selfhst", + "id": "jellyseerr" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "jellyseerr", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 5055, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + } + ] + } + ] +} diff --git a/apps/kavita/files/compose.yaml b/apps/kavita/files/compose.yaml new file mode 100644 index 0000000..740396a --- /dev/null +++ b/apps/kavita/files/compose.yaml @@ -0,0 +1,14 @@ +services: + << service_name >>: + image: lscr.io/linuxserver/kavita:latest + container_name: << service_name >> + ports: + - "<< port >>:5000" + environment: + - TZ=<< timezone >> + - PUID=<< puid >> + - PGID=<< pgid >> + volumes: + - << data_dir >>/<< service_name >>/config:/config + - << library_dir >>:/library + restart: unless-stopped diff --git a/apps/kavita/template.json b/apps/kavita/template.json new file mode 100644 index 0000000..43d180b --- /dev/null +++ b/apps/kavita/template.json @@ -0,0 +1,76 @@ +{ + "kind": "compose", + "metadata": { + "name": "Kavita", + "description": "Leesserver voor strips, manga en e-books, met leesvoortgang per gebruiker.", + "tags": [ + "media", + "boeken", + "strips" + ], + "icon": { + "provider": "selfhst", + "id": "kavita" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "kavita", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 5001, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "puid", + "type": "int", + "title": "Gebruikers-ID (PUID)", + "default": 1000, + "required": true, + "description": "Bepaalt de eigenaar van de bestanden; `id -u` op je host" + }, + { + "name": "pgid", + "type": "int", + "title": "Groeps-ID (PGID)", + "default": 1000, + "required": true + }, + { + "name": "library_dir", + "type": "str", + "title": "Bibliotheekmap", + "default": "/opt/serverup/media/books", + "required": true + } + ] + } + ] +} diff --git a/apps/mosquitto/files/compose.yaml b/apps/mosquitto/files/compose.yaml new file mode 100644 index 0000000..5b3c6d9 --- /dev/null +++ b/apps/mosquitto/files/compose.yaml @@ -0,0 +1,14 @@ +services: + << service_name >>: + image: eclipse-mosquitto:2 + container_name: << service_name >> + ports: + - "<< port >>:1883" + - "<< websocket_port >>:9001" + environment: + - TZ=<< timezone >> + volumes: + - << data_dir >>/<< service_name >>/config:/mosquitto/config + - << data_dir >>/<< service_name >>/data:/mosquitto/data + - << data_dir >>/<< service_name >>/log:/mosquitto/log + restart: unless-stopped diff --git a/apps/mosquitto/template.json b/apps/mosquitto/template.json new file mode 100644 index 0000000..9644f16 --- /dev/null +++ b/apps/mosquitto/template.json @@ -0,0 +1,61 @@ +{ + "kind": "compose", + "metadata": { + "name": "Mosquitto MQTT", + "description": "Lichtgewicht MQTT-broker. De centrale berichtenbus waar Zigbee2MQTT, ESPHome en Home Assistant op praten.", + "tags": [ + "domotica", + "mqtt", + "infrastructuur" + ], + "icon": { + "provider": "selfhst", + "id": "eclipse-mosquitto" + }, + "version": { + "name": "2" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "mosquitto", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "MQTT poort", + "default": 1883, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "websocket_port", + "type": "int", + "title": "WebSocket poort", + "default": 9001, + "required": true + } + ] + } + ] +} diff --git a/apps/navidrome/files/compose.yaml b/apps/navidrome/files/compose.yaml new file mode 100644 index 0000000..72dd207 --- /dev/null +++ b/apps/navidrome/files/compose.yaml @@ -0,0 +1,14 @@ +services: + << service_name >>: + image: deluan/navidrome:latest + container_name: << service_name >> + ports: + - "<< port >>:4533" + environment: + - TZ=<< timezone >> + - ND_SCANSCHEDULE=<< scan_schedule >> + - ND_LOGLEVEL=info + volumes: + - << music_dir >>:/music:ro + - << data_dir >>/<< service_name >>/data:/data + restart: unless-stopped diff --git a/apps/navidrome/template.json b/apps/navidrome/template.json new file mode 100644 index 0000000..4a634f6 --- /dev/null +++ b/apps/navidrome/template.json @@ -0,0 +1,69 @@ +{ + "kind": "compose", + "metadata": { + "name": "Navidrome", + "description": "Muziekstreaming vanaf je eigen collectie. Werkt met alle Subsonic-apps.", + "tags": [ + "media", + "muziek", + "streaming" + ], + "icon": { + "provider": "selfhst", + "id": "navidrome" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "navidrome", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 4533, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "music_dir", + "type": "str", + "title": "Muziekmap", + "default": "/opt/serverup/media/music", + "required": true + }, + { + "name": "scan_schedule", + "type": "str", + "title": "Scaninterval", + "default": "@every 1h", + "required": true, + "description": "Hoe vaak de collectie opnieuw wordt ingelezen" + } + ] + } + ] +} diff --git a/apps/node-red/files/compose.yaml b/apps/node-red/files/compose.yaml new file mode 100644 index 0000000..7af8ad4 --- /dev/null +++ b/apps/node-red/files/compose.yaml @@ -0,0 +1,11 @@ +services: + << service_name >>: + image: nodered/node-red:latest + container_name: << service_name >> + ports: + - "<< port >>:1880" + environment: + - TZ=<< timezone >> + volumes: + - << data_dir >>/<< service_name >>/data:/data + restart: unless-stopped diff --git a/apps/node-red/template.json b/apps/node-red/template.json new file mode 100644 index 0000000..8ad2373 --- /dev/null +++ b/apps/node-red/template.json @@ -0,0 +1,54 @@ +{ + "kind": "compose", + "metadata": { + "name": "Node-RED", + "description": "Visueel automatiseringen bouwen door blokken aan elkaar te knopen. Populair naast Home Assistant voor complexere logica.", + "tags": [ + "domotica", + "automatisering", + "low-code" + ], + "icon": { + "provider": "selfhst", + "id": "node-red" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "node-red", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 1880, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + } + ] + } + ] +} diff --git a/apps/octoprint/files/compose.yaml b/apps/octoprint/files/compose.yaml new file mode 100644 index 0000000..406d9c3 --- /dev/null +++ b/apps/octoprint/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: octoprint/octoprint:latest + container_name: << service_name >> + ports: + - "<< port >>:80" + environment: + - TZ=<< timezone >> + volumes: + - << data_dir >>/<< service_name >>:/octoprint + devices: + - << usb_device >>:/dev/ttyACM0 + restart: unless-stopped diff --git a/apps/octoprint/template.json b/apps/octoprint/template.json new file mode 100644 index 0000000..88ebba3 --- /dev/null +++ b/apps/octoprint/template.json @@ -0,0 +1,61 @@ +{ + "kind": "compose", + "metadata": { + "name": "OctoPrint", + "description": "Bedien en volg een 3D-printer via het netwerk, met webcam en plug-ins.", + "tags": [ + "3d-printen", + "printerbeheer" + ], + "icon": { + "provider": "selfhst", + "id": "octoprint" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "octoprint", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8087, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "usb_device", + "type": "str", + "title": "Printer (USB)", + "default": "/dev/ttyACM0", + "required": true, + "description": "Seriële poort van je printer" + } + ] + } + ] +} diff --git a/apps/scrypted/files/compose.yaml b/apps/scrypted/files/compose.yaml new file mode 100644 index 0000000..2a8abd5 --- /dev/null +++ b/apps/scrypted/files/compose.yaml @@ -0,0 +1,12 @@ +services: + << service_name >>: + image: ghcr.io/koush/scrypted:latest + container_name: << service_name >> + # Camera-ontdekking en HomeKit vereisen mDNS op het lokale netwerk. + network_mode: host + environment: + - TZ=<< timezone >> + - SCRYPTED_SECURE_PORT=<< port >> + volumes: + - << data_dir >>/<< service_name >>:/server/volume + restart: unless-stopped diff --git a/apps/scrypted/template.json b/apps/scrypted/template.json new file mode 100644 index 0000000..773cc67 --- /dev/null +++ b/apps/scrypted/template.json @@ -0,0 +1,54 @@ +{ + "kind": "compose", + "metadata": { + "name": "Scrypted", + "description": "Koppelt camera's aan HomeKit, Google Home en Alexa, met opname en bewegingsdetectie.", + "tags": [ + "camera", + "domotica", + "nvr" + ], + "icon": { + "provider": "selfhst", + "id": "scrypted" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "scrypted", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "HTTPS poort", + "default": 10443, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + } + ] + } + ] +} diff --git a/apps/searxng/files/compose.yaml b/apps/searxng/files/compose.yaml new file mode 100644 index 0000000..a13df3d --- /dev/null +++ b/apps/searxng/files/compose.yaml @@ -0,0 +1,19 @@ +services: + << service_name >>: + image: searxng/searxng:latest + container_name: << service_name >> + ports: + - "<< port >>:8080" + environment: + - TZ=<< timezone >> + - SEARXNG_BASE_URL=<< base_url >> + - SEARXNG_SECRET=<< secret_key >> + volumes: + - << data_dir >>/<< service_name >>/config:/etc/searxng + cap_drop: + - ALL + cap_add: + - CHOWN + - SETGID + - SETUID + restart: unless-stopped diff --git a/apps/searxng/template.json b/apps/searxng/template.json new file mode 100644 index 0000000..4281f8e --- /dev/null +++ b/apps/searxng/template.json @@ -0,0 +1,72 @@ +{ + "kind": "compose", + "metadata": { + "name": "SearXNG", + "description": "Eigen zoekmachine die resultaten van andere zoekmachines samenvoegt, zonder je te volgen.", + "tags": [ + "privacy", + "zoeken", + "web" + ], + "icon": { + "provider": "selfhst", + "id": "searxng" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "searxng", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8095, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "base_url", + "type": "str", + "title": "Basis-URL", + "default": "http://localhost:8095/", + "required": true + }, + { + "name": "secret_key", + "type": "str", + "title": "Geheime sleutel", + "default": "", + "required": true, + "description": "Willekeurige waarde; gebruik de dobbelsteenknop", + "config": { + "placeholder": "sterk-wachtwoord" + } + } + ] + } + ] +} diff --git a/apps/spoolman/files/compose.yaml b/apps/spoolman/files/compose.yaml new file mode 100644 index 0000000..82e9581 --- /dev/null +++ b/apps/spoolman/files/compose.yaml @@ -0,0 +1,11 @@ +services: + << service_name >>: + image: ghcr.io/donkie/spoolman:latest + container_name: << service_name >> + ports: + - "<< port >>:8000" + environment: + - TZ=<< timezone >> + volumes: + - << data_dir >>/<< service_name >>/data:/home/app/.local/share/spoolman + restart: unless-stopped diff --git a/apps/spoolman/template.json b/apps/spoolman/template.json new file mode 100644 index 0000000..3549b95 --- /dev/null +++ b/apps/spoolman/template.json @@ -0,0 +1,53 @@ +{ + "kind": "compose", + "metadata": { + "name": "Spoolman", + "description": "Houdt bij hoeveel filament je nog hebt en wat elke print verbruikt. Koppelt aan OctoPrint, Klipper en Bambuddy.", + "tags": [ + "3d-printen", + "voorraad" + ], + "icon": { + "provider": "selfhst", + "id": "spoolman" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "spoolman", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 7912, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + } + ] + } + ] +} diff --git a/apps/tandoor/files/compose.yaml b/apps/tandoor/files/compose.yaml new file mode 100644 index 0000000..3f5aee5 --- /dev/null +++ b/apps/tandoor/files/compose.yaml @@ -0,0 +1,34 @@ +services: + << service_name >>: + image: ghcr.io/tandoorrecipes/recipes:latest + container_name: << service_name >> + ports: + - "<< port >>:8080" + environment: + - TZ=<< timezone >> + - SECRET_KEY=<< secret_key >> + - DB_ENGINE=django.db.backends.postgresql + - POSTGRES_HOST=<< service_name >>-db + - POSTGRES_DB=tandoor + - POSTGRES_USER=tandoor + - POSTGRES_PASSWORD=<< db_password >> + volumes: + - << data_dir >>/<< service_name >>/staticfiles:/opt/recipes/staticfiles + - << data_dir >>/<< service_name >>/mediafiles:/opt/recipes/mediafiles + depends_on: + - << service_name >>-db + restart: unless-stopped + + << service_name >>-db: + image: postgres:16-alpine + container_name: << service_name >>-db + environment: + - POSTGRES_DB=tandoor + - POSTGRES_USER=tandoor + - POSTGRES_PASSWORD=<< db_password >> + volumes: + - << service_name >>_pgdata:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + << service_name >>_pgdata: diff --git a/apps/tandoor/template.json b/apps/tandoor/template.json new file mode 100644 index 0000000..30bcde4 --- /dev/null +++ b/apps/tandoor/template.json @@ -0,0 +1,76 @@ +{ + "kind": "compose", + "metadata": { + "name": "Tandoor Recipes", + "description": "Receptenboek met boodschappenlijst en maaltijdplanner. Importeert recepten van websites.", + "tags": [ + "huishouden", + "recepten", + "gezin" + ], + "icon": { + "provider": "selfhst", + "id": "tandoor" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "tandoor", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8092, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "db_password", + "type": "str", + "title": "Database wachtwoord", + "default": "", + "required": true, + "description": "Gebruik de dobbelsteenknop voor een willekeurige waarde", + "config": { + "placeholder": "sterk-wachtwoord" + } + }, + { + "name": "secret_key", + "type": "str", + "title": "Geheime sleutel", + "default": "", + "required": true, + "description": "Willekeurige waarde voor sessieversleuteling", + "config": { + "placeholder": "willekeurige-lange-string" + } + } + ] + } + ] +} diff --git a/apps/technitium-dns/files/compose.yaml b/apps/technitium-dns/files/compose.yaml new file mode 100644 index 0000000..46d3a1b --- /dev/null +++ b/apps/technitium-dns/files/compose.yaml @@ -0,0 +1,16 @@ +services: + << service_name >>: + image: technitium/dns-server:latest + container_name: << service_name >> + ports: + - "<< port >>:5380" + - "53:53/udp" + - "53:53/tcp" + environment: + - TZ=<< timezone >> + - DNS_SERVER_ADMIN_PASSWORD=<< admin_password >> + volumes: + - << data_dir >>/<< service_name >>/config:/etc/dns + sysctls: + - net.ipv4.ip_local_port_range=1024 65000 + restart: unless-stopped diff --git a/apps/technitium-dns/template.json b/apps/technitium-dns/template.json new file mode 100644 index 0000000..c149dc9 --- /dev/null +++ b/apps/technitium-dns/template.json @@ -0,0 +1,65 @@ +{ + "kind": "compose", + "metadata": { + "name": "Technitium DNS", + "description": "Eigen DNS-server met advertentieblokkering, DNS-over-HTTPS en gedetailleerde statistieken.", + "tags": [ + "netwerk", + "dns", + "privacy" + ], + "icon": { + "provider": "selfhst", + "id": "technitium-dns-server" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "technitium-dns", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 5380, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "admin_password", + "type": "str", + "title": "Beheerderswachtwoord", + "default": "", + "required": true, + "description": "Wachtwoord voor de webinterface", + "config": { + "placeholder": "sterk-wachtwoord" + } + } + ] + } + ] +} diff --git a/apps/wallabag/files/compose.yaml b/apps/wallabag/files/compose.yaml new file mode 100644 index 0000000..a0d33d8 --- /dev/null +++ b/apps/wallabag/files/compose.yaml @@ -0,0 +1,34 @@ +services: + << service_name >>: + image: wallabag/wallabag:latest + container_name: << service_name >> + ports: + - "<< port >>:80" + environment: + - TZ=<< timezone >> + - SYMFONY__ENV__DATABASE_DRIVER=pdo_pgsql + - SYMFONY__ENV__DATABASE_HOST=<< service_name >>-db + - SYMFONY__ENV__DATABASE_PORT=5432 + - SYMFONY__ENV__DATABASE_NAME=wallabag + - SYMFONY__ENV__DATABASE_USER=wallabag + - SYMFONY__ENV__DATABASE_PASSWORD=<< db_password >> + - SYMFONY__ENV__DOMAIN_NAME=<< app_url >> + volumes: + - << data_dir >>/<< service_name >>/images:/var/www/wallabag/web/assets/images + depends_on: + - << service_name >>-db + restart: unless-stopped + + << service_name >>-db: + image: postgres:16-alpine + container_name: << service_name >>-db + environment: + - POSTGRES_DB=wallabag + - POSTGRES_USER=wallabag + - POSTGRES_PASSWORD=<< db_password >> + volumes: + - << service_name >>_pgdata:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + << service_name >>_pgdata: diff --git a/apps/wallabag/template.json b/apps/wallabag/template.json new file mode 100644 index 0000000..df040e6 --- /dev/null +++ b/apps/wallabag/template.json @@ -0,0 +1,72 @@ +{ + "kind": "compose", + "metadata": { + "name": "Wallabag", + "description": "Artikelen bewaren om later te lezen, zonder reclame en advertentietrackers. Alternatief voor Pocket.", + "tags": [ + "lezen", + "artikelen", + "archief" + ], + "icon": { + "provider": "selfhst", + "id": "wallabag" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "wallabag", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8094, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "db_password", + "type": "str", + "title": "Database wachtwoord", + "default": "", + "required": true, + "description": "Gebruik de dobbelsteenknop voor een willekeurige waarde", + "config": { + "placeholder": "sterk-wachtwoord" + } + }, + { + "name": "app_url", + "type": "str", + "title": "Basis-URL", + "default": "http://localhost:8094", + "required": true + } + ] + } + ] +} diff --git a/apps/watchtower/files/compose.yaml b/apps/watchtower/files/compose.yaml new file mode 100644 index 0000000..cea377e --- /dev/null +++ b/apps/watchtower/files/compose.yaml @@ -0,0 +1,13 @@ +services: + << service_name >>: + image: containrrr/watchtower:latest + container_name: << service_name >> + environment: + - TZ=<< timezone >> + - WATCHTOWER_SCHEDULE=<< schedule >> + - WATCHTOWER_CLEANUP=<< cleanup | lower >> + # Alleen containers met het label com.centurylinklabs.watchtower.enable=true + - WATCHTOWER_LABEL_ENABLE=true + volumes: + - /var/run/docker.sock:/var/run/docker.sock + restart: unless-stopped diff --git a/apps/watchtower/template.json b/apps/watchtower/template.json new file mode 100644 index 0000000..cde684e --- /dev/null +++ b/apps/watchtower/template.json @@ -0,0 +1,55 @@ +{ + "kind": "compose", + "metadata": { + "name": "Watchtower", + "description": "Werkt containers automatisch bij zodra er een nieuw image is. Let op: Server Up beheert stacks zelf, gebruik dit alleen voor containers daarbuiten.", + "tags": [ + "beheer", + "updates", + "onderhoud" + ], + "icon": { + "provider": "selfhst", + "id": "watchtower" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "watchtower", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "schedule", + "type": "str", + "title": "Planning (cron)", + "default": "0 0 4 * * *", + "required": true, + "description": "Zes velden, seconden eerst. Standaard elke nacht om 4 uur." + }, + { + "name": "cleanup", + "type": "bool", + "title": "Oude images opruimen", + "default": true, + "description": "Verwijdert het vorige image na een update" + } + ] + } + ] +} diff --git a/apps/wg-easy/files/compose.yaml b/apps/wg-easy/files/compose.yaml new file mode 100644 index 0000000..6fa3f00 --- /dev/null +++ b/apps/wg-easy/files/compose.yaml @@ -0,0 +1,21 @@ +services: + << service_name >>: + image: ghcr.io/wg-easy/wg-easy:latest + container_name: << service_name >> + ports: + - "<< vpn_port >>:51820/udp" + - "<< port >>:51821/tcp" + environment: + - TZ=<< timezone >> + - WG_HOST=<< host >> + - PASSWORD_HASH=<< password_hash >> + - WG_PORT=<< vpn_port >> + volumes: + - << data_dir >>/<< service_name >>/config:/etc/wireguard + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 + restart: unless-stopped diff --git a/apps/wg-easy/template.json b/apps/wg-easy/template.json new file mode 100644 index 0000000..62105aa --- /dev/null +++ b/apps/wg-easy/template.json @@ -0,0 +1,80 @@ +{ + "kind": "compose", + "metadata": { + "name": "WireGuard Easy", + "description": "VPN naar je eigen netwerk, met een webinterface om apparaten toe te voegen via een QR-code.", + "tags": [ + "vpn", + "netwerk", + "beveiliging" + ], + "icon": { + "provider": "selfhst", + "id": "wg-easy" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "wg-easy", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 51821, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "vpn_port", + "type": "int", + "title": "VPN poort (UDP)", + "default": 51820, + "required": true + }, + { + "name": "host", + "type": "str", + "title": "Publiek adres", + "default": "vpn.example.com", + "required": true, + "description": "Het adres of IP waarop je van buitenaf binnenkomt" + }, + { + "name": "password_hash", + "type": "str", + "title": "Wachtwoordhash beheerder", + "default": "", + "required": true, + "description": "Bcrypt-hash van je wachtwoord; zie de documentatie van wg-easy", + "config": { + "placeholder": "sterk-wachtwoord" + } + } + ] + } + ] +} diff --git a/apps/zigbee2mqtt/files/compose.yaml b/apps/zigbee2mqtt/files/compose.yaml new file mode 100644 index 0000000..83848d6 --- /dev/null +++ b/apps/zigbee2mqtt/files/compose.yaml @@ -0,0 +1,15 @@ +services: + << service_name >>: + image: ghcr.io/koenkk/zigbee2mqtt:latest + container_name: << service_name >> + ports: + - "<< port >>:8080" + environment: + - TZ=<< timezone >> + - ZIGBEE2MQTT_CONFIG_MQTT_SERVER=<< mqtt_server >> + volumes: + - << data_dir >>/<< service_name >>/data:/app/data + - /run/udev:/run/udev:ro + devices: + - << usb_device >>:/dev/ttyACM0 + restart: unless-stopped diff --git a/apps/zigbee2mqtt/template.json b/apps/zigbee2mqtt/template.json new file mode 100644 index 0000000..e2953d3 --- /dev/null +++ b/apps/zigbee2mqtt/template.json @@ -0,0 +1,70 @@ +{ + "kind": "compose", + "metadata": { + "name": "Zigbee2MQTT", + "description": "Koppelt Zigbee-apparaten aan MQTT, zonder de hub van de fabrikant. Werkt met Home Assistant.", + "tags": [ + "domotica", + "zigbee", + "mqtt" + ], + "icon": { + "provider": "selfhst", + "id": "zigbee2mqtt" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "zigbee2mqtt", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8088, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "usb_device", + "type": "str", + "title": "USB-adapter", + "default": "/dev/ttyACM0", + "required": true, + "description": "Pad naar je Zigbee-stick; vind hem met `ls -l /dev/serial/by-id/`" + }, + { + "name": "mqtt_server", + "type": "str", + "title": "MQTT-server", + "default": "mqtt://mosquitto:1883", + "required": true, + "description": "Adres van je MQTT-broker (zie de Mosquitto-app)" + } + ] + } + ] +} diff --git a/apps/zwave-js-ui/files/compose.yaml b/apps/zwave-js-ui/files/compose.yaml new file mode 100644 index 0000000..143fa6c --- /dev/null +++ b/apps/zwave-js-ui/files/compose.yaml @@ -0,0 +1,14 @@ +services: + << service_name >>: + image: zwavejs/zwave-js-ui:latest + container_name: << service_name >> + ports: + - "<< port >>:8091" + - "<< ws_port >>:3000" + environment: + - TZ=<< timezone >> + volumes: + - << data_dir >>/<< service_name >>/store:/usr/src/app/store + devices: + - << usb_device >>:/dev/zwave + restart: unless-stopped diff --git a/apps/zwave-js-ui/template.json b/apps/zwave-js-ui/template.json new file mode 100644 index 0000000..d8ccc54 --- /dev/null +++ b/apps/zwave-js-ui/template.json @@ -0,0 +1,70 @@ +{ + "kind": "compose", + "metadata": { + "name": "Z-Wave JS UI", + "description": "Beheer je Z-Wave-netwerk en koppel het aan Home Assistant of MQTT.", + "tags": [ + "domotica", + "zwave", + "mqtt" + ], + "icon": { + "provider": "selfhst", + "id": "zwave-js-ui" + }, + "version": { + "name": "latest" + } + }, + "variables": [ + { + "title": "Algemeen", + "items": [ + { + "name": "service_name", + "type": "str", + "title": "Servicenaam", + "default": "zwave-js-ui", + "required": true + }, + { + "name": "port", + "type": "int", + "title": "Web poort", + "default": 8091, + "required": true + }, + { + "name": "data_dir", + "type": "str", + "title": "Data directory", + "default": "/opt/serverup/appdata", + "required": true + }, + { + "name": "timezone", + "type": "str", + "title": "Tijdzone", + "default": "Europe/Amsterdam", + "required": true + }, + { + "name": "usb_device", + "type": "str", + "title": "USB-adapter", + "default": "/dev/ttyUSB0", + "required": true, + "description": "Pad naar je Z-Wave-stick" + }, + { + "name": "ws_port", + "type": "int", + "title": "WebSocket poort", + "default": 3003, + "required": true, + "description": "Poort waarop Home Assistant verbinding maakt" + } + ] + } + ] +} diff --git a/server-up/core/boilerplates.py b/server-up/core/boilerplates.py index ffc8353..d5ce5ae 100644 --- a/server-up/core/boilerplates.py +++ b/server-up/core/boilerplates.py @@ -257,6 +257,21 @@ def _tidy(text: str) -> str: _KEY_RE = re.compile(r"^(\s*)([A-Za-z_][\w\-]*)\s*:\s*$") +# Alleen deze compose-sleutels worden opgeruimd als ze leeg achterblijven. Dat +# zijn de sleutels die een `<% if %>`-blok leeg kan maken. +# +# De beperking is wezenlijk: een named volume wordt gedeclareerd als een kale +# sleutel zonder waarde (` immich_pgdata:`), en zonder deze lijst werd die als +# "lege mapping" opgeruimd. Daarna viel ook het bovenliggende `volumes:` weg, +# en verwees de compose naar een volume dat nergens meer gedeclareerd stond — +# waarop docker compose weigert te starten. +_OPRUIMBARE_SLEUTELS = frozenset({ + "ports", "environment", "volumes", "depends_on", "networks", "labels", + "devices", "cap_add", "cap_drop", "sysctls", "extra_hosts", "dns", + "expose", "profiles", "tmpfs", "secrets", "configs", "command", + "entrypoint", "healthcheck", "deploy", "links", "env_file", "build", +}) + def _drop_empty_mappings(text: str) -> str: """Verwijder ´keys´ die op een lege regel of een gelijk/minder-indent sibling @@ -269,6 +284,8 @@ def _drop_empty_mappings(text: str) -> str: m = _KEY_RE.match(line) if not m: continue + if m.group(2) not in _OPRUIMBARE_SLEUTELS: + continue indent = len(m.group(1)) # Zoek de eerste niet-lege, niet-commentaar regel hierna j = i + 1 diff --git a/tests/test_apps.py b/tests/test_apps.py new file mode 100644 index 0000000..18940ca --- /dev/null +++ b/tests/test_apps.py @@ -0,0 +1,122 @@ +"""Elke app in apps/ moet renderen naar geldige, startbare compose-YAML. + +Deze suite draait over de echte templates, niet over voorbeelden. Zo valt een +kapot template op vóórdat iemand het probeert te installeren. +""" +import sys +import tempfile +from pathlib import Path + +import pytest +import yaml + +WORTEL = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(WORTEL / "server-up")) + +from core import boilerplates as bp + +APPS = sorted(d for d in (WORTEL / "apps").iterdir() if d.is_dir()) +NAMEN = [d.name for d in APPS] + + +def _render(d: Path, tmp: Path) -> dict: + velden = bp.fields(d) + waarden = {f["name"]: f["default"] for f in velden if f.get("default") is not None} + waarden.setdefault("service_name", d.name) + # Verplichte velden zonder default zijn geheimen; die vult de gebruiker in. + for f in velden: + if f.get("required") and not waarden.get(f["name"]): + waarden[f["name"]] = "testwaarde12345678901234567890ab" + bp.render_to_dir(d, tmp, waarden) + return yaml.safe_load((tmp / "docker-compose.yml").read_text()) + + +def test_er_zijn_apps(): + assert len(APPS) >= 60 + + +@pytest.mark.parametrize("app", APPS, ids=NAMEN) +def test_app_is_geldige_boilerplate(app): + assert bp.is_boilerplate(app), "template.json of files/ ontbreekt" + meta = bp.metadata(app) + assert meta["name"], "naam ontbreekt in template.json" + assert meta["description"], "omschrijving ontbreekt" + assert meta["tags"], "tags ontbreken" + assert any(f["name"] == "service_name" for f in bp.fields(app)), \ + "service_name-variabele ontbreekt" + + +@pytest.mark.parametrize("app", APPS, ids=NAMEN) +def test_app_rendert_naar_geldige_compose(app): + with tempfile.TemporaryDirectory() as t: + doc = _render(app, Path(t)) + assert isinstance(doc, dict) and doc.get("services"), "geen services" + for naam, svc in doc["services"].items(): + assert isinstance(svc, dict), f"{naam} is geen mapping" + assert svc.get("image") or svc.get("build"), f"{naam}: geen image of build" + + +@pytest.mark.parametrize("app", APPS, ids=NAMEN) +def test_named_volumes_zijn_gedeclareerd(app): + """Named volumes moeten in het top-level volumes-blok staan. + + _drop_empty_mappings ruimde dat blok weg omdat een volumedeclaratie een + kale sleutel zonder waarde is. Zes apps (immich, ghost, miniflux, + paperless-ngx, unifi-network, vikunja) renderden daardoor naar een compose + die docker weigert met "refers to undefined volume". + """ + with tempfile.TemporaryDirectory() as t: + doc = _render(app, Path(t)) + gedeclareerd = set((doc.get("volumes") or {}).keys()) + for naam, svc in doc["services"].items(): + for v in svc.get("volumes") or []: + if isinstance(v, str) and not v.startswith(("/", ".")): + bron = v.split(":")[0] + assert bron in gedeclareerd, \ + f"{naam}: volume '{bron}' wordt gebruikt maar niet gedeclareerd" + + +@pytest.mark.parametrize("app", APPS, ids=NAMEN) +def test_geen_onvervangen_variabelen(app): + """Een typefout in een variabelenaam laat << … >> in het resultaat staan.""" + with tempfile.TemporaryDirectory() as t: + tmp = Path(t) + _render(app, tmp) + tekst = (tmp / "docker-compose.yml").read_text() + assert "<<" not in tekst and ">>" not in tekst, "onvervangen variabele" + + +# Poorten die meerdere apps bewust delen: alternatieven voor dezelfde taak, +# die je nooit tegelijk draait. Alles daarbuiten hoort uniek te zijn. +GEDEELDE_POORTEN = { + 80: {"nginx-proxy-manager", "traefik"}, # reverse proxies + 443: {"nginx-proxy-manager", "traefik"}, + 53: {"adguard-home", "pihole"}, # dns-blokkers + 5055: {"arr-stack", "jellyseerr"}, # zelfde app + # Hieronder: botsingen die er al waren vóór deze testsuite. De + # vrije-poortsuggestie bij installeren vangt ze op, dus ze zijn hinderlijk + # maar niet kapot. Nieuwe apps horen hier niet bij te komen. + 3000: {"adguard-home", "forgejo", "grafana"}, + 8080: {"miniflux", "nextcloud-aio", "pihole", "qbittorrent", + "stirling-pdf", "traefik", "unifi-network"}, + 9000: {"mealie", "portainer"}, +} + + +def test_poorten_botsen_niet_tussen_apps(): + """Twee apps met dezelfde standaardpoort geven bij installeren meteen een + conflict. De vrije-poortsuggestie vangt dat op, maar standaardwaarden die + al botsen zijn onnodig verwarrend.""" + per_poort: dict[int, list[str]] = {} + for app in APPS: + for f in bp.fields(app): + if f.get("type") == "int" and "port" in f["name"].lower(): + d = f.get("default") + if isinstance(d, int): + per_poort.setdefault(d, []).append(f"{app.name}:{f['name']}") + botsingen = {} + for poort, gebruikers in per_poort.items(): + apps = {g.split(":")[0] for g in gebruikers} + if len(apps) > 1 and not apps <= GEDEELDE_POORTEN.get(poort, set()): + botsingen[poort] = sorted(apps) + assert not botsingen, f"onbedoeld dubbele standaardpoorten: {botsingen}" diff --git a/tests/test_boilerplates.py b/tests/test_boilerplates.py index fa0d98c..b09026b 100644 --- a/tests/test_boilerplates.py +++ b/tests/test_boilerplates.py @@ -72,9 +72,11 @@ def test_sandbox_geeft_geen_python_internals_prijs(tmp_path): src = _maak_boilerplate(tmp_path, "x: << service_name.__class__ >>\n") bp.render_to_dir(src, tmp_path / "out", {"service_name": "web"}) tekst = (tmp_path / "out" / "docker-compose.yml").read_text() - # `x:` blijft leeg achter en wordt daarna door _drop_empty_mappings opgeruimd. - assert tekst.strip() == "" + # De waarde blijft leeg; `x:` zelf blijft staan omdat alleen bekende + # compose-sleutels worden opgeruimd (zie _OPRUIMBARE_SLEUTELS). + assert tekst.strip() == "x:" assert "class" not in tekst + assert "object" not in tekst def test_sandbox_laat_aanroep_van_internals_falen(tmp_path):