All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m13s
Defect (v0.7.00): add_shared_network() hing elke service aan het gedeelde
netwerk, ook services met network_mode. Docker compose weigert die combinatie
("declares mutually exclusive network_mode and networks"), waardoor Homebridge,
Scrypted, Beszel en de ARR-stack met Gluetun niet te installeren waren met de
standaardkeuze "Verbinden met andere apps". Die services worden nu overgeslagen;
blijft er niets over, dan ook geen netwerkblok. Test over alle 96 apps.
Instellingen wijzigen na installatie:
- De gemaakte keuzes worden bij installatie opgeslagen in .serverup.json
(values + format); zonder die waarden viel het formulier niet te heropenen.
- GET /api/stacks/<naam>/config geeft de opgeslagen waarden plus het actuele
veldschema; POST /reconfigure maakt een backup, rendert opnieuw, past netwerk
en eigen IP opnieuw toe, valideert en herstart. Mislukt het valideren, dan
wordt de backup teruggezet.
- Knop Instellingen op de stackkaart opent dezelfde modal in wijzigen-stand,
met een waarschuwing dat handmatige compose-wijzigingen verloren gaan.
- Stacks van voor deze versie: knop uit met uitleg.
Bestaande stacks koppelen:
- POST /api/stacks/<naam>/connect zet een bestaande stack op het gedeelde
netwerk, valideert en herstart; wordt de compose ongeldig, dan wordt de
wijziging teruggedraaid.
Audit-log:
- audit.query()/count() accepteren filters op bron, actie, status, periode en
vrije tekst; facets() levert de keuzelijsten. /api/audit ondersteunt die als
queryparameters plus doorbladeren. Filterbalk in de UI.
Documentatie:
- README.md (bestond niet), docs/apps-maken.md met het volledige
templateformaat, docs/README.md als index.
- Changelog-secties voor v0.5.44/45/46 aangevuld; release.yml haalt de notes
daaruit, dus een tag daarop gaf een lege release. Test die afdwingt dat het
huidige VERSION een sectie heeft.
941 tests groen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
305 lines
8.8 KiB
Markdown
305 lines
8.8 KiB
Markdown
# 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
|
|
```
|