# Eigen apps toevoegen Een app in Server Up is een map met twee dingen: `template.json` beschrijft wat de app is en welke vragen je bij het installeren krijgt, en `files/compose.yaml` is het compose-bestand met plaatshouders erin. ``` apps/mijn-app/ ├── template.json └── files/ └── compose.yaml ``` Zet die map in `apps/`, of in je eigen git-repo die je toevoegt bij **Instellingen → App Store → Repository**. --- ## Het kortste voorbeeld dat werkt **`template.json`** ```json { "kind": "compose", "metadata": { "name": "Mijn App", "description": "Wat de app doet, in één zin.", "tags": ["voorbeeld"], "categories": ["productiviteit"], "icon": {"provider": "selfhst", "id": "mijn-app"}, "version": {"name": "latest"} }, "variables": [ { "title": "Algemeen", "items": [ {"name": "service_name", "type": "str", "title": "Servicenaam", "default": "mijn-app", "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} ] } ] } ``` **`files/compose.yaml`** ```yaml services: << service_name >>: image: voorbeeld/mijn-app:latest container_name: << service_name >> ports: - "<< port >>:8080" environment: - TZ=<< timezone >> volumes: - << data_dir >>/<< service_name >>/config:/config restart: unless-stopped ``` Dat is genoeg. De rest van dit document beschrijft wat je er nog meer mee kunt. --- ## Compose-syntaxis De sjablonen gebruiken Jinja met afwijkende haakjes, zodat ze niet botsen met de `${VAR}` van docker compose zelf: | | | |---|---| | `<< variabele >>` | waarde invullen | | `<%- if variabele %>` … `<%- endif %>` | blok alleen opnemen als de waarde waar is | | `<< waarde \| lower >>` | filters van Jinja werken gewoon | Het renderen gebeurt in een sandbox: je kunt niet bij Python-internals. ### Named volumes moeten gedeclareerd worden Gebruik je een named volume, zet het dan óók in het top-level `volumes:`-blok. Vergeet je dat, dan weigert docker compose met *"refers to undefined volume"*: ```yaml services: << service_name >>-db: image: postgres:16-alpine volumes: - << service_name >>_pgdata:/var/lib/postgresql/data volumes: << service_name >>_pgdata: ``` De testsuite controleert dit voor elke app. --- ## Metadata | Veld | Betekenis | |---|---| | `name` | Naam zoals getoond in de catalogus | | `description` | Eén zin: wat is het, en waarvoor gebruik je het | | `tags` | Vrije trefwoorden, gebruikt voor zoeken en filteren | | `categories` | Eén of meer categorie-ids (zie onder). Ontbreken ze, dan worden ze afgeleid uit de tags | | `icon` | `{"provider": "selfhst", "id": "…"}` of `dashboard-icons`. Bestaat het icoon niet, dan valt de interface terug op een emoji | | `version` | Alleen ter informatie in de catalogus | ### Categorieën Een app mag in meerdere categorieën zitten. De ids: `media` · `fotos` · `downloaden` · `domotica` · `3d-printen` · `netwerk` · `beveiliging` · `opslag` · `documenten` · `productiviteit` · `communicatie` · `financien` · `gezin` · `monitoring` · `ontwikkeling` · `ai` · `beheer` · `overig` Elke categorie heeft een vaste kleur die overal in de interface terugkomt. De definities staan in `server-up/core/categories.py`. --- ## Velden Elk item onder `items` wordt één vraag in het installatieformulier. ```json { "name": "port", "type": "int", "title": "Web poort", "default": 8200, "required": true, "description": "Korte tekst onder het veld", "help": "Langere uitleg, zichtbaar achter het info-icoon", "advanced": false, "config": {"placeholder": "bv. 8200", "options": ["a", "b"]} } ``` | Type | Wordt | |---|---| | `str` | tekstveld | | `int` | getalveld | | `bool` | aanvinkvakje | | `enum` | keuzelijst, met de waarden uit `config.options` | ### Vaste namen Deze namen worden speciaal behandeld: - **`service_name`** — verplicht. Wordt de containernaam en het voorvoegsel van alles in de stack. Wordt automatisch gevuld met de instantienaam. - **Velden met `port` in de naam en type `int`** — krijgen bij het installeren automatisch een vrij poortnummer voorgesteld als de standaard al bezet is. - **Velden met `token`, `password`, `secret` of `key` in de naam** — krijgen een knop die een willekeurige waarde genereert. ### Geavanceerde velden `"advanced": true` zet een veld achter het uitklapblok *Geavanceerde opties*. Bedoeld voor wat je meestal met rust laat: database-wachtwoorden, PUID/PGID, USB-paden, bewaartermijnen. Zet niet álles op geavanceerd — een test controleert dat er per app zichtbare velden overblijven. Je kunt het ook op een hele groep zetten: `{"title": "…", "advanced": true, …}`. ### Uitleg `description` staat als kleine tekst onder het veld. `help` is de langere uitleg achter het info-icoon; ontbreekt die, dan wordt `description` getoond. Gebruik `help` voor dingen die een gebruiker echt moet weten: ```json { "name": "downloads_dir", "type": "str", "title": "Downloadmap", "default": "/mnt/downloads", "help": "Zet deze map op hetzelfde bestandssysteem als je mediamap. Staan ze op verschillende volumes, dan kopieert elke voltooide download in plaats van te verplaatsen." } ``` ### Velden afhankelijk maken `needs` verbergt een veld tot een ander veld een bepaalde waarde heeft: ```json {"name": "smtp_host", "type": "str", "title": "SMTP-server", "needs": ["mail_enabled == true"]} ``` --- ## Apps aan elkaar koppelen Verwijst een veld naar een andere app, zet er dan `connect` op. De interface toont dan een keuzelijst met wat er al geïnstalleerd is, in plaats van een leeg tekstveld: ```json { "name": "mqtt_server", "type": "str", "title": "MQTT-server", "default": "mqtt://mosquitto:1883", "connect": {"app": "mosquitto", "scheme": "mqtt", "port": 1883}, "help": "Heb je de Mosquitto-app geïnstalleerd, kies die dan hier." } ``` | Sleutel | Betekenis | |---|---| | `app` | De mapnaam van de app waarnaar verwezen wordt (`apps/mosquitto`) | | `scheme` | Wat voor het adres komt: `http`, `mqtt`, … | | `port` | Poort binnen het gedeelde netwerk (de interne poort, niet die op de host) | | `path` | Optioneel pad achter het adres | Dat levert bijvoorbeeld `mqtt://mosquitto:1883` op. Staat de app er nog niet, dan meldt het veld dat en kun je alsnog handmatig invullen. **Let op de poort:** dit gaat over de poort *binnen* de container, niet de poort die op de host gepubliceerd is. De stacks zitten samen op een gedeeld netwerk en bereiken elkaar op containernaam. --- ## Onderdelen optioneel maken Voor stacks met meerdere componenten kun je per groep een schakelaar zetten: ```json { "title": "Bazarr — ondertitels", "toggle": "enable_bazarr", "toggle_default": true, "description": "Haalt automatisch ondertitels op", "items": [ {"name": "port_bazarr", "type": "int", "title": "Poort", "default": 6767} ] } ``` In het compose-bestand gebruik je die schakelaar als voorwaarde: ```yaml <%- if enable_bazarr %> << service_name >>-bazarr: image: lscr.io/linuxserver/bazarr:latest ports: - "<< port_bazarr >>:6767" <%- endif %> ``` `toggle_default` bepaalt of het onderdeel standaard aan staat. Zonder die sleutel staat hij aan. De ARR-stack (`apps/arr-stack`) gebruikt dit voor twintig onderdelen en is het beste voorbeeld om van af te kijken. --- ## Waar je op moet letten **Poorten.** Kies een standaardpoort die nog niet door een andere app gebruikt wordt. De testsuite controleert dat, en meldt welke botsen. Alternatieven voor dezelfde taak (twee reverse proxies, twee DNS-blokkers) mogen wel dezelfde poort delen; die staan in een allowlist in `tests/test_apps.py`. **`network_mode`.** Heeft een service `network_mode: host` of `network_mode: service:…`, dan kan hij niet aan het gedeelde netwerk hangen. Server Up slaat die services over, dus het werkt — maar zo'n app kan andere apps niet op naam bereiken. **Images.** Gebruik een image waarvan je zeker weet dat hij bestaat en welke architecturen hij ondersteunt. De testsuite controleert wel de vorm van je compose, maar haalt geen images op. --- ## Testen Elke app in `apps/` wordt automatisch meegenomen in de testsuite: ```bash python -m pytest tests/test_apps.py -q ``` Gecontroleerd wordt: geldige metadata, renderen naar geldige YAML, elke service heeft een `image` of `build`, named volumes zijn gedeclareerd, geen onvervangen `<< variabelen >>`, geen onbedoeld dubbele poorten, geldige categorieën, en dat `network_mode` niet botst met het gedeelde netwerk. Wil je alleen jouw app zien: ```bash python -m pytest tests/test_apps.py -q -k mijn-app ```