Compare commits

..

28 commits

Author SHA1 Message Date
Ramon
2862f7844c v0.7.30-beta - installatiescript voor Linux
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 35s
Deploy server-up (prod) / deploy (push) Failing after 40s
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh

install.sh controleert het systeem, biedt aan Docker te installeren als dat
ontbreekt, kloont de repo (of pakt het tar-archief uit als git ontbreekt), maakt
.env aan, bouwt en start, wacht op /healthz en toont waar je terecht kunt met de
waarschuwing meteen een account aan te maken.

- Opties: --dir, --port, --bind, --branch, --token, --update, --uninstall,
  --yes, --dry-run.
- --update haalt nieuwe code op en herstart; .env, stacks en gegevens blijven.
- --uninstall stopt de container maar laat gegevens staan, en vertelt hoe je
  die alsnog opruimt.
- Gebruikt alleen sudo waar nodig: kun je zelf in de doelmap schrijven en
  docker aanroepen, dan blijft alles onder je eigen account.

Tijdens het testen gevonden en verholpen:
- `docker compose --project-directory` zoekt zonder -f het compose-bestand in de
  huidige map; bij `curl | sh` is dat je thuismap. Nu altijd -f erbij.
- De wachtlus gaf anderhalve minuut geen teken van leven; nu een punt per
  poging en 90 in plaats van 120 seconden.
- Bij --dry-run braken ontbrekende Docker-onderdelen de voorvertoning af; nu
  waarschuwingen zodat je de hele gang van zaken ziet.
- De versie-uitlezing uit /healthz tolereert nu ook json met spaties.

Documentatie: docs/installeren.md met alle opties, bijwerken, verwijderen en een
probleemoplostabel; README verwijst ernaar.

Tests: syntaxis onder sh/dash/bash, geen bashismen, --help noemt alle opties,
ongeldige invoer stopt, een proefdraai maakt niets aan, en de installatieregel
in README en docs is identiek.

958 tests groen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 19:30:45 +02:00
Ramon
2ba15418ab v0.7.20-beta - instellingen wijzigen, koppelen achteraf, defect uit v0.7.00
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
2026-07-27 15:43:49 +02:00
Ramon
ff1979ba51 v0.7.10-beta - categorieen met kleur in de app store en bij je apps
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m10s
- Nieuw core/categories.py: zeventien categorieen, elk met naam (nl/en), kleur
  en pictogram. Kleuren zijn middentonen die in licht en donker leesbaar zijn;
  de interface gebruikt ze als tekstkleur op een doorzichtige variant.
- Alle 96 apps ingedeeld via metadata.categories. Een app mag in meerdere
  categorieen zitten; 39 apps doen dat (Frigate onder domotica en media,
  Immich onder fotos en opslag, Kavita onder media en documenten).
- Apps uit externe repos geven geen categorieen op; die worden afgeleid uit
  tags en omschrijving, zodat ook zij een kleur en een plek in het filter
  krijgen.
- App store: gekleurde categoriebalk met aantallen, klikbare badges per app,
  werkt naast het bestaande zoekveld en de tagfilters.
- Stackslijst: dezelfde balk met alleen de categorieen die je draait, gekleurde
  rand per kaart en klikbare badges.
- Nieuw /api/categories geeft de definities met kleur, zodat interface en
  backend dezelfde kleur voor dezelfde categorie gebruiken. /api/stacks geeft
  de categorieen mee; een stack van voor deze versie valt terug op "overig"
  in plaats van kleurloos te blijven.
- Tests: elke app heeft geldige categorieen, kleuren zijn uniek, en er staat
  geen categorie in het filter zonder apps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 14:14:25 +02:00
Ramon
4450b3c171 v0.7.00-beta - apps koppelen, eenvoudiger instellen, geavanceerde opties verborgen
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m16s
Apps onderling verbinden:
- Elke stack draaide als eigen compose-project met een eigen netwerk, waardoor
  stacks elkaar niet op naam konden bereiken. Bij installeren staat nu
  "Verbinden met andere apps" aan: de stack komt op een gedeeld bridge-netwerk
  (SHARED_NETWORK, standaard "serverup") dat op aanvraag wordt aangemaakt.
- compose_transform.add_shared_network() hangt elke service eraan en laat
  bestaande netwerken staan, dus het werkt samen met een macvlan-adres.
- Nieuw /api/apps/connectable geeft de geinstalleerde apps met hun
  containernamen, zodat de installatiemodal kan laten kiezen.

Kiezen in plaats van typen:
- Een veld met "connect": {"app": "mosquitto", "scheme": "mqtt", "port": 1883}
  toont een keuzelijst met wat er al draait, met handmatig invullen als
  terugval. Toegepast op zigbee2mqtt -> mosquitto en open-webui -> ollama.
- Is de doel-app nog niet geinstalleerd, dan zegt het veld dat.

Uitleg per instelling:
- Info-icoon per veld opent een blok met de volledige uitleg plus de
  standaardwaarde. Uitgeschreven voor de instellingen die de meeste vragen
  oproepen (downloadmap vs mediamap, shm_size bij frigate, poort 80 bij caddy,
  PUID bij de arr-stack, GPU bij ollama, publiek adres bij wg-easy).

Geavanceerde opties:
- advanced: true op een veld of groep zet het achter een uitklapblok met
  teller. Toegepast op wachtwoorden, PUID/PGID, USB-paden en bewaartermijnen
  van 28 apps.
- Test bewaakt dat elke app zichtbare velden overhoudt en dat koppelvelden naar
  een bestaande app verwijzen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 13:52:17 +02:00
Ramon
146d7ae79d v0.6.20-beta - 31 apps erbij na een rondgang langs de verzamelsites
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m24s
Van 65 naar 96 apps. Selectie op basis van awesome-selfhosted, selfh.st,
Perfect Media Server en een paar overzichtslijsten van 2026, aangevuld met de
gaten in de bestaande collectie.

- Monitoring: prometheus, influxdb, netdata, beszel
- Proxy: caddy
- Notities/kennis: memos, hedgedoc, wikijs, excalidraw, karakeep
- Data: nocodb, baserow, metabase
- Identiteit: authelia, keycloak
- Media: komga, photoprism, metube, pinchflat
- Meldingen: ntfy
- Productiviteit: planka, focalboard, kimai
- Locatie/voertuig: owntracks, teslamate
- Hulpmiddelen: shlink, cyberchef, languagetool, kopia
- AI: ollama, open-webui

Prometheus ontbrak terwijl Grafana er al stond; Authelia en Keycloak sluiten
aan op de reverse-proxy-SSO die Server Up zelf ondersteunt.

De images van de nieuwere apps (beszel, karakeep, pinchflat) zijn nagetrokken
bij de bron in plaats van uit het hoofd opgeschreven. Van karakeep is de
officiele compose overgenomen inclusief de meilisearch- en chrome-containers.

Vijftien poortbotsingen met bestaande apps automatisch rechtgezet. Caddy deelt
80/443 met traefik en nginx-proxy-manager (alternatieven voor dezelfde taak) en
staat daarvoor in de allowlist.

705 tests groen; elke app rendert naar geldige compose-YAML met gedeclareerde
volumes en zonder onvervangen variabelen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 13:37:40 +02:00
Ramon
7ae62fe78c v0.6.10-beta - ARR-stack met een schakelaar per onderdeel
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m32s
De ARR-stack installeerde altijd dezelfde zeven containers. Nu kies je per
onderdeel uit twintig:

- Media: sonarr, radarr, lidarr, readarr, whisparr
- Indexers: prowlarr, jackett, flaresolverr, bazarr
- Downloaden: qbittorrent, sabnzbd, nzbget, gluetun (VPN)
- Aanvullend: jellyseerr, recyclarr, unpackerr, tdarr, autobrr, maintainerr,
  huntarr

Standaard aan: sonarr, radarr, prowlarr, bazarr, qbittorrent, jellyseerr.

Gluetun:
- Staat die aan, dan draaien de downloadclients in diens netwerknamespace
  zonder eigen poortmapping; anders lekt hun verkeer om de VPN heen. Hun
  webinterfaces komen op de poorten van de Gluetun-container.
- qBittorrent en SABnzbd luisteren allebei standaard op 8080 en botsten
  daardoor achter een gedeelde namespace. qBittorrent draait nu intern op 8090.

Groepsschakelaars werkten niet:
- Een `toggle` op een groep in template.json was zelf geen variabele en kreeg
  dus nooit een startwaarde, waardoor elk schakelbaar onderdeel standaard uit
  stond en een stack met schakelaars leeg binnenkwam. fields() geeft de
  schakelaar nu mee als synthetisch bool-veld met een instelbare
  standaardstand (toggle_default), en de UI toont hem alleen in de kop van de
  groep in plaats van er ook nog eens onder.

Tests: elk onderdeel levert precies een service op als het aan staat en geen
spoor als het uit staat, de downloadclients zitten achter de VPN zonder eigen
poorten, en de interne poorten achter Gluetun botsen niet. Poortbotsingen met
bestaande apps rechtgezet (8090 -> 8097, 8095 -> 8098); 6881 en 5055 blijven
gedeeld met de losse variant van dezelfde app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 09:46:01 +02:00
Ramon
b8c8e89fa0 v0.6.00-beta - 29 apps erbij, en named volumes die verdwenen
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m5s
Bij het toevoegen van de apps kwam een bestaande fout boven water.
_drop_empty_mappings ruimt sleutels op die een <% if %>-blok leeg achterlaat,
maar een named volume wordt gedeclareerd als een kale sleutel zonder waarde
(`  immich_pgdata:`). Die werd opgeruimd, waarna ook het bovenliggende
`volumes:` wegviel en de services verwezen naar een volume dat nergens meer
gedeclareerd stond. Immich, Ghost, Miniflux, Paperless-ngx, Unifi Network en
Vikunja waren daardoor niet installeerbaar: docker compose weigert met "refers
to undefined volume". Alleen bekende compose-sleutels worden nu opgeruimd.

29 nieuwe apps (teller op 65):
- Domotica: zigbee2mqtt, mosquitto, esphome, node-red, zwave-js-ui,
  homebridge, scrypted, frigate
- 3D-printen: bambuddy, octoprint, spoolman
- Gezin/huishouden: baby-buddy, grocy, tandoor, firefly-iii
- Media: audiobookshelf, navidrome, kavita, calibre-web, jellyseerr
- Kennis: bookstack, freshrss, wallabag
- Netwerk/beheer: wg-easy, homepage, searxng, duplicati, watchtower,
  technitium-dns

Nieuwe tests/test_apps.py rendert elke app met de echte engine en controleert
geldige YAML, image of build per service, gedeclareerde named volumes, geen
onvervangen variabelen en geen onbedoeld dubbele standaardpoorten. Die laatste
vond zeven botsingen tussen nieuwe en bestaande apps; rechtgezet. Logische
botsingen (reverse proxies op 80/443, dns-blokkers op 53) staan met uitleg in
een allowlist.

Het image van Bambuddy is nagetrokken op de bron
(github.com/maziggy/bambuddy): ghcr.io/maziggy/bambuddy, poort 8000.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 08:23:58 +02:00
Ramon
7f0d34cb1a v0.5.50-beta - containers tonen die niet bij een beheerde stack horen
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m5s
Het dashboard meldde 13 actieve containers en 17 images terwijl die nergens
terug te vinden waren. De tellers komen van `docker info` (de hele daemon), de
stacklijst toont alleen mappen in LIBRARY_DIR met een compose-bestand. Alles wat
buiten Server Up om gestart is telde dus wel mee maar was onzichtbaar.

- Nieuw /api/containers: alle containers op de host, met per container of hij
  bij een beheerde stack hoort (compose-project of werkmap onder LIBRARY_DIR).
- Blok "Overige containers" onder de stacklijst, met start/stop/herstart/logs.
- Dashboardteller "actief" vermeldt nu hoeveel daarvan via Server Up loopt.
- De eigen container is gemarkeerd en kan niet via dit blok gestopt worden;
  daarvoor blijft de herstartknop in de instellingen.
- Rollen: een viewer ziet de lijst maar mag niets bedienen.

Verholpen tijdens het bouwen: containers zonder poorten en zonder
compose-labels vielen uit de lijst als ze als laatste regel stonden, doordat
strip() de lege velden aan het eind wegnam en de regel daarna te weinig
kolommen leek te hebben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 07:57:35 +02:00
Ramon
34a48fb74a ci: deploy mag niet stranden op een .env van een andere gebruiker
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m30s
De stap "Instellingen en versie klaarzetten" faalde op `touch: cannot touch
'.env': Permission denied`. Het bestand was met sudo aangemaakt en dus van
root, terwijl de runner als een gewone gebruiker draait. Daarmee brak de hele
deploy af op iets dat geen blokkade hoort te zijn.

- SU_TAG wordt nu altijd via de omgeving aan `docker compose` meegegeven
  (export SU_TAG). Het wegschrijven in .env blijft de voorkeur, omdat het een
  handmatige `docker compose up` overleeft, maar het is niet meer nodig om te
  kunnen deployen.
- Is .env niet schrijfbaar, dan meldt de log wie de eigenaar is en welk
  chown-commando dat rechtzet, en loopt de deploy gewoon door.
- Het aanmaken vanuit .env.example faalt niet meer hard.

Positief neveneffect van de vorige fix: de log toont nu "Werkmap:
/opt/docker/server-up", dus de cd landt op de juiste plek.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 00:46:30 +02:00
Ramon
cb04c4a7ad v0.5.45-beta - wizard-knop toegevoegd in Instellingen
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 25s
De wizard was alleen te zien bij een verse installatie: zodra hij een keer was
afgerond, was er geen enkele manier meer om hem te openen. De vertaalsleutels
wizard_open en wizard_reset bestonden al, en /api/wizard/reset werkte ook, maar
de knop is nooit gebouwd - ook niet in v0.4.60.

- Blok "Setup Wizard" onder Instellingen met "Wizard openen" en "Opnieuw
  uitvoeren". Dat laatste zet WIZARD_DONE terug en opent hem meteen.
- openWizard() staat los van checkWizard(), zodat handmatig openen niet meer
  afhangt van of de wizard al gedraaid heeft. Stap wordt op 0 gezet.
- Sluitknop in de wizard zelf; die kon je eerder alleen verlaten door hem
  helemaal af te ronden.
- De staptitels lopen nu via t() in plaats van harde Nederlandse teksten.

De wizard-stappen zelf zijn end-to-end nagelopen tegen een draaiende instantie
(paths, git, sync, complete, reset): alle endpoints geven 200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 00:39:35 +02:00
Ramon
dea949a003 v0.5.44-beta - gedetecteerd netwerk verdween uit het formulier
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 22s
Zodra je een netwerk had toegevoegd, was het blok "gevonden op deze server" leeg
bij een volgende poging. Ik filterde subnetten eruit die al geconfigureerd
waren, met de gedachte "niet nog een keer voorstellen". Dat pakt verkeerd uit:
je kunt prima een tweede netwerk op hetzelfde subnet willen met een andere
range, en de gegevens zijn sowieso handig om te kunnen naslaan. Zonder dat blok
lijkt het formulier bovendien kapot.

- Gedetecteerde netwerken blijven altijd staan; ze krijgen een vlag
  'configured' en tonen 'al toegevoegd' in plaats van 'gebruiken'.
- Levert de detectie niets op zonder dat er een fout is, dan staat er nu een
  uitleg in plaats van een leeg blok.
- Regressietest die het blok controleert voor en na het aanmaken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 00:34:22 +02:00
Ramon
eb52c2ac41 ci: expliciete cd in plaats van working-directory, en de deploy-map loggen
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 23s
.env verschijnt niet in de deploy-map en BIND moet nog steeds in
docker-compose.yml worden aangepast. De stappen die .env aanmaken, VERSION
schrijven en compose draaien vertrouwden alle drie op 'working-directory:'.
Wordt dat door de runner niet toegepast, dan belanden die bestanden in de
werkmap van de runner en draait de container vanaf de compose daar - terwijl
de rsync (die een expliciet pad gebruikt) wel netjes naar /opt/docker/server-up
schrijft. Dat verklaart precies wat er gemeld wordt.

- Alle stappen doen nu 'cd "$DEPLOY_DIR"' in het script zelf.
- De run-log toont de werkmap, de inhoud van .env, de compose-projectmap
  volgens de draaiende container en de daadwerkelijke poortbinding. Daarmee is
  in de log te zien waar alles landt in plaats van het te moeten raden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 00:18:55 +02:00
Ramon
8da53aba6f changelog: weggevallen tekst herstellen (backticks door de shell opgegeten)
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m17s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 00:14:18 +02:00
Ramon
b30f83866f v0.5.43-beta - van-tot omrekenen naar een CIDR-blok
Some checks failed
Deploy server-up (dev) / deploy (push) Has been cancelled
"Ik wil van 10.0.20.200 tot .254" bestaat niet als CIDR-notatie: 55 adressen,
terwijl Docker alleen uitgelijnde blokken van een macht van twee accepteert.
Dat is niet iets wat een gebruiker zelf hoort uit te rekenen.

- Nieuwe range_options(): vertaalt een gewenst begin- en eindadres naar twee
  bruikbare blokken. "Ruim" is het kleinste blok dat de hele wens omvat,
  "krap" het grootste blok dat er volledig binnen valt. Met per optie hoeveel
  adressen erbij komen of ontbreken.
- Endpoint POST /api/networks/range en een uitklapbare van-tot-hulp onder het
  IP-range-veld; klikken op een optie vult het veld.
- Voor 10.0.20.200-254 levert dat 10.0.20.192/26 op: 62 adressen, .193 t/m .254.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 00:13:53 +02:00
Ramon
cbb232d63f v0.5.42-beta - netwerk toevoegen: twee blokkades weg
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m3s
Gemeld met twee screenshots: een foutmelding dat de IP-range buiten het subnet
lag terwijl 10.0.20.240/28 gewoon binnen 10.0.20.0/24 valt, en een geval zonder
melding waarbij aanmaken toch niet lukte. Twee losse oorzaken.

1. Verouderd beoordelingsantwoord
   De live beoordeling gaat af bij elke toetsaanslag. Tijdens het typen van
   /28 kom je langs /2, en 10.0.20.240/2 normaliseert naar 0.0.0.0/2 - buiten
   het subnet. Kwam dat antwoord na het goede binnen, dan bleef die fout staan
   en bleef de knop uit.
   - Volgnummer op de verzoeken; alleen het antwoord bij de laatste vraag telt.
   - Knop niet meer uitgeschakeld op clientstatus; de server beslist bij het
     opslaan, zodat een verouderde beoordeling nooit blokkeert.

2. CIDR met hostbits
   10.0.20.200/28 leest Python met strict=False als 10.0.20.192/28, maar Docker
   weigert het met "has host bits set". We valideerden soepel en stuurden de
   ruwe tekst door, dus de interface keurde iets goed dat bij het aanmaken
   alsnog strandde.
   - normalize() zet subnet en ip_range om naar canonieke vorm voor ze naar
     Docker gaan en voor ze worden opgeslagen.
   - Waarschuwing die toont wat er van de invoer gemaakt is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 00:06:12 +02:00
Ramon
923c8dabee ci: .env aanmaken vanuit .env.example bij de deploy
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 39s
BIND bleef niet staan. Oorzaak: docker-compose.yml staat in Git, dus rsync zet
dat bestand bij elke deploy terug; een wijziging daarin op de server houdt het
nooit. De juiste plek is .env ernaast, maar dat bestand bevatte na een deploy
alleen een kale SU_TAG-regel en was daarmee niet vindbaar.

- Bestaat er nog geen .env, dan zet de deploy hem op vanuit .env.example, met
  alle instellingen erin en uitleg per sleutel. Een bestaande .env blijft
  ongemoeid (rsync slaat hem al over sinds 6d5453e).
- De deploy logt nu welke instellingen actief zijn, zodat je in de run-log ziet
  wat er staat.
- Waarschuwing in docker-compose.yml zelf dat je BIND daar niet moet aanpassen
  maar in .env. Dat bestand wordt toch elke deploy teruggezet, dus de tip staat
  er altijd.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 23:57:34 +02:00
Ramon
4346fefe4d v0.5.41-beta - netwerk toevoegen liep vast tijdens het typen
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m10s
JSON-parsefout in de browser bij het toevoegen van een netwerk.

De live beoordeling die bij elke toetsaanslag afgaat, bouwde een lijst van alle
bruikbare adressen in het bereik. Tijdens het intypen van 192.168.1.0/24 is de
tussenstand 192.168.1.0/2 een geldig netwerk met 1.073.741.824 adressen; het
verzoek liep daarop vast en de browser kreeg geen JSON terug.

- review() rekent eerste adres, laatste adres en aantal nu uit via
  num_addresses in plaats van hosts() te doorlopen. Alle prefixes van /1 tot
  /32 antwoorden binnen 0,05 seconde.
- suggest_range() had hetzelfde probleem: die somde alle /28-blokken op, bij
  een /8 ruim een miljoen. Rekent nu van achteren naar voren, hoogstens
  zestien stappen.
- Waarschuwing bij meer dan 4096 adressen in het bereik; dat is vrijwel altijd
  een typefout in het prefix.
- Tests met /1 t/m /32, inclusief een vergelijking van _bruikbaar_bereik() met
  de uitkomst van hosts() uit de stdlib, en tijdslimieten zodat een
  herintroductie meteen opvalt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 23:42:56 +02:00
Ramon
866c5b8c1e .env.example toevoegen met alle instellingen erin
Some checks are pending
Deploy server-up (dev) / deploy (push) Waiting to run
Nergens stond bij elkaar welke sleutels in het .env van de deploy-map horen.
BIND kwam alleen in docs/beveiliging.md voor, SU_IMAGE en SU_TAG alleen in
docs/updates.md, en de rest nergens.

- .env.example met BIND, PORT, SU_TAG, SU_IMAGE, BASE_DIR, SU_HTTPS,
  SU_BOOT_REPOS en SU_DEBUG, elk met uitleg wanneer je ze nodig hebt.
- docs/beveiliging.md wijst er nu naar, met de concrete stappen om de interface
  van buiten de server bereikbaar te maken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 23:35:11 +02:00
Ramon
6d5453ed70 ci: .env van de deploy-map niet meer wissen bij elke deploy
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 22s
.env staat in .gitignore, dus de checkout bevat er geen. De rsync-stap draait
met --delete, waardoor /opt/docker/server-up/.env bij elke deploy verdween --
inclusief BIND, PORT en SU_IMAGE die de beheerder daar had gezet.

SU_TAG werd daarna wel opnieuw geschreven, dus dat viel niet op, maar een
BIND=0.0.0.0 raakte je elke run kwijt en daarmee de bereikbaarheid van de
interface van buiten de server.

--exclude='.env' toegevoegd in deploy.yml en deploy-prod.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 23:27:49 +02:00
Ramon
576ee17a61 Duidelijker maken waarom de updateknop uit staat bij een zelfgebouwd image
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 41s
Op een server die zelf bouwt (SU_IMAGE leeg, zoals de dev-server na een
deploy-run) draait het image als 'server-up:<versie>'. Zonder registry-pad valt
er niets op te halen, dus de knop staat uit. De melding verwees alleen naar
UPDATE_IMAGE, terwijl het juiste antwoord daar is: die server werkt al bij via
Git.

- status() geeft nu een 'mode' terug (registry / local-build / no-compose /
  no-container) en de melding bij local-build noemt beide routes: pushen naar
  de branch of de deploy-workflow starten, en als alternatief SU_IMAGE.
- De UI toont dat geval als informatie in plaats van als waarschuwing; het is
  de normale opzet, geen storing.
- docs/updates.md: tabel met de twee manieren van bijwerken en de waarschuwing
  om ze niet door elkaar te gebruiken op dezelfde server, omdat de
  deploy-workflow en de updateknop allebei SU_TAG in .env schrijven.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 23:23:30 +02:00
Ramon
a1fb5f5a8f v0.5.40-beta - netwerken instellen zonder uitzoekwerk
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m11s
- Automatische detectie van het hostnetwerk: interface, subnet, gateway en een
  voorgestelde vrije IP-range worden bovenaan het formulier getoond en vullen
  met een klik alles in.
- Detectie gebeurt via een kortstondige container in de netwerknamespace van de
  host, die de routetabel uitleest. Dat repareert meteen een fout: de vorige
  host_interfaces() las /sys/class/net binnen de container en toonde dus de
  bridge-interface van de container in plaats van de netwerkkaart van de host.
- Live beoordeling terwijl je typt (nieuwe endpoint /api/networks/review): laat
  zien hoeveel adressen je overhoudt en welke, en legt in gewone taal uit wat er
  niet klopt. Onderscheid tussen blokkerende fouten en waarschuwingen.
- Knop die een vrij blok voorstelt op basis van het subnet, met de gateway
  ontweken.
- Uitklapbare uitleg in het formulier over hoe subnet, gateway, IP-range en
  host-interface samenhangen, met voorbeeld.
- docs/netwerken.md uitgebreid met een schema van de adresverdeling, een tabel
  met veelgebruikte ranges, en de nadruk op het punt dat het instellen van de
  range in Server Up niets aan de router doet: daar moet het DHCP-bereik zelf
  verkleind worden.
- Bijgesteld tijdens het testen: een gateway binnen het subnet is de normale
  situatie en gold ten onrechte als fout wanneer er geen eigen range was
  opgegeven. Nu alleen een waarschuwing bij een expliciete range.
- 248 tests (21 nieuwe).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 23:16:52 +02:00
Ramon
342b2ee9e5 ci: tests in een container draaien in plaats van een venv op de runner
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m38s
De testsstap die ik in v0.5.00 toevoegde blokkeerde de deploy: de runner heeft
geen python3-venv, dus `python3 -m venv` faalde en alle volgende stappen werden
overgeslagen.

- Tests draaien nu in python:3.12-slim via docker, dat de runner sowieso heeft.
  Bijkomend voordeel: dezelfde Python-versie als waarop de app in productie
  draait, in plaats van wat er toevallig op de runner staat.
- PYTHONDONTWRITEBYTECODE en -p no:cacheprovider voorkomen dat de container
  root-eigen __pycache__/.pytest_cache in de werkmap achterlaat, wat de
  rsync-stap en volgende checkouts zou kunnen hinderen.
- Pip-cache in een named volume, zodat een herhaalde run niet opnieuw downloadt.

Toegepast in deploy.yml, deploy-prod.yml en build.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 22:34:14 +02:00
Ramon
f40fd39ed9 v0.5.30-beta - rollen, app-store-filter en twee planningsfouten
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 2s
Rollen per gebruiker:
- Drie rollen: beheerder (alles), operator (stacks, containers en backups) en
  alleen lezen. Afgedwongen in de before_request-guard, niet alleen in de UI.
- Eigen wachtwoord wijzigen mag iedereen; de laatste beheerder kan niet
  gedegradeerd of verwijderd worden.
- Proxy-gebruikers zonder eigen account krijgen AUTH.proxy_role; bestaat er wel
  een lokaal account met die naam, dan wint dat account.
- Accounts van voor deze versie hebben geen rol en gelden als beheerder.

Rechtenescalatie verholpen:
- set_password verving het hele gebruikersrecord, waardoor het role-veld
  wegviel. Een ontbrekende rol geldt als beheerder (nodig voor bestaande
  accounts), dus elke operator of viewer kon zichzelf tot beheerder promoveren
  door zijn eigen wachtwoord te wijzigen. Gevonden bij het naspelen van de
  rollenflow tegen een draaiende instantie.

Geplande backups draaiden nooit:
- De scheduler stempelde ook taken af die zichzelf hadden overgeslagen. De
  backup-taak bewaakt zelf het ingestelde uur, dus die werd afgestempeld op het
  moment van de eerste tick; 24 uur later viel het volgende moment op precies
  dat verkeerde tijdstip. Een taak stempelt nu alleen af bij True. De
  backup-taak draait op een kort interval en beslist zelf, zodat een gewijzigde
  planning meteen werkt. BACKUP_SCHEDULE_DAY toegevoegd voor wekelijks.

Verder:
- Zoekveld en tagfilters in de app store; repo's zonder treffers vallen weg.
- Opstartcontrole op de meegeleverde front-end-bestanden: mislukt het downloaden
  tijdens de image-build, dan laadde de UI zonder opmaak terwijl de server prima
  leek te draaien. Nu een duidelijke melding in het log.
- 227 tests (15 nieuwe voor rollen, 15 voor de scheduler).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 22:04:08 +02:00
Ramon
ae99df2fc7 v0.5.20-beta - complete backups en updates per app
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 6s
Backups (core/backups.py, core/scheduler.py):
- Terugzetten met een klik: stack stoppen, huidige map opzij, uitpakken,
  starten. Mislukt het uitpakken, dan wordt de oude situatie teruggeplaatst.
- Bewaarbeleid: aantal per stack en/of maximale leeftijd; de nieuwste backup
  van een stack blijft altijd staan.
- Geplande backups (dagelijks/wekelijks) via een eigen planner in de app, geen
  cron. Een gemiste ronde loopt bij de eerstvolgende gelegenheid alsnog.
- Automatisch een backup voor het bijwerken of verwijderen van een stack.
  Mislukt die, dan gaat de actie door - anders kun je een kapotte stack niet
  meer opruimen.
- Uitpakken met tarfile + filter="data": absolute paden en ..-ingangen worden
  geweigerd. Met een kaal `tar xzf` kon een geprepareerd archief buiten de
  stackmap schrijven.
- Twee bugs in de oude implementatie: de returncode van tar werd genegeerd
  (mislukte backup gold als succes) en backups binnen dezelfde seconde
  overschreven elkaar.

Updates per app (core/stackupdates.py, core/registry.py):
- Badge op de stackkaart als er een nieuwer image is; bijwerken doet de
  bestaande update-knop.
- Vergelijking via de Registry API v2 (Docker-Content-Digest) in plaats van
  `docker manifest inspect`: dat laatste geeft per platform een aparte digest
  terwijl RepoDigests de manifest-list-digest bevat, wat bij elk multi-arch
  image permanent "update beschikbaar" zou opleveren.
- Drie statussen: update / current / unknown. Lokaal gebouwd, nog niet gepulld
  of registry onbereikbaar geeft unknown en dus geen badge.
- Dagelijkse achtergrondcheck, resultaten 6 uur gecached.

Verder:
- docs/backups.md, met nadruk op wat er niet in een backup zit: de
  Docker-volumes met de eigenlijke appdata.
- Backup-instellingen onder Instellingen; backup-geschiedenis per stack.
- 197 tests groen (40 nieuwe).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 19:54:51 +02:00
Ramon
fe7abce95b Vertalingen voor de nieuwe schermen + SSO-instelscherm
- De schermen die in v0.5.x zijn toegevoegd (login/setup, Beveiliging,
  Netwerken, Updates, containerpaneel, netwerkkeuze bij installeren) gebruikten
  harde Nederlandse teksten terwijl de rest van de UI via t() loopt. Op Engels
  gaf dat een mengelmoes. 80 nieuwe sleutels toegevoegd aan nl.json en en.json
  en alle literals vervangen.
- t() ondersteunt nu plaatshouders: t('port_taken', {port: 8080, suggested:
  8081}). Zo blijven zinnen heel in plaats van in losse stukjes geknipt.
- Nieuw instelscherm voor reverse-proxy-SSO onder Instellingen -> Beveiliging:
  modus lokaal/proxy/beide, identiteitsheader en de lijst met vertrouwde
  proxy-adressen. De backend (PUT /api/auth/mode) bestond al maar was alleen
  met curl te bereiken. Bij het omschakelen naar 'proxy' vraagt de UI om
  bevestiging, want die modus schakelt de lokale login uit.
- Nieuw tests/test_i18n.py: sleutelpariteit tussen nl en en, geen lege
  waarden, gelijke plaatshouders in beide talen, en een controle dat de UI
  geen t()-sleutels gebruikt die nergens gedefinieerd zijn. Die laatste vond
  meteen een fout in de eerste opzet.
- 157 tests groen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 19:43:30 +02:00
Ramon
8ff5f4b1d3 v0.5.10-beta - update-systeem met kanalen en een-klik bijwerken
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 4s
- Update-kanalen stable/beta in plaats van het vinkje "pre-releases meenemen".
  Stable ziet alleen releases, beta ook pre-releases; een release telt hoger
  dan zijn eigen beta (0.5.10-beta1 < 0.5.10). UPDATE_INCLUDE_PRERELEASE
  migreert automatisch naar het beta-kanaal.
- Nieuw core/selfupdate.py: image uit de registry ophalen, SU_TAG wegschrijven
  en de eigen container laten hercreeren door een korte helper-container die op
  het nieuwe image draait (bevat de docker- en compose-CLI al). Een container
  kan zichzelf niet hercreeren, vandaar de helper.
- Deploy-map wordt uitgelezen uit de compose-labels van de eigen container.
  Ontbreken die, draait de container niet vanaf een registry-image of is de
  docker-socket er niet, dan meldt de UI waarom bijwerken niet kan.
- Vorige tag wordt onthouden; terugrolknop in de instellingen.
- Update-check een uur gecached, met geforceerde check via de knop; eerder deed
  elke paginalading een netwerkverzoek. Laatst-gecontroleerd zichtbaar.
- Registry-inloggegevens instelbaar; het token komt net als de git-tokens nooit
  terug via de API en leeg laten betekent ongewijzigd.
- docker-compose.yml gebruikt ${SU_IMAGE:-server-up}:${SU_TAG:-latest}; zonder
  SU_IMAGE blijft lokaal bouwen werken zoals voorheen.
- deploy-prod.yml bouwt en pusht het image in dezelfde job wanneer SU_IMAGE
  ingesteld is; build.yml is nu alleen handmatig. Bewust geen aparte
  build-workflow op dezelfde tag: bij een runner met een job tegelijk zou de
  deploy wachten op een build die zelf nog in de wachtrij staat.
- docs/updates.md: werking, complete Forgejo-instelling (registry, tokens,
  variables, secrets, runners), release-procedure voor stable en beta,
  terugrollen en probleemoplossing.
- Tests uitgebreid naar 148 (kanaallogica, cache, image-afleiding incl.
  registry-poortnummers, tag-validatie, .env-schrijven, tokenlek).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 15:13:18 +02:00
Ramon
8b29553a71 v0.5.00-beta - containerbeheer, eigen IP-adressen, veiliger installeren
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 3s
- Beheer per container: de stackkaart klapt uit naar de losse containers, met
  start/stop/herstart, logs en live CPU-/geheugengebruik per container. De
  containernaam wordt getoetst aan compose_ps van díé stack, zodat de route
  geen willekeurige container op de host kan raken.
- Stacks met een eigen IP-adres (macvlan/ipvlan): netwerkbeheer onder
  Instellingen → Netwerken, netwerkkeuze in de installatiemodal met voorstel
  voor het eerstvolgende vrije adres, en automatische omzetting van het
  gerenderde compose-bestand (poortmappings eruit, ipv4_address erin). De
  templates in apps/ blijven ongewijzigd.
- Toegekende IP's worden vastgehouden in .serverup.json en getoond op de
  stackkaart, zodat een volgende installatie ze niet opnieuw uitdeelt.
- Vrije poort voorstellen bij installeren: next_free_port() bestond al maar
  werd nergens gebruikt. Bezette poorten worden in de UI gemeld.
- Genereerknop voor velden die op een geheim wijzen (token/password/secret),
  lokaal gegenereerd via crypto.getRandomValues.
- Compose valideren met `docker compose config` vóór het wegschrijven, zowel
  bij de editor als na het renderen bij installatie. Ontbreekt de compose-CLI,
  dan blokkeert dat een installatie niet.
- Uitloggen in de zijbalk; gebruikersbeheer en wachtwoord wijzigen onder
  Instellingen → Beveiliging.
- docs/netwerken.md (incl. de shim-interface die de host nodig heeft om zijn
  eigen macvlan-containers te bereiken) en docs/beveiliging.md toegevoegd.
- CHANGELOG bijgewerkt; testsuite uitgebreid naar 112 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 14:51:32 +02:00
Ramon
916e54a229 v0.5.00-beta - authenticatie + beveiligingsfixes
Server Up beheerde de Docker-daemon als root zonder enige vorm van
authenticatie: elke /api/*-route was gelijk aan root-toegang op de host.

- Authenticatie toegevoegd (core/auth.py): lokale accounts met scrypt-hash,
  sessiecookie (HttpOnly, SameSite=Strict) en optionele trusted-proxy-header
  SSO die alleen vanaf geconfigureerde proxy-IP's wordt vertrouwd.
- before_request-guard schermt alle API-routes af; loginscherm en
  eerste-account-setup in de UI.
- CSRF-token verplicht op elke mutatie; GET-varianten van state-wijzigende
  routes verwijderd (o.a. /api/docker/restart was via <img> te triggeren).
- Path traversal in /api/store/install gedicht; gedeelde safe_name()-validatie
  voor stack-, instantie- en repo-namen.
- Git-tokens worden niet meer teruggegeven via /api/repos en /api/settings
  (has_token-vlag); settings-PUT wist een bestaand token niet meer en kan AUTH
  niet overschrijven.
- Git-URL's beperkt tot http(s)/ssh/scp-syntax; ext::-transport (voert een
  shell-commando uit) en file:// worden geweigerd.
- Boilerplate-templates renderen in een SandboxedEnvironment (SSTI).
- Automatisch syncen van repo's bij boot standaard uit (AUTO_SYNC_ON_BOOT),
  optionele commit-pinning per repo.
- Waitress in plaats van de Flask-ontwikkelserver, MAX_CONTENT_LENGTH,
  ProxyFix, en CSP/X-Frame-Options/nosniff/Referrer-Policy headers.
- Front-end libraries (Tailwind, Alpine, htmx) lokaal meegeleverd i.p.v. CDN;
  Google Fonts verwijderd. Werkt nu ook offline.
- SSH host-key-verificatie aan (accept-new + /data/known_hosts).
- Lichte /healthz voor de healthcheck i.p.v. `docker info`.
- config.json en secret.key met 0600-rechten.
- Poort standaard op 127.0.0.1 gebonden.
- Audit-log gebruikt één gedeelde SQLite-verbinding (fd-lek per job verholpen);
  joblogs afgekapt op 2000 regels.
- VERSION-bestand is de enige bron voor het versienummer.
- pytest-suite toegevoegd (74 tests) en als stap in beide deploy-workflows.
- fix-config.sh verwijderd (hardgecodeerd intern IP, overschreef config).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 14:40:05 +02:00
220 changed files with 18378 additions and 494 deletions

48
.env.example Normal file
View file

@ -0,0 +1,48 @@
# Instellingen van deze Server Up-installatie.
#
# Kopieer naar `.env` in dezelfde map als docker-compose.yml. Het bestand staat
# in .gitignore en wordt door de deploy-workflow met rust gelaten, dus wat je
# hier zet blijft staan bij een update.
#
# cp .env.example .env
# ── Bereikbaarheid ───────────────────────────────────────────────────────────
# Op welk adres de poort wordt gepubliceerd.
# 127.0.0.1 alleen vanaf de server zelf (standaard, veiligst)
# 0.0.0.0 vanaf het hele netwerk — doe dit alleen achter een reverse proxy
# met TLS, want Server Up beheert Docker als root
BIND=127.0.0.1
# Poort op de host.
PORT=5000
# ── Versie en bijwerken ──────────────────────────────────────────────────────
# Welke image-tag draait. De deploy-workflow schrijft deze waarde zelf; met de
# hand aanpassen kan om terug te rollen naar een eerdere versie.
#SU_TAG=0.5.40-beta
# Registry-image, zónder tag. Staat dit ingevuld, dan draait deze server vanaf
# een registry en werkt de knop "Nu bijwerken" in de interface. Laat het leeg
# als de server zelf bouwt na een push naar Git — dan is Git de update-route.
# Zie docs/updates.md.
#SU_IMAGE=git.example.com/bes-r/server-up
# ── Overig ───────────────────────────────────────────────────────────────────
# Waar de stacks, appdata en backups komen te staan. Dit pad wordt in de
# container op hetzelfde pad gemount, zodat compose-bestanden kloppen.
#BASE_DIR=/opt/serverup
# Zet op 1 zodra alles via HTTPS loopt: de sessiecookie krijgt dan de
# Secure-vlag.
#SU_HTTPS=1
# Extra app-repositories zonder de wizard, als JSON-array. Eenmalig ingelezen
# en daarna opgeslagen in config.json.
#SU_BOOT_REPOS=[{"id":"mijn-apps","name":"Mijn Apps","url":"https://…","branch":"main","subdir":"apps"}]
# Debug-endpoints (/api/debug/*) inschakelen. Alleen tijdelijk gebruiken:
# /api/debug/config toont de volledige configuratie.
#SU_DEBUG=1

View file

@ -1,52 +1,98 @@
# Forgejo Actions — bouw + push image NAAR DE REGISTRY (optioneel, handmatig)
# Forgejo Actions — bouw het image en push het naar de container-registry.
#
# UITGESCHAKELD voor automatische runs. Deze server bouwt zelf via
# deploy-prod.yml / deploy.yml (rsync + `docker compose build`), dus een
# registry-push is niet nodig. Stond dit op `push: tags: v*`, dan bleef de job
# hangen omdat hij een runner met label `docker` zoekt die niet bestaat —
# dat veroorzaakte de eindeloos "building" run in de Actions-tab.
# Dit image is wat de "Nu bijwerken"-knop in Server Up ophaalt. Draait bij elke
# v*-tag, zodat elke release (en pre-release) een image krijgt met dezelfde tag
# als de release zelf: v0.5.10 → server-up:0.5.10.
#
# Wil je tóch naar de Forgejo container-registry publiceren: start deze workflow
# handmatig via de Actions-tab (Run workflow) en zorg dat er een runner met
# label `docker` bestaat (of pas `runs-on` aan naar je eigen runner-label).
# Vereisten (Repository → Settings):
# Variables → REGISTRY = git.example.com (host van je Forgejo)
# OWNER = bes-r (gebruiker/organisatie)
# Secrets → PACKAGE_TOKEN = <token met write:package>
#
# Vereisten (alleen voor handmatige runs):
# Settings → Variables → REGISTRY=git.example.com, OWNER=bes-r
# Settings → Secrets → PACKAGE_TOKEN=<token met write:package scope>
# Zie docs/updates.md voor de complete instelling.
name: Build and push image (handmatig)
name: Build en push image (handmatig)
# Alleen handmatig. Bij een v*-tag bouwt en pusht deploy-prod.yml het image al
# in dezelfde job — zo kan er geen wachtrij ontstaan waarin de deploy op een
# build wacht die zelf nog in de rij staat.
on:
workflow_dispatch:
inputs:
tag:
description: "Versie om te bouwen (bv. 0.5.10). Leeg = VERSION-bestand"
required: false
jobs:
build:
runs-on: docker
runs-on: [self-hosted, prod]
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Extract versie uit tag
id: tag
run: echo "v=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Login op Forgejo registry
- name: Versie en tags bepalen
id: meta
run: |
set -euo pipefail
VERSION="${{ inputs.tag }}"
if [ -z "$VERSION" ]; then
VERSION="$(tr -d '[:space:]' < VERSION)"
fi
IMAGE="${{ vars.REGISTRY }}/${{ vars.OWNER }}/server-up"
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
echo "IMAGE=${IMAGE}" >> "$GITHUB_ENV"
# Pre-release (tag met een streepje, bv. v0.5.10-beta1) krijgt géén
# 'latest'-tag: die hoort bij het stable-kanaal.
if [[ "$VERSION" == *-* ]]; then
echo "EXTRA_TAG=beta" >> "$GITHUB_ENV"
else
echo "EXTRA_TAG=latest" >> "$GITHUB_ENV"
fi
echo "Bouwen: ${IMAGE}:${VERSION}"
- name: Tests draaien
run: |
set -euo pipefail
# In een container in plaats van een venv op de runner: die heeft
# geen python3-venv, en zo draaien de tests bovendien op exact de
# Python-versie waarop de app in productie draait.
# PYTHONDONTWRITEBYTECODE + no:cacheprovider voorkomen dat er
# root-eigen bestanden in de werkmap achterblijven.
docker run --rm \
-v "$PWD:/w" -w /w \
-v su-pip-cache:/root/.cache/pip \
-e PYTHONDONTWRITEBYTECODE=1 \
python:3.12-slim \
sh -c "pip install -q -r server-up/requirements.txt pytest \
&& python -m pytest tests -q -p no:cacheprovider"
- name: Inloggen op de registry
run: |
set -euo pipefail
echo "${{ secrets.PACKAGE_TOKEN }}" \
| docker login ${{ vars.REGISTRY }} \
-u ${{ vars.OWNER }} \
--password-stdin
| docker login "${{ vars.REGISTRY }}" \
-u "${{ vars.OWNER }}" --password-stdin
- name: Bouw image met versie ingebakken
- name: Bouwen
run: |
set -euo pipefail
docker build \
--build-arg SU_VERSION=${{ steps.tag.outputs.v }} \
-t ${{ vars.REGISTRY }}/${{ vars.OWNER }}/server-up:${{ steps.tag.outputs.v }} \
-t ${{ vars.REGISTRY }}/${{ vars.OWNER }}/server-up:latest \
--pull \
--build-arg "SU_VERSION=${VERSION}" \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:${EXTRA_TAG}" \
.
- name: Push image
- name: Pushen
run: |
docker push ${{ vars.REGISTRY }}/${{ vars.OWNER }}/server-up:${{ steps.tag.outputs.v }}
docker push ${{ vars.REGISTRY }}/${{ vars.OWNER }}/server-up:latest
set -euo pipefail
docker push "${IMAGE}:${VERSION}"
docker push "${IMAGE}:${EXTRA_TAG}"
echo "Gepusht: ${IMAGE}:${VERSION} en ${IMAGE}:${EXTRA_TAG}"
- name: Uitloggen
if: always()
run: docker logout "${{ vars.REGISTRY }}" || true

View file

@ -41,32 +41,115 @@ jobs:
echo "SU_VERSION=${VERSION}" >> "$GITHUB_ENV"
echo "Deploying version: ${VERSION} (${CHANNEL})"
- name: Tests draaien
run: |
set -euo pipefail
# In een container in plaats van een venv op de runner: die heeft
# geen python3-venv, en zo draaien de tests bovendien op exact de
# Python-versie waarop de app in productie draait.
# PYTHONDONTWRITEBYTECODE + no:cacheprovider voorkomen dat er
# root-eigen bestanden in de werkmap achterblijven.
docker run --rm \
-v "$PWD:/w" -w /w \
-v su-pip-cache:/root/.cache/pip \
-e PYTHONDONTWRITEBYTECODE=1 \
python:3.12-slim \
sh -c "pip install -q -r server-up/requirements.txt pytest \
&& python -m pytest tests -q -p no:cacheprovider"
- name: Sync code naar deploy-directory
run: |
set -euo pipefail
# .env staat in .gitignore, dus de checkout heeft er geen. Zonder deze
# uitzondering wist --delete de lokale instellingen van de server
# (BIND, SU_IMAGE, PORT) bij elke deploy.
rsync -a --delete \
--exclude='.git/' \
--exclude='.forgejo/' \
--exclude='.env' \
./ "$DEPLOY_DIR/"
- name: Schrijf VERSION-bestand
working-directory: ${{ env.DEPLOY_DIR }}
run: echo "${VERSION}" > VERSION
- name: Build image
working-directory: ${{ env.DEPLOY_DIR }}
run: |
docker compose build --pull
# docker compose bouwt als server-up:latest; voeg versie- en channel-tags toe
docker tag server-up:latest "server-up:${VERSION}"
docker tag server-up:latest "server-up:${CHANNEL}"
- name: Deploy
working-directory: ${{ env.DEPLOY_DIR }}
run: |
set -euo pipefail
# Expliciet cd in plaats van 'working-directory:'; dat laatste wordt
# niet door elke runner even betrouwbaar toegepast.
cd "$DEPLOY_DIR"
echo "${VERSION}" > VERSION
echo "Werkmap: $(pwd)"
- name: Bouwen (en pushen naar de registry)
run: |
set -euo pipefail
cd "$DEPLOY_DIR"
# SU_TAG vastleggen zodat compose en de "Nu bijwerken"-knop in de app
# naar hetzelfde image wijzen (zie docs/updates.md).
#
# SU_TAG bepaalt welk image compose start. We schrijven dat bij
# voorkeur in .env zodat het een handmatige `docker compose up`
# overleeft, maar dat bestand kan van een andere gebruiker zijn (bv.
# met sudo aangemaakt). Lukt schrijven niet, dan geven we SU_TAG mee
# via de omgeving: compose gebruikt die en de deploy loopt gewoon door.
if [ ! -f .env ] && [ -f .env.example ]; then
cp .env.example .env 2>/dev/null \
&& echo "Nieuwe .env aangemaakt vanuit .env.example" \
|| echo "LET OP: kon geen .env aanmaken in $(pwd)"
fi
if [ -w .env ] || { [ ! -e .env ] && [ -w . ]; }; then
touch .env
sed -i '/^SU_TAG=/d' .env
echo "SU_TAG=${VERSION}" >> .env
echo "Inhoud van $(pwd)/.env:"
grep -vE '^\s*(#|$)' .env | sed 's/^/ /'
else
echo "LET OP: $(pwd)/.env is niet schrijfbaar voor $(id -un)."
echo " Eigenaar: $(stat -c '%U:%G %a' .env 2>/dev/null || echo onbekend)"
echo " Herstel eenmalig met:"
echo " sudo chown $(stat -c '%U:%G' . ) $(pwd)/.env"
echo " SU_TAG wordt nu via de omgeving meegegeven."
fi
# Draait deze server vanaf de registry (SU_IMAGE in .env), dan bouwen
# we hier én pushen we het resultaat, zodat de in-app updateknop het
# image kan ophalen. Bouwen en pushen zitten bewust in dezelfde job:
# een aparte build-workflow zou met deze deploy om de runner strijden.
export SU_TAG="${VERSION}"
SU_IMAGE="$(grep -E '^SU_IMAGE=' .env 2>/dev/null | cut -d= -f2- || true)"
docker compose build --pull
if [ -n "$SU_IMAGE" ]; then
echo "Registry-modus: ${SU_IMAGE}:${VERSION} pushen"
echo "${{ secrets.PACKAGE_TOKEN }}" \
| docker login "${{ vars.REGISTRY }}" -u "${{ vars.OWNER }}" --password-stdin
docker tag "${SU_IMAGE}:${VERSION}" "${SU_IMAGE}:${CHANNEL}" || true
docker push "${SU_IMAGE}:${VERSION}"
# Pre-releases krijgen geen 'latest': dat hoort bij stable.
case "$VERSION" in
*-*) docker tag "${SU_IMAGE}:${VERSION}" "${SU_IMAGE}:beta"
docker push "${SU_IMAGE}:beta" ;;
*) docker tag "${SU_IMAGE}:${VERSION}" "${SU_IMAGE}:latest"
docker push "${SU_IMAGE}:latest" ;;
esac
docker logout "${{ vars.REGISTRY }}" || true
else
echo "Lokale bouwmodus (geen SU_IMAGE ingesteld)"
docker tag "server-up:${VERSION}" "server-up:${CHANNEL}" || true
fi
- name: Deploy
run: |
set -euo pipefail
cd "$DEPLOY_DIR"
export SU_TAG="${VERSION}"
docker compose up -d --remove-orphans
docker compose ps
echo "Compose-projectmap volgens de container:"
docker inspect server-up \
--format ' {{index .Config.Labels "com.docker.compose.project.working_dir"}}'
echo "Poortbinding:"
docker inspect server-up \
--format ' {{range $p, $c := .NetworkSettings.Ports}}{{$p}} -> {{range $c}}{{.HostIp}}:{{.HostPort}}{{end}}{{"\n"}}{{end}}'
- name: Wachten op healthcheck
run: |

View file

@ -41,32 +41,93 @@ jobs:
echo "SU_VERSION=${VERSION}" >> "$GITHUB_ENV"
echo "Deploying version: ${VERSION} (${CHANNEL})"
- name: Tests draaien
run: |
set -euo pipefail
# In een container in plaats van een venv op de runner: die heeft
# geen python3-venv, en zo draaien de tests bovendien op exact de
# Python-versie waarop de app in productie draait.
# PYTHONDONTWRITEBYTECODE + no:cacheprovider voorkomen dat er
# root-eigen bestanden in de werkmap achterblijven.
docker run --rm \
-v "$PWD:/w" -w /w \
-v su-pip-cache:/root/.cache/pip \
-e PYTHONDONTWRITEBYTECODE=1 \
python:3.12-slim \
sh -c "pip install -q -r server-up/requirements.txt pytest \
&& python -m pytest tests -q -p no:cacheprovider"
- name: Sync code naar deploy-directory
run: |
set -euo pipefail
# .env staat in .gitignore, dus de checkout heeft er geen. Zonder deze
# uitzondering wist --delete de lokale instellingen van de server
# (BIND, SU_IMAGE, PORT) bij elke deploy.
rsync -a --no-group --delete \
--exclude='.git/' \
--exclude='.forgejo/' \
--exclude='.env' \
./ "$DEPLOY_DIR/"
- name: Schrijf VERSION-bestand
working-directory: ${{ env.DEPLOY_DIR }}
run: echo "${VERSION}" > VERSION
- name: Build image
working-directory: ${{ env.DEPLOY_DIR }}
run: |
docker compose build --pull
# docker compose bouwt als server-up:latest; voeg versie- en channel-tags toe
docker tag server-up:latest "server-up:${VERSION}"
docker tag server-up:latest "server-up:${CHANNEL}"
- name: Deploy
working-directory: ${{ env.DEPLOY_DIR }}
- name: Instellingen en versie klaarzetten
run: |
set -euo pipefail
# Expliciet cd in plaats van 'working-directory:'. Dat laatste wordt
# niet door elke runner-implementatie even betrouwbaar toegepast, en
# als het misgaat belanden VERSION, .env en de container stilletjes in
# de werkmap van de runner in plaats van in de deploy-map.
cd "$DEPLOY_DIR"
echo "Werkmap: $(pwd)"
echo "${VERSION}" > VERSION
# SU_TAG bepaalt welk image compose start. We schrijven dat bij
# voorkeur in .env zodat het een handmatige `docker compose up`
# overleeft, maar dat bestand kan van een andere gebruiker zijn (bv.
# met sudo aangemaakt). Lukt schrijven niet, dan geven we SU_TAG mee
# via de omgeving: compose gebruikt die en de deploy loopt gewoon door.
if [ ! -f .env ] && [ -f .env.example ]; then
cp .env.example .env 2>/dev/null \
&& echo "Nieuwe .env aangemaakt vanuit .env.example" \
|| echo "LET OP: kon geen .env aanmaken in $(pwd)"
fi
if [ -w .env ] || { [ ! -e .env ] && [ -w . ]; }; then
touch .env
sed -i '/^SU_TAG=/d' .env
echo "SU_TAG=${VERSION}" >> .env
echo "Inhoud van $(pwd)/.env:"
grep -vE '^\s*(#|$)' .env | sed 's/^/ /'
else
echo "LET OP: $(pwd)/.env is niet schrijfbaar voor $(id -un)."
echo " Eigenaar: $(stat -c '%U:%G %a' .env 2>/dev/null || echo onbekend)"
echo " Herstel eenmalig met:"
echo " sudo chown $(stat -c '%U:%G' . ) $(pwd)/.env"
echo " SU_TAG wordt nu via de omgeving meegegeven."
fi
- name: Build image
run: |
set -euo pipefail
cd "$DEPLOY_DIR"
export SU_TAG="${VERSION}"
docker compose build --pull
docker tag "server-up:${VERSION}" "server-up:${CHANNEL}" || true
- name: Deploy
run: |
set -euo pipefail
cd "$DEPLOY_DIR"
export SU_TAG="${VERSION}"
docker compose up -d --remove-orphans
docker compose ps
# Waar draait de container vandaan, en op welk adres luistert hij?
echo "Compose-projectmap volgens de container:"
docker inspect server-up \
--format ' {{index .Config.Labels "com.docker.compose.project.working_dir"}}'
echo "Poortbinding:"
docker inspect server-up \
--format ' {{range $p, $c := .NetworkSettings.Ports}}{{$p}} -> {{range $c}}{{.HostIp}}:{{.HostPort}}{{end}}{{"\n"}}{{end}}'
- name: Wachten op healthcheck
run: |

2
.gitignore vendored
View file

@ -2,3 +2,5 @@ __pycache__/
*.pyc
*.pyo
.env
.pytest_cache/
venv/

View file

@ -1,3 +1,757 @@
# v0.7.30-beta — Installeren met één regel
```bash
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh
```
Het script controleert je systeem, biedt aan Docker te installeren als dat
ontbreekt, haalt de code op, maakt `.env` aan, bouwt het image, start de
container en wacht tot `/healthz` antwoordt. Daarna toont het waar je terecht
kunt — met de waarschuwing dat je meteen een beheerdersaccount moet aanmaken.
Opties: `--dir`, `--port`, `--bind`, `--branch`, `--token`, `--update`,
`--uninstall`, `--yes` en `--dry-run`.
**`--dry-run`** toont de hele gang van zaken zonder iets te wijzigen. Handig om
te zien wat er gaat gebeuren voordat je een script met root-rechten loslaat op
je server; `docs/installeren.md` zet die stap dan ook vooraan.
**Bijwerken** gaat met `sh /opt/server-up/install.sh --update`: nieuwe code
ophalen, opnieuw bouwen, herstarten. Je `.env`, stacks en gegevens blijven staan.
**Verwijderen** stopt de container maar laat je gegevens met rust, en vertelt
daarna hoe je die alsnog opruimt.
Het script gebruikt alleen `sudo` waar dat nodig is: kun je zelf al in de
doelmap schrijven én docker aanroepen, dan blijft alles onder je eigen account.
Nieuw: `README.md` verwijst er nu naar, plus `docs/installeren.md` met alle
opties en een probleemoplostabel.
Getest onder `sh`, `dash` en `bash`, met een testsuite die controleert dat er
geen bashismen in sluipen, dat een typefout in de opties netjes stopt, en dat
een proefdraai echt niets aanmaakt. Twee dingen die daarbij naar boven kwamen:
de wachtlus gaf anderhalve minuut lang geen enkel teken van leven (nu puntjes),
en `docker compose --project-directory` zoekt zonder `-f` het compose-bestand
alsnog in de huidige map — wat bij `curl | sh` je thuismap is.
---
# v0.7.20-beta — Instellingen wijzigen, koppelen achteraf, en een defect uit v0.7.00
## 🔴 Gedeeld netwerk botste met `network_mode`
De koppeling uit v0.7.00 hing élke service aan het gedeelde netwerk. Maar een
service met `network_mode` deelt al andermans namespace, en docker compose
weigert die combinatie. Met de standaardkeuze "Verbinden met andere apps" waren
**Homebridge**, **Scrypted**, **Beszel** en de **ARR-stack met Gluetun** daardoor
niet te installeren.
Services met `network_mode` worden nu overgeslagen. Een test past het gedeelde
netwerk toe over alle 96 apps en controleert dat die combinatie nooit ontstaat.
## ⚙️ Instellingen wijzigen na installatie
Je koos bij het installeren netjes een poort, map en wachtwoord — maar daarna was
er geen weg terug naar dat formulier. Voor een andere poort moest je het
compose-bestand met de hand bewerken.
De knop **Instellingen** op elke stackkaart opent hetzelfde formulier, voorgevuld
met wat je destijds hebt gekozen. Opslaan maakt eerst een backup, rendert de
stack opnieuw, valideert en herstart. Loopt het valideren mis, dan wordt de
backup automatisch teruggezet.
Stacks die zijn geïnstalleerd voordat Server Up die keuzes bewaarde, hebben de
knop uit staan met uitleg waarom.
## 🔗 Bestaande stacks alsnog koppelen
Het gedeelde netwerk gold alleen voor nieuwe installaties. Een knop op de
stackkaart zet een bestaande stack er alsnog op, valideert en herstart. Wordt de
compose ongeldig, dan wordt de wijziging teruggedraaid.
## 🔍 Audit-log doorzoekbaar
Het log toonde de laatste tweehonderd regels zonder filter, terwijl er inmiddels
logins, rolwijzigingen, backups en updates in landen. Nu filteren op bron, actie,
status en periode, met vrij zoeken door referentie, details en IP-adres, en
doorbladeren voorbij de eerste honderd.
## 📖 Documentatie
- **`README.md`** — die was er niet. Wat Server Up is, installeren, het eerste
account, en waar je verder moet kijken.
- **`docs/apps-maken.md`** — het volledige templateformaat: velden, geavanceerde
opties, uitleg per veld, apps aan elkaar koppelen, onderdelen optioneel maken
met groepsschakelaars, en de valkuilen (named volumes, poorten, `network_mode`).
- **`docs/README.md`** — index over de zeven documenten.
De changelog-secties voor `v0.5.44`, `v0.5.45` en `v0.5.46` ontbraken; omdat
`release.yml` de notes daaruit haalt, zou een tag op die versies een lege release
opleveren. Aangevuld, met een test die afdwingt dat het huidige `VERSION` altijd
een sectie heeft.
---
# v0.7.10-beta — Categorieën met kleur, in de app store en bij je apps
Alle 96 apps zijn ingedeeld in zeventien categorieën, elk met een eigen kleur en
pictogram. Een app mag in meerdere categorieën zitten: Frigate staat onder
Domotica én Media, Immich onder Foto's én Opslag, Kavita onder Media én
Documenten. Negenendertig apps vallen onder meer dan één.
| | Categorie | | Categorie |
|---|---|---|---|
| 🟣 | Media | 🔵 | Netwerk |
| 🩷 | Foto's | 🔴 | Beveiliging |
| 🟢 | Downloaden | 🟠 | Opslag & backup |
| 🟩 | Domotica | 🩵 | Documenten |
| 🟦 | 3D-printen | 🟦 | Productiviteit |
| 🔷 | Communicatie | 🟩 | Financiën |
| 🌸 | Gezin & huishouden | 🟧 | Monitoring |
| 🩵 | Ontwikkeling | 🟪 | AI |
| ⬜ | Systeembeheer | | |
**In de app store** staat een gekleurde categoriebalk met per categorie het
aantal apps. Klikken filtert; het bestaande zoekveld en de tagfilters werken
ernaast gewoon door. Elke app-kaart toont zijn categorieën als gekleurde badge,
en klikken op zo'n badge filtert er meteen op.
**Bij je geïnstalleerde apps** staat dezelfde balk, met alleen de categorieën die
je daadwerkelijk draait. Elke stackkaart heeft een gekleurde rand in de kleur van
zijn eerste categorie, plus dezelfde klikbare badges.
Apps uit externe repo's (zoals de ChristianLempa-boilerplates) geven geen
categorieën op. Die worden afgeleid uit hun tags en omschrijving, zodat ook zij
een kleur en een plek in het filter krijgen in plaats van als naamloze groep
buiten de indeling te vallen.
Tests bewaken dat elke app minstens één geldige categorie heeft, dat de kleuren
onderling verschillen, en dat er geen categorie in het filter staat waar geen
enkele app onder valt.
---
# v0.7.00-beta — Apps koppelen, eenvoudiger instellen, geavanceerde opties verborgen
## 🔗 Apps kunnen elkaar nu bereiken
Elke stack draaide als eigen compose-project en kreeg daarmee zijn eigen
netwerk. Twee stacks konden elkaar dus niet op naam vinden: Zigbee2MQTT zag de
Mosquitto-broker uit een andere stack simpelweg niet.
Bij het installeren staat nu **"Verbinden met andere apps"** aan. De stack komt
dan op een gedeeld bridge-netwerk (`serverup`), waar containers elkaar op naam
oplossen. Het netwerk wordt aangemaakt zodra de eerste stack het nodig heeft.
## 🎯 Kiezen in plaats van typen
Een veld dat naar een andere app verwijst, toont voortaan een **keuzelijst met
wat je al hebt draaien** in plaats van een leeg tekstveld. Installeer je
Zigbee2MQTT en heb je Mosquitto al staan, dan staat `mqtt://mosquitto:1883` er
meteen ingevuld. Hetzelfde voor Open WebUI dat je Ollama vindt.
Staat de app er nog niet, dan zegt het veld dat — met de mogelijkheid om alsnog
handmatig een adres in te vullen.
## 💬 Uitleg per instelling
Achter elk veld met toelichting staat een **info-icoon**. Klikken opent een blok
met de volledige uitleg en de standaardwaarde. Voor de instellingen die de
meeste vragen oproepen is die uitleg uitgeschreven — waarom je downloadmap op
hetzelfde bestandssysteem moet staan als je mediamap, wat `shm_size` bij Frigate
doet, waarom Caddy poort 80 nodig heeft ook als je alles via HTTPS aanbiedt.
## 🎛️ Geavanceerde opties uit het zicht
Velden die je meestal met rust laat — database-wachtwoorden, PUID/PGID,
USB-paden, bewaartermijnen — zitten nu achter een uitklapblok **"Geavanceerde
opties"** met een teller erbij. Het installatieformulier begint daardoor met
alleen de vragen die er echt toe doen.
Templates kunnen `advanced: true` op een veld of op een hele groep zetten. Een
test bewaakt dat er per app altijd zichtbare velden overblijven, zodat niemand
per ongeluk een volledig leeg formulier maakt.
---
# v0.6.20-beta — 31 apps erbij na een rondgang langs de verzamelsites
Van 65 naar 96 apps. Selectie op basis van awesome-selfhosted, selfh.st,
Perfect Media Server en een paar overzichtslijsten van 2026, aangevuld met wat
er in de bestaande collectie nog ontbrak.
**Monitoring** — Prometheus (de tegenhanger van Grafana, die er al stond),
InfluxDB, Netdata, Beszel
**Reverse proxy** — Caddy, dat zijn certificaten volledig zelf regelt
**Notities en kennis** — Memos, HedgeDoc, Wiki.js, Excalidraw, Karakeep
**Data en dashboards** — NocoDB, Baserow, Metabase
**Identiteit** — Authelia en Keycloak, allebei bruikbaar met de
reverse-proxy-SSO van Server Up zelf
**Media** — Komga, PhotoPrism, MeTube, Pinchflat
**Meldingen** — ntfy
**Productiviteit** — Planka, Focalboard, Kimai
**Locatie en voertuig** — OwnTracks, TeslaMate
**Hulpmiddelen** — Shlink, CyberChef, LanguageTool, Kopia
**AI** — Ollama en Open WebUI, allebei volledig lokaal
De images van de nieuwere apps (Beszel, Karakeep, Pinchflat) zijn nagetrokken
bij de bron in plaats van uit het hoofd opgeschreven; van Karakeep is de
officiële compose overgenomen, inclusief de meilisearch- en chrome-containers
die hij nodig heeft.
Vijftien poortbotsingen met bestaande apps zijn automatisch rechtgezet. Caddy
deelt 80 en 443 met Traefik en Nginx Proxy Manager — alternatieven voor dezelfde
taak, dus dat staat in de allowlist.
---
# v0.6.10-beta — ARR-stack met een schakelaar per onderdeel
De ARR-stack installeerde altijd dezelfde zeven containers. Nu kies je per
onderdeel wat je wilt, uit twintig:
**Media beheren** — Sonarr (series), Radarr (films), Lidarr (muziek), Readarr
(boeken), Whisparr
**Indexers en ondersteuning** — Prowlarr, Jackett, FlareSolverr, Bazarr
(ondertitels)
**Downloaden** — qBittorrent, SABnzbd, NZBGet, en Gluetun om al het
downloadverkeer door een VPN te leiden
**Verzoeken en aanvullingen** — Jellyseerr, Recyclarr (TRaSH-profielen),
Unpackerr, Tdarr (hercoderen), Autobrr, Maintainerr, Huntarr
Standaard staan Sonarr, Radarr, Prowlarr, Bazarr, qBittorrent en Jellyseerr aan;
de rest zet je zelf bij.
## Gluetun schakelt de downloadclients om
Zet je Gluetun aan, dan verhuizen qBittorrent, SABnzbd en NZBGet naar diens
netwerknamespace en verliezen ze hun eigen poortmapping — precies zoals het
hoort, want anders lekt hun verkeer om de VPN heen. Hun webinterfaces bereik je
dan via de poorten van de Gluetun-container.
Daarbij zat een val: qBittorrent en SABnzbd luisteren allebei standaard op 8080,
en achter één VPN-container botsen ze. qBittorrent draait nu intern op 8090.
## Groepsschakelaars werkten niet
Een groepsschakelaar in template.json was zelf geen variabele en kreeg dus nooit
een startwaarde. Elk schakelbaar onderdeel stond daardoor standaard uit — een
stack met schakelaars zou leeg binnenkomen. Ze worden nu als veld meegegeven met
een instelbare standaardstand, en de interface toont ze alleen als schakelaar in
de kop van de groep.
---
# 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
terugzag. Dat kwam doordat de cijfers en de lijst uit twee verschillende bronnen
komen:
* de **tellers** komen van `docker info` en gaan over de hele Docker-daemon
* de **stacklijst** toont alleen mappen in `LIBRARY_DIR` met een compose-bestand
Alles wat je buiten Server Up om had gestart — je Forgejo, de act_runner,
containers van vóór je Server Up ging gebruiken — telde dus wel mee maar was
onzichtbaar en onbedienbaar. Een beheertool die dingen telt die hij niet toont,
is verwarrend.
## Wat er nu is
- **Overzicht van alle containers op de host.** Onder de stacklijst staat een
blok "Overige containers" met alles wat niet bij een beheerde stack hoort,
inclusief image, poorten en compose-project. Starten, stoppen, herstarten en
logs bekijken kan direct.
- **De dashboardteller vertelt nu het hele verhaal**: onder "actief" staat
hoeveel daarvan via Server Up loopt.
- De eigen container is gemarkeerd als "deze app" en kan niet via dit blok
gestopt worden — daarvoor is de herstartknop in de instellingen, die dat
netjes afhandelt in plaats van zichzelf halverwege een verzoek te stoppen.
- Een viewer ziet de lijst wel maar kan niets bedienen.
## Verholpen tijdens het bouwen
Containers zonder poorten én zonder compose-labels vielen weg uit de lijst als
ze toevallig als laatste stonden: `strip()` haalde de lege velden aan het eind
van de uitvoer weg, waarna die regel te weinig kolommen leek te hebben. Gevonden
door de test die precies zo'n container als laatste zet.
---
# v0.5.46-beta — Deploy strandde op een .env van een andere gebruiker
De stap die instellingen klaarzet faalde op `touch: cannot touch '.env':
Permission denied`. Het bestand was met sudo aangemaakt en dus van root, terwijl
de runner als gewone gebruiker draait — waarmee de hele deploy afbrak op iets
dat geen blokkade hoort te zijn.
- `SU_TAG` gaat nu altijd via de omgeving naar `docker compose`. Wegschrijven in
`.env` blijft de voorkeur omdat het een handmatige `docker compose up`
overleeft, maar is niet meer nodig om te kunnen deployen.
- Is `.env` niet schrijfbaar, dan meldt de log wie de eigenaar is en welk
`chown`-commando dat rechtzet, en loopt de deploy door.
---
# v0.5.45-beta — Wizard-knop toegevoegd
De wizard was alleen zichtbaar bij een verse installatie: zodra hij een keer was
afgerond, was er geen enkele manier meer om hem te openen. De vertaalsleutels
`wizard_open` en `wizard_reset` bestonden al en `/api/wizard/reset` werkte ook,
maar de knop is nooit gebouwd — ook niet in v0.4.60.
- Blok **Setup Wizard** onder Instellingen met "Wizard openen" en "Opnieuw
uitvoeren".
- Sluitknop in de wizard zelf; die kon je eerder alleen verlaten door hem
helemaal af te ronden.
- De staptitels lopen nu via `t()` in plaats van harde Nederlandse teksten.
---
# v0.5.44-beta — Gedetecteerd netwerk verdween uit het formulier
Zodra je een netwerk had toegevoegd, was het blok "gevonden op deze server" leeg
bij een volgende poging: subnetten die al geconfigureerd waren werden uit de
suggesties gefilterd. Dat pakt verkeerd uit — je kunt prima een tweede netwerk op
hetzelfde subnet willen met een andere range, en zonder dat blok lijkt het
formulier kapot.
- Gedetecteerde netwerken blijven staan en tonen "al toegevoegd" in plaats van
te verdwijnen.
- Levert de detectie niets op zonder dat er een fout is, dan staat er nu uitleg
in plaats van een leeg blok.
---
# v0.5.43-beta — Van-tot omrekenen naar een CIDR-blok
"Ik wil van 10.0.20.200 tot .254" is een volstrekt redelijke wens, maar bestaat
niet als CIDR-notatie: dat zijn 55 adressen, en Docker accepteert alleen
uitgelijnde blokken van 2, 4, 8, 16 … adressen.
Onder het IP-range-veld zit nu een **van-tot-hulp**. Vul de eerste en laatste
gewenste adressen in en Server Up rekent uit welke blokken in de buurt komen:
* **dekt alles** — het kleinste blok dat je hele wens omvat (pakt er onderaan
wat bij)
* **past binnen** — het grootste blok dat volledig binnen je wens valt
Bij `.200` t/m `.254` levert dat `10.0.20.192/26` op: 62 adressen, `.193` t/m
`.254`. Klikken vult het veld in.
---
# v0.5.42-beta — Netwerk toevoegen: twee blokkades weg
Een netwerk aanmaken lukte niet, met wisselend gedrag: soms een foutmelding dat
de IP-range buiten het subnet lag terwijl dat aantoonbaar niet zo was, soms geen
melding maar toch geen resultaat. Het bleken twee losse oorzaken.
## De foutmelding die niet klopte
De beoordeling gaat af bij elke toetsaanslag. Tijdens het intypen van `/28` ga
je langs `/2`, en `10.0.20.240/2` normaliseert naar `0.0.0.0/2` — inderdaad
buiten je subnet. Kwam dat antwoord ná het antwoord voor de volledige waarde
binnen, dan bleef die foutmelding staan en bleef de knop uitgeschakeld.
Er zit nu een volgnummer op: alleen het antwoord bij de laatste vraag telt. En
de knop wordt niet meer uitgeschakeld op de clientstatus — bij het opslaan
beslist de server, zodat een verouderde beoordeling je nooit kan blokkeren.
## De range die Docker weigerde
`10.0.20.200/28` heeft hostbits gezet. Python leest dat soepel als
`10.0.20.192/28`, maar Docker is streng en antwoordt *"has host bits set"*. Wij
valideerden soepel en stuurden vervolgens de ruwe tekst door, dus de interface
keurde iets goed dat bij het aanmaken alsnog strandde.
Subnet en IP-range worden nu omgezet naar hun canonieke vorm voordat ze naar
Docker gaan én voordat ze worden opgeslagen, met een waarschuwing die laat zien
wat er van je invoer gemaakt is. `.200/28` is trouwens een logische invoer — je
bedoelt "vanaf .200" — maar een /28 begint nu eenmaal op een veelvoud van 16.
---
# v0.5.41-beta — Netwerk toevoegen liep vast tijdens het typen
Een netwerk toevoegen gaf een JSON-parsefout in de browser.
Oorzaak: de live beoordeling die bij elke toetsaanslag afgaat, bouwde een lijst
van alle bruikbare adressen in het bereik. Tijdens het intypen van
`192.168.1.0/24` is de tussenstand `192.168.1.0/2` een volkomen geldig
netwerk — met 1.073.741.824 adressen. Het verzoek liep daarop vast en de browser
kreeg geen JSON meer terug.
- `review()` rekent eerste adres, laatste adres en aantal nu uit in plaats van
ze op te sommen. Antwoord binnen 0,05 seconde bij elk prefix.
- `suggest_range()` had hetzelfde probleem: die somde alle /28-blokken op, wat
bij een `/8` ruim een miljoen blokken zijn. Rekent nu van achteren naar voren.
- Waarschuwing bij een bereik van meer dan 4096 adressen, want dat is vrijwel
altijd een typefout in het prefix.
- Ook `.env` van de deploy-map werd bij elke deploy gewist door `rsync --delete`,
omdat het bestand in `.gitignore` staat en dus niet in de checkout zit. Daardoor
raakte je `BIND` telkens kwijt. `--exclude='.env'` toegevoegd.
- Nieuw `.env.example` met alle instellingen bij elkaar en uitleg per sleutel.
---
# v0.5.40-beta — Netwerken instellen zonder uitzoekwerk
## 🔍 Automatische detectie
Bij **Instellingen → Netwerken → Netwerk** toont Server Up nu bovenaan het
netwerk dat je server zelf gebruikt: interface, subnet, gateway en een
voorgestelde vrije IP-range. Eén klik vult het hele formulier.
De detectie start heel kort een container die de netwerknamespace van de host
deelt en leest daar de routetabel. Dat is nodig omdat Server Up in een eigen
namespace zit: de vorige versie las `/sys/class/net` binnen de container en
toonde daardoor de bridge-interface van de container zelf in plaats van de
netwerkkaart van de host — een lijstje dat er goed uitzag maar niet klopte.
Lukt detecteren niet (geen docker-socket, ouder image), dan staat er een nette
melding en vul je het als vanouds handmatig in.
## 💡 Live meedenken bij het invullen
Terwijl je typt controleert Server Up de combinatie en toont wat het oplevert:
*"14 adressen beschikbaar: 192.168.1.241 tot en met 192.168.1.254"*. Klopt er
iets niet, dan staat er in gewone taal bij waarom — een range buiten het subnet,
een gateway die je beter buiten het bereik kunt houden, of een range zo klein
dat er maar een paar stacks in passen.
Een knop stelt een vrij blok voor op basis van het subnet, met de gateway
ontweken.
## 📖 Uitleg waar je hem nodig hebt
In het formulier zit een uitklapbaar blok dat uitlegt hoe subnet, gateway,
IP-range en host-interface zich tot elkaar verhouden, met een concreet voorbeeld.
`docs/netwerken.md` heeft dezelfde uitleg met een schema van de adresverdeling,
een tabel met veelgebruikte ranges, en één punt dat er echt uit moet springen:
**het instellen van de IP-range in Server Up doet niets aan je router** — je moet
daar zelf het DHCP-bereik verkleinen, anders blijven dubbele adressen mogelijk.
---
# v0.5.30-beta — Rollen, app-store-filter en twee planningsfouten
## 👥 Rollen per gebruiker
Tot nu toe had elk account volledige toegang. Nu drie rollen:
| Rol | Mag |
|---|---|
| **Beheerder** | alles |
| **Operator** | stacks, containers en backups beheren — niet de instellingen, gebruikers, repo's, netwerken of modules |
| **Alleen lezen** | alleen bekijken |
- Afgedwongen in de `before_request`-guard, niet alleen in de interface.
- Je eigen wachtwoord wijzigen mag iedereen, ongeacht rol.
- De laatste beheerder kan niet gedegradeerd of verwijderd worden.
- Wie via de reverse proxy binnenkomt zonder eigen account krijgt de rol uit
`AUTH.proxy_role`; bestaat er wél een lokaal account met die naam, dan wint
dat account.
- Accounts van vóór deze versie hebben geen rol en gelden als beheerder.
## 🔒 Rechtenescalatie verholpen
Bij het wijzigen van een wachtwoord werd het hele gebruikersrecord vervangen,
waardoor het `role`-veld wegviel. Omdat een ontbrekende rol als beheerder geldt
(nodig voor bestaande accounts), kon **elke operator of viewer zichzelf tot
beheerder promoveren door zijn eigen wachtwoord te wijzigen.** Gevonden bij het
naspelen van de rollenflow; de rol blijft nu behouden en een test dekt het af.
## ⏰ Geplande backups draaiden nooit
De scheduler stempelde ook taken af die zichzelf hadden overgeslagen. De
backup-taak bewaakt zelf of het het ingestelde uur is, dus die werd afgestempeld
op het moment van de eerste tick — waarna het volgende moment 24 uur later op
precies dat verkeerde tijdstip viel. Resultaat: de geplande backup kwam nooit
aan de beurt.
Een taak stempelt nu alleen af als hij `True` teruggeeft. De backup-taak draait
daardoor op een kort interval en bepaalt zelf wanneer het zover is, zodat een
gewijzigde planning ook meteen werkt zonder herstart. Voor wekelijkse backups is
`BACKUP_SCHEDULE_DAY` toegevoegd.
## 🔎 Zoeken en filteren in de app store
Zoekveld over naam, omschrijving en tags, plus klikbare tagfilters (de veertien
meestgebruikte, op frequentie gesorteerd). Repo's zonder treffers vallen weg.
## 🔧 Overig
- Opstartcontrole op de meegeleverde front-end-bestanden. Mislukt het downloaden
tijdens de image-build, dan laadde de interface zonder opmaak terwijl de
server prima leek te draaien; nu staat er een duidelijke melding in het log.
- 227 tests.
---
# v0.5.20-beta — Vertalingen, SSO-scherm, complete backups en app-updates
## 🌍 Vertalingen en SSO
- De schermen uit v0.5.x (login, Beveiliging, Netwerken, Updates,
containerpaneel) gebruikten harde Nederlandse teksten terwijl de rest van de
interface via `t()` loopt. Op Engels gaf dat een mengelmoes. Nu volledig
vertaald: 208 sleutels in `nl` en `en`.
- `t()` ondersteunt plaatshouders: `t('port_taken', {port: 8080})`. Zo blijven
zinnen heel in plaats van in losse stukjes geknipt.
- Nieuw instelscherm voor **reverse-proxy-SSO** onder Instellingen →
Beveiliging: modus lokaal/proxy/beide, identiteitsheader en de vertrouwde
proxy-adressen. De backend bestond al maar was alleen met `curl` te bereiken.
- Nieuwe test bewaakt dat `nl` en `en` dezelfde sleutels houden, dat de
plaatshouders in beide talen gelijk zijn, en dat de interface geen sleutels
gebruikt die nergens gedefinieerd staan.
## 💾 Backups compleet
Backup was één `tar`-commando waarvan de returncode genegeerd werd — een
mislukte backup gold als succes, en terugzetten kon alleen met de hand.
- **Terugzetten** met één klik: stack stoppen, huidige map opzij, uitpakken,
starten. Mislukt het uitpakken, dan komt de oude situatie terug.
- **Bewaarbeleid**: aantal per stack en/of maximale leeftijd. De nieuwste
backup van een stack blijft altijd staan.
- **Geplande backups**, dagelijks of wekelijks, via een eigen planner in de
applicatie (geen cron). Een gemiste ronde loopt bij de eerstvolgende
gelegenheid alsnog.
- **Automatisch een backup vóór het bijwerken of verwijderen** van een stack.
Mislukt die, dan gaat de actie door — anders kun je een kapotte stack niet
meer opruimen.
- Uitpakken gebeurt met `tarfile` en `filter="data"`: absolute paden en
`..`-ingangen worden geweigerd. Met een kaal `tar xzf` kon een geprepareerd
archief buiten de stackmap schrijven.
- Backups binnen dezelfde seconde overschreven elkaar; namen krijgen nu een
teller.
- `docs/backups.md` legt uit wat er wél en niet in zit — de compose- en
`.env`-bestanden, **niet** de Docker-volumes met de eigenlijke appdata.
## 📦 Updates per app
- Server Up controleert nu ook of er nieuwere images zijn voor de
geïnstalleerde stacks, met een badge op de stackkaart en een knop om te
controleren. Bijwerken doet de bestaande update-knop.
- De vergelijking loopt via de Registry API v2 (`Docker-Content-Digest`), niet
via `docker manifest inspect`: dat laatste geeft per platform een aparte
digest terug terwijl `RepoDigests` de digest van de manifest-list bevat, wat
bij elk multi-arch image permanent "update beschikbaar" zou opleveren.
- Drie statussen per service — `update`, `current` en `unknown`. Een lokaal
gebouwd image, een nog niet gepulld image of een onbereikbare registry levert
`unknown` op en dus géén badge, zodat er nooit ten onrechte een update wordt
gemeld.
- Dagelijkse achtergrondcheck via dezelfde planner; resultaten 6 uur gecached.
---
# v0.5.10-beta — Update-systeem met kanalen en één-klik bijwerken
## 🔄 Kanalen
Server Up kent nu twee update-kanalen in plaats van een vinkje "pre-releases
meenemen":
- **stable** — alleen echte releases (`v0.5.10`)
- **beta** — ook pre-releases (`v0.5.10-beta1`), die `release.yml` automatisch
als zodanig markeert bij een tag met een streepje
Een release telt hoger dan zijn eigen beta (`0.5.10-beta1` < `0.5.10`), dus wie
op beta zit krijgt de definitieve versie alsnog aangeboden. De oude instelling
`UPDATE_INCLUDE_PRERELEASE` migreert automatisch naar het beta-kanaal.
## ⬇️ Bijwerken vanuit de interface
Nieuw: **Nu bijwerken** haalt het image uit je Forgejo container-registry en
vervangt de eigen container.
- Omdat een container zichzelf niet kan hercreëren, doet Server Up alleen het
voorwerk (image ophalen, `SU_TAG` wegschrijven) en laat het de hercreatie over
aan een korte helper-container die op het nieuwe image draait — dat bevat de
docker- en compose-CLI al.
- De deploy-map wordt uitgelezen uit de compose-labels van de eigen container,
niet geraden. Ontbreken die labels, draait de container niet vanaf een
registry-image, of is de docker-socket er niet, dan meldt de interface
precies waaróm bijwerken niet kan in plaats van iets te proberen.
- De vorige tag wordt onthouden, met een **terugrolknop** in de instellingen.
- Voortgang van de `docker pull` loopt via het bestaande job-logvenster; daarna
pollt de browser `/healthz` tot de nieuwe versie leeft.
## 🔧 Overig
- Update-check wordt een uur gecached; **Controleren** forceert een verse check.
Eerder deed elke paginalading een netwerkverzoek.
- Laatst-gecontroleerd-tijdstip zichtbaar in de interface.
- Registry-inloggegevens instelbaar voor een privé registry. Het token wordt net
als de git-tokens nooit teruggegeven door de API (alleen een `has_`-vlag) en
leeg laten betekent "ongewijzigd".
- `docker-compose.yml` gebruikt `${SU_IMAGE:-server-up}:${SU_TAG:-latest}`, zodat
image en tag los instelbaar zijn. Zonder `SU_IMAGE` blijft alles werken zoals
voorheen (lokaal bouwen).
- `deploy-prod.yml` bouwt én pusht het image in dezelfde job wanneer `SU_IMAGE`
ingesteld is. Bewust niet als aparte workflow: bij één runner zou de deploy
wachten op een build die zelf nog in de wachtrij staat.
- `build.yml` is nu alleen handmatig, voor het herbouwen van een specifieke tag.
- `docs/updates.md` toegevoegd: hoe het werkt, de complete Forgejo-instelling
(registry, tokens, variables, secrets, runners), de release-procedure voor
zowel stable als beta, terugrollen en een probleemoplostabel.
---
# v0.5.00-beta — Authenticatie, beveiliging en eigen IP-adressen
> **Let op bij het bijwerken.** Server Up heeft nu een login. Open na het
> bijwerken meteen de webinterface en maak een beheerdersaccount aan — zolang
> dat niet gebeurd is, kan iedereen die de pagina bereikt het account claimen.
> De poort wordt voortaan standaard op `127.0.0.1` gebonden; zet `BIND=0.0.0.0`
> in je `.env` als je er van buiten de server bij moet (liefst achter een
> reverse proxy met TLS — zie `docs/beveiliging.md`).
## 🔐 Authenticatie
Tot nu toe was elke `/api/*`-route open. Omdat Server Up de docker-socket als
root gebruikt, betekende dat: wie de poort kon bereiken, had root op de host.
- Lokale accounts met scrypt-gehashte wachtwoorden, sessiecookie
(`HttpOnly`, `SameSite=Strict`) en lockout na vijf mislukte pogingen.
- Optionele SSO via een reverse-proxy-header (Authelia/Authentik/Cloudflare
Access). De header wordt alleen vertrouwd vanaf een geconfigureerd proxy-IP.
- Loginscherm en eerste-account-setup in de interface; gebruikersbeheer en
wachtwoord wijzigen onder Instellingen → Beveiliging.
## 🛡️ Beveiligingsfixes
- **CSRF**: elke mutatie vereist een `X-CSRF-Token`-header. Routes die de
toestand wijzigen accepteren geen `GET` meer — `/api/docker/restart` was
eerder met een `<img>`-tag vanaf een willekeurige website te triggeren.
- **Path traversal**: `/api/store/install` controleerde de instantienaam niet;
`"instance": "../../…"` schreef buiten de library. Alle stack-, instantie- en
repo-namen lopen nu door één `safe_name()`-validatie.
- **Tokenlek**: git-tokens werden teruggegeven door `/api/repos` en
`/api/settings`. Die zijn vervangen door een `has_token`-vlag; opslaan met een
leeg veld wist het bestaande token niet meer.
- **Git-URL's**: alleen `http(s)://`, `ssh://` en `git@host:pad`. Git's
`ext::`-transport voert een shell-commando uit en wordt nu geweigerd.
- **Templates** renderen in een Jinja2-sandbox (server-side template injection).
- **SSH**: host-keys worden geverifieerd (`accept-new` + `/data/known_hosts`);
eerder stond `StrictHostKeyChecking=no`, waarmee elke MITM onzichtbaar was.
- **Modules**: repo's worden niet meer automatisch bij elke start gepulld
(`AUTO_SYNC_ON_BOOT`, standaard uit) en kunnen per repo op een commit worden
vastgezet.
- Productie-WSGI-server (waitress) i.p.v. de Flask-ontwikkelserver, limiet op
request-grootte, `ProxyFix`, en CSP/`X-Frame-Options`/`nosniff`/
`Referrer-Policy`-headers.
- Tailwind, Alpine en htmx worden meegeleverd in plaats van vanaf een CDN
geladen; Google Fonts is eruit. De interface werkt nu ook offline.
- `config.json` en de sleutel staan op 0600.
## 🌐 Stacks met een eigen IP-adres
Nieuw: geef een stack een eigen adres in je LAN in plaats van poorten op de host
(macvlan/ipvlan). Geen poortconflicten meer, apps op hun eigen standaardpoort, en
je kunt per app firewallen.
- Netwerkbeheer onder Instellingen → Netwerken (driver, host-interface, subnet,
gateway, optionele IP-range).
- Bij het installeren kies je "Poorten op de host" of een netwerk; Server Up
stelt het eerstvolgende vrije adres voor en houdt toegekende adressen vast.
- Het gerenderde compose-bestand wordt automatisch omgezet: poortmappings eruit,
netwerk met `ipv4_address` erin. De templates in `apps/` blijven ongewijzigd.
- Uitleg en valkuilen (waaronder de shim-interface die de host nodig heeft om
zijn eigen macvlan-containers te bereiken) staan in `docs/netwerken.md`.
## 🧩 Beheer per container
De stackkaart klapt uit naar de losse containers: per container starten, stoppen,
herstarten, logs bekijken en live CPU-/geheugengebruik.
## 📦 Veiliger installeren
- Poortvelden krijgen een vrij poortnummer voorgesteld — `next_free_port()`
bestond al maar werd nergens gebruikt. Bezette poorten worden gemeld.
- Velden voor tokens en wachtwoorden krijgen een genereerknop.
- Compose wordt gevalideerd (`docker compose config`) vóór het wegschrijven, dus
een typefout in de editor maakt een draaiende stack niet meer onstartbaar.
## 🔧 Overig
- Testsuite met pytest (112 tests), ook als stap in beide deploy-workflows.
- Audit-log gebruikt één gedeelde SQLite-verbinding — elke job lekte eerder een
file descriptor. Joblogs worden afgekapt op 2000 regels.
- Het `VERSION`-bestand is de enige bron voor het versienummer.
- Lichte `/healthz` voor de healthcheck in plaats van `docker info`.
- `fix-config.sh` verwijderd: bevatte een hardgecodeerd intern IP en
overschreef de configuratie van de gebruiker.
---
# v0.4.60 — Tweecijferig patch-nummer
## Versiebeleid vanaf v0.4.60

View file

@ -37,19 +37,38 @@ COPY --from=build /inst /usr/local
WORKDIR /app
COPY server-up/ ./
COPY modules/ ./modules-bundled/
COPY VERSION ./VERSION
RUN mkdir -p static/fonts \
&& curl -fsSL "https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" -o static/fonts/mdi.min.css \
&& curl -fsSL "https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/fonts/materialdesignicons-webfont.woff2" -o static/fonts/materialdesignicons-webfont.woff2 \
&& sed -i "s|https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/fonts/||g" static/fonts/mdi.min.css
# Front-end libraries meeleveren in plaats van vanaf een CDN laden. Zo kan de
# Content-Security-Policy op 'self' blijven, is er geen supply-chain-risico van
# unpkg/jsdelivr op runtime, en werkt de UI ook zonder internetverbinding.
RUN mkdir -p static/vendor \
&& curl -fsSL "https://cdn.tailwindcss.com/3.4.16?plugins=forms,typography" \
-o static/vendor/tailwind.js \
&& curl -fsSL "https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js" \
-o static/vendor/alpine.min.js \
&& curl -fsSL "https://cdn.jsdelivr.net/npm/htmx.org@2.0.3/dist/htmx.min.js" \
-o static/vendor/htmx.min.js \
&& for f in static/vendor/tailwind.js static/vendor/alpine.min.js static/vendor/htmx.min.js; do \
test -s "$f" || { echo "LEEG: $f"; exit 1; }; \
done
# Directories + git safe.directory (fix dubious ownership)
RUN mkdir -p /data/stacks /data/appdata /data/backups /data/git modules /root \
&& printf '[safe]\n\tdirectory = *\n' > /root/.gitconfig
# GIT_SSH_COMMAND: StrictHostKeyChecking=accept-new vertrouwt een host bij de
# eerste verbinding en bewaart de sleutel in /data/known_hosts. Wijzigt die
# sleutel later, dan faalt de verbinding — met `no` was elke MITM onzichtbaar.
ENV PYTHONUNBUFFERED=1 PORT=5000 HOME=/root \
SU_VERSION=${SU_VERSION} \
SU_CONFIG=/data/config.json SU_AUDIT=/data/audit.db SU_GIT_CACHE=/data/git \
GIT_SSH_COMMAND="ssh -F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
SU_SECRET=/data/secret.key \
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/data/known_hosts"
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=8s --start-period=15s CMD curl -fs http://localhost:5000/api/docker/info || exit 1
HEALTHCHECK --interval=30s --timeout=8s --start-period=15s CMD curl -fs http://localhost:5000/healthz || exit 1
CMD ["python","app.py"]

137
README.md Normal file
View file

@ -0,0 +1,137 @@
# Server Up
Zelfgehoste beheerinterface voor Docker: apps installeren uit een catalogus,
stacks starten en stoppen, backups maken en terugzetten, en containers een eigen
IP-adres in je netwerk geven.
> ⚠️ **Toegang tot Server Up staat gelijk aan root op de host.** De applicatie
> beheert de Docker-daemon, en wie containers kan starten kan willekeurige mappen
> van de host mounten. Behandel de interface navenant: maak direct een account
> aan, zet hem niet zonder reverse proxy met TLS op internet, en lees
> [docs/beveiliging.md](docs/beveiliging.md).
---
## Wat het doet
| | |
|---|---|
| **App-catalogus** | 96 kant-en-klare apps, ingedeeld in categorieën met kleur. Zoeken, filteren en installeren met een formulier in plaats van YAML. |
| **Stacks beheren** | Starten, stoppen, bijwerken en herstarten — per stack of per losse container, met logs en live CPU-/geheugengebruik. |
| **Apps koppelen** | Stacks komen op een gedeeld netwerk, zodat ze elkaar op naam bereiken. Een veld dat naar een andere app verwijst toont een keuzelijst met wat je al draait. |
| **Eigen IP per stack** | Met macvlan of ipvlan krijgt een app een eigen adres in je LAN in plaats van poorten op de host. |
| **Backups** | Handmatig, gepland, en automatisch vóór een update of verwijdering. Met terugzetten en een bewaarbeleid. |
| **Updates** | Voor Server Up zelf (stable of beta) en voor de images van je geïnstalleerde stacks. |
| **Toegang** | Lokale accounts met rollen (beheerder, operator, alleen lezen), of SSO via een reverse proxy. |
## Wat je nodig hebt
- Een Linux-server met Docker en de compose-plugin
- Toegang tot `/var/run/docker.sock`
- Ongeveer 300 MB schijfruimte voor de applicatie zelf
## Installeren
```bash
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh | sh
```
Dat controleert je systeem, installeert Docker als het ontbreekt, zet Server Up
in `/opt/server-up` en start het op `http://localhost:5000`.
Liever eerst lezen wat je uitvoert — verstandig, want het script gebruikt
root-rechten:
```bash
curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/raw/branch/main/install.sh -o install.sh
less install.sh
sh install.sh --dry-run # toont wat het zou doen, zonder iets te wijzigen
sh install.sh
```
Handmatig kan ook:
```bash
git clone https://git.ramonbesselink.nl/bes-r/server-up.git /opt/server-up
cd /opt/server-up
cp .env.example .env
docker compose up -d --build
```
Alle opties staan in [docs/installeren.md](docs/installeren.md):
`--dir`, `--port`, `--bind`, `--branch`, `--token`, `--update`, `--uninstall`.
Open daarna de interface en **maak meteen een beheerdersaccount aan.**
Zolang dat niet gebeurd is, kan iedereen die de pagina bereikt het account
claimen — de container waarschuwt daar bij het opstarten ook voor.
### Instellingen
Alles staat met uitleg in [`.env.example`](.env.example). De twee die je
waarschijnlijk meteen nodig hebt:
```bash
BIND=127.0.0.1 # 0.0.0.0 om er van buiten de server bij te kunnen
PORT=5000
```
De poort bindt standaard op `127.0.0.1`. Zet hem pas open als er een reverse
proxy met TLS voor staat.
## Documentatie
| Document | Waarover |
|---|---|
| [docs/installeren.md](docs/installeren.md) | Het installatiescript, opties, bijwerken en verwijderen |
| [docs/beveiliging.md](docs/beveiliging.md) | Accounts, rollen, SSO, reverse proxy, wat wel en niet is afgedekt |
| [docs/apps-maken.md](docs/apps-maken.md) | Eigen apps toevoegen: het volledige templateformaat |
| [docs/netwerken.md](docs/netwerken.md) | Stacks een eigen IP geven met macvlan of ipvlan |
| [docs/backups.md](docs/backups.md) | Wat er wél en niet in een backup zit, terugzetten, bewaarbeleid |
| [docs/updates.md](docs/updates.md) | Update-kanalen en de Forgejo-registry inrichten |
| [docs/synchroniseren.md](docs/synchroniseren.md) | Code van je apparaat naar de server via Forgejo |
| [docs/forgejo-actions-setup.md](docs/forgejo-actions-setup.md) | Runners instellen |
## Eigen apps toevoegen
Wijs Server Up naar je eigen git-repo met app-sjablonen (Instellingen → App
Store → Repository), of voeg een map toe onder `apps/`:
```
apps/mijn-app/
├── template.json metadata en de vragen bij het installeren
└── files/
└── compose.yaml het compose-bestand met << variabelen >>
```
Het volledige formaat staat in [docs/apps-maken.md](docs/apps-maken.md).
## Bijwerken en verwijderen
```bash
sh /opt/server-up/install.sh --update # nieuwste versie ophalen en herstarten
sh /opt/server-up/install.sh --uninstall # stoppen; je gegevens blijven staan
```
Draait Server Up vanaf een registry-image, dan kan bijwerken ook met een knop in
de interface — zie [docs/updates.md](docs/updates.md).
## Ontwikkelen
```bash
python3 -m venv venv && venv/bin/pip install -r server-up/requirements.txt pytest
venv/bin/python -m pytest tests -q # de volledige suite
cd server-up && SU_DEV=1 python app.py # lokaal draaien op poort 5000
```
De testsuite draait ook over alle app-sjablonen: elke app moet renderen naar
geldige compose-YAML, met gedeclareerde volumes en zonder onvervangen
variabelen. Voeg je een app toe, dan wordt die automatisch meegenomen.
De deploy-workflows draaien de tests in een container (`python:3.12-slim`), dus
op de runner is verder niets nodig dan Docker.
## Licentie en bijdragen
Zie `CHANGELOG.md` voor de geschiedenis. Werk op de `dev`-branch en bump
`VERSION` bij elke wijziging; een test bewaakt dat er een changelog-sectie bij
hoort.

View file

@ -1 +1 @@
0.4.60
0.7.30-beta

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Actual Budget",
"description": "Lokaal persoonlijk financieel beheer en budgettering zonder cloudafhankelijkheid.",
"tags": ["financiën", "budget", "persoonlijk", "privacy"],
"icon": {"provider": "selfhst", "id": "actual-budget"},
"version": {"name": "latest"}
"tags": [
"financiën",
"budget",
"persoonlijk",
"privacy"
],
"icon": {
"provider": "selfhst",
"id": "actual-budget"
},
"version": {
"name": "latest"
},
"categories": [
"financien"
]
},
"variables": [
{

View file

@ -3,9 +3,23 @@
"metadata": {
"name": "AdGuard Home",
"description": "DNS-level advertentie- en trackerblokkering voor het hele netwerk",
"tags": ["dns", "adblock", "netwerk", "privacy"],
"icon": {"provider": "selfhst", "id": "adguard-home"},
"version": {"name": "latest"}
"tags": [
"dns",
"adblock",
"netwerk",
"privacy"
],
"icon": {
"provider": "selfhst",
"id": "adguard-home"
},
"version": {
"name": "latest"
},
"categories": [
"netwerk",
"beveiliging"
]
},
"variables": [
{

View file

@ -1,19 +1,13 @@
# ARR-stack — kies per onderdeel of je het wilt.
#
# Aandachtspunten:
# * Zet de download- en mediamap op hetzelfde bestandssysteem, anders kopieert
# elke voltooide download in plaats van te verplaatsen (traag, dubbele opslag).
# * Draai je met Gluetun, dan zitten de downloadclients in het netwerk van die
# container en bereik je hun webinterface via de Gluetun-poorten.
services:
<< service_name >>-radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: << service_name >>-radarr
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/radarr:/config
- << media_dir >>/films:/movies
- << downloads_dir >>:/downloads
ports:
- "<< port_radarr >>:7878"
restart: unless-stopped
<%- if enable_sonarr %>
<< service_name >>-sonarr:
image: lscr.io/linuxserver/sonarr:latest
container_name: << service_name >>-sonarr
@ -28,7 +22,26 @@ services:
ports:
- "<< port_sonarr >>:8989"
restart: unless-stopped
<%- endif %>
<%- if enable_radarr %>
<< service_name >>-radarr:
image: lscr.io/linuxserver/radarr:latest
container_name: << service_name >>-radarr
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/radarr:/config
- << media_dir >>/films:/movies
- << downloads_dir >>:/downloads
ports:
- "<< port_radarr >>:7878"
restart: unless-stopped
<%- endif %>
<%- if enable_lidarr %>
<< service_name >>-lidarr:
image: lscr.io/linuxserver/lidarr:latest
container_name: << service_name >>-lidarr
@ -43,7 +56,9 @@ services:
ports:
- "<< port_lidarr >>:8686"
restart: unless-stopped
<%- endif %>
<%- if enable_readarr %>
<< service_name >>-readarr:
image: lscr.io/linuxserver/readarr:develop
container_name: << service_name >>-readarr
@ -58,7 +73,26 @@ services:
ports:
- "<< port_readarr >>:8787"
restart: unless-stopped
<%- endif %>
<%- if enable_whisparr %>
<< service_name >>-whisparr:
image: ghcr.io/hotio/whisparr:latest
container_name: << service_name >>-whisparr
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/whisparr:/config
- << media_dir >>/volwassen:/adult
- << downloads_dir >>:/downloads
ports:
- "<< port_whisparr >>:6969"
restart: unless-stopped
<%- endif %>
<%- if enable_prowlarr %>
<< service_name >>-prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
container_name: << service_name >>-prowlarr
@ -71,7 +105,24 @@ services:
ports:
- "<< port_prowlarr >>:9696"
restart: unless-stopped
<%- endif %>
<%- if enable_jackett %>
<< service_name >>-jackett:
image: lscr.io/linuxserver/jackett:latest
container_name: << service_name >>-jackett
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/jackett:/config
ports:
- "<< port_jackett >>:9117"
restart: unless-stopped
<%- endif %>
<%- if enable_bazarr %>
<< service_name >>-bazarr:
image: lscr.io/linuxserver/bazarr:latest
container_name: << service_name >>-bazarr
@ -86,14 +137,226 @@ services:
ports:
- "<< port_bazarr >>:6767"
restart: unless-stopped
<%- endif %>
<%- if enable_flaresolverr %>
<< service_name >>-flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
container_name: << service_name >>-flaresolverr
environment:
- TZ=<< timezone >>
- LOG_LEVEL=info
ports:
- "<< port_flaresolverr >>:8191"
restart: unless-stopped
<%- endif %>
<%- if enable_gluetun %>
<< service_name >>-gluetun:
image: qmcgaw/gluetun:latest
container_name: << service_name >>-gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
- TZ=<< timezone >>
- VPN_SERVICE_PROVIDER=<< vpn_provider >>
- VPN_TYPE=<< vpn_type >>
- WIREGUARD_PRIVATE_KEY=<< wireguard_private_key >>
- WIREGUARD_ADDRESSES=<< wireguard_addresses >>
- OPENVPN_USER=<< openvpn_user >>
- OPENVPN_PASSWORD=<< openvpn_password >>
ports:
# De downloadclients delen dit netwerk, dus hun poorten staan hier.
<%- if enable_qbittorrent %>
- "<< port_qbittorrent >>:8090"
- "<< port_qbittorrent_tcp >>:6881"
- "<< port_qbittorrent_tcp >>:6881/udp"
<%- endif %>
<%- if enable_sabnzbd %>
- "<< port_sabnzbd >>:8080"
<%- endif %>
<%- if enable_nzbget %>
- "<< port_nzbget >>:6789"
<%- endif %>
volumes:
- << data_dir >>/<< service_name >>/gluetun:/gluetun
restart: unless-stopped
<%- endif %>
<%- if enable_qbittorrent %>
<< service_name >>-qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: << service_name >>-qbittorrent
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
- WEBUI_PORT=8090
volumes:
- << data_dir >>/<< service_name >>/qbittorrent:/config
- << downloads_dir >>:/downloads
<%- if enable_gluetun %>
network_mode: "service:<< service_name >>-gluetun"
depends_on:
- << service_name >>-gluetun
<%- endif %>
<%- if not enable_gluetun %>
ports:
- "<< port_qbittorrent >>:8090"
- "<< port_qbittorrent_tcp >>:6881"
- "<< port_qbittorrent_tcp >>:6881/udp"
<%- endif %>
restart: unless-stopped
<%- endif %>
<%- if enable_sabnzbd %>
<< service_name >>-sabnzbd:
image: lscr.io/linuxserver/sabnzbd:latest
container_name: << service_name >>-sabnzbd
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/sabnzbd:/config
- << downloads_dir >>:/downloads
<%- if enable_gluetun %>
network_mode: "service:<< service_name >>-gluetun"
depends_on:
- << service_name >>-gluetun
<%- endif %>
<%- if not enable_gluetun %>
ports:
- "<< port_sabnzbd >>:8080"
<%- endif %>
restart: unless-stopped
<%- endif %>
<%- if enable_nzbget %>
<< service_name >>-nzbget:
image: lscr.io/linuxserver/nzbget:latest
container_name: << service_name >>-nzbget
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/nzbget:/config
- << downloads_dir >>:/downloads
<%- if enable_gluetun %>
network_mode: "service:<< service_name >>-gluetun"
depends_on:
- << service_name >>-gluetun
<%- endif %>
<%- if not enable_gluetun %>
ports:
- "<< port_nzbget >>:6789"
<%- endif %>
restart: unless-stopped
<%- endif %>
<%- if enable_jellyseerr %>
<< service_name >>-jellyseerr:
image: fallenbagel/jellyseerr:latest
container_name: << service_name >>-jellyseerr
environment:
- TZ=<< timezone >>
- LOG_LEVEL=info
volumes:
- << data_dir >>/<< service_name >>/jellyseerr:/app/config
ports:
- "<< port_jellyseerr >>:5055"
restart: unless-stopped
<%- endif %>
<%- if enable_recyclarr %>
<< service_name >>-recyclarr:
image: ghcr.io/recyclarr/recyclarr:latest
container_name: << service_name >>-recyclarr
user: << puid >>:<< pgid >>
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/recyclarr:/config
restart: unless-stopped
<%- endif %>
<%- if enable_unpackerr %>
<< service_name >>-unpackerr:
image: ghcr.io/unpackerr/unpackerr:latest
container_name: << service_name >>-unpackerr
user: << puid >>:<< pgid >>
environment:
- TZ=<< timezone >>
- UN_SONARR_0_URL=http://<< service_name >>-sonarr:8989
- UN_RADARR_0_URL=http://<< service_name >>-radarr:7878
volumes:
- << data_dir >>/<< service_name >>/unpackerr:/config
- << downloads_dir >>:/downloads
restart: unless-stopped
<%- endif %>
<%- if enable_tdarr %>
<< service_name >>-tdarr:
image: ghcr.io/haveagitgat/tdarr:latest
container_name: << service_name >>-tdarr
environment:
- PUID=<< puid >>
- PGID=<< pgid >>
- TZ=<< timezone >>
- serverIP=0.0.0.0
- serverPort=8266
- webUIPort=8265
- internalNode=true
volumes:
- << data_dir >>/<< service_name >>/tdarr/server:/app/server
- << data_dir >>/<< service_name >>/tdarr/configs:/app/configs
- << media_dir >>:/media
- << downloads_dir >>/tdarr-cache:/temp
ports:
- "<< port_tdarr >>:8265"
- "<< port_tdarr_server >>:8266"
restart: unless-stopped
<%- endif %>
<%- if enable_autobrr %>
<< service_name >>-autobrr:
image: ghcr.io/autobrr/autobrr:latest
container_name: << service_name >>-autobrr
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/autobrr:/config
ports:
- "<< port_autobrr >>:7474"
restart: unless-stopped
<%- endif %>
<%- if enable_maintainerr %>
<< service_name >>-maintainerr:
image: ghcr.io/jorenn92/maintainerr:latest
container_name: << service_name >>-maintainerr
user: << puid >>:<< pgid >>
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/maintainerr:/opt/data
ports:
- "<< port_maintainerr >>:6246"
restart: unless-stopped
<%- endif %>
<%- if enable_huntarr %>
<< service_name >>-huntarr:
image: ghcr.io/plexguide/huntarr:latest
container_name: << service_name >>-huntarr
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/huntarr:/config
ports:
- "<< port_huntarr >>:9705"
restart: unless-stopped
<%- endif %>

View file

@ -2,10 +2,25 @@
"kind": "compose",
"metadata": {
"name": "ARR-stack",
"description": "Complete media-automatiseringsstack: Radarr (films), Sonarr (series), Lidarr (muziek), Readarr (boeken), Prowlarr (indexers), Bazarr (ondertitels) en Jellyseerr (verzoeken).",
"tags": ["media", "automatisering", "radarr", "sonarr", "prowlarr", "bazarr", "jellyseerr"],
"icon": {"provider": "selfhst", "id": "radarr"},
"version": {"name": "latest"}
"description": "Media-automatisering met een schakelaar per onderdeel: Sonarr, Radarr, Lidarr, Readarr, Whisparr, Prowlarr, Jackett, Bazarr, FlareSolverr, qBittorrent, SABnzbd, NZBGet, Gluetun (VPN), Jellyseerr, Recyclarr, Unpackerr, Tdarr, Autobrr, Maintainerr en Huntarr.",
"tags": [
"media",
"automatisering",
"films",
"series",
"downloaden"
],
"icon": {
"provider": "selfhst",
"id": "sonarr"
},
"version": {
"name": "latest"
},
"categories": [
"media",
"downloaden"
]
},
"variables": [
{
@ -14,34 +29,34 @@
{
"name": "service_name",
"type": "str",
"title": "Servicenaam (prefix)",
"title": "Naam van de stack",
"default": "arr",
"required": true,
"description": "Prefix voor alle containers in de stack, bijv. 'arr' geeft arr-radarr, arr-sonarr, etc."
"description": "Wordt het voorvoegsel van elke container, bv. arr-sonarr"
},
{
"name": "data_dir",
"type": "str",
"title": "Config directory",
"title": "Configuratiemap",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor configuratiebestanden van alle ARR-apps"
"required": true
},
{
"name": "media_dir",
"type": "str",
"title": "Media directory",
"title": "Mediamap",
"default": "/mnt/media",
"required": true,
"description": "Map voor films, series en muziek. Moet ook zichtbaar zijn voor de download-client."
"description": "Hier komen films/, series/, muziek/ en boeken/ onder te staan"
},
{
"name": "downloads_dir",
"type": "str",
"title": "Downloads directory",
"title": "Downloadmap",
"default": "/mnt/downloads",
"required": true,
"description": "Map waar de download-client bestanden neerzet (qBittorrent of SABnzbd). Moet overeenkomen met het pad in de download-client."
"description": "Zet deze op hetzelfde bestandssysteem als de mediamap, anders kopieert elke download in plaats van te verplaatsen",
"help": "Zet deze map op hetzelfde bestandssysteem als je mediamap. Staan ze op verschillende schijven of volumes, dan kopieert elke voltooide download in plaats van te verplaatsen: traag, en je hebt alles tijdelijk dubbel."
},
{
"name": "timezone",
@ -49,84 +64,366 @@
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
},
{
"title": "Gebruiker",
"items": [
},
{
"name": "puid",
"type": "int",
"title": "PUID",
"title": "Gebruikers-ID (PUID)",
"default": 1000,
"required": true,
"description": "User ID van de systeemgebruiker (id -u)"
"description": "`id -u` op je host; bepaalt de eigenaar van bestanden",
"help": "Het gebruikersnummer waaronder de containers draaien. Klopt dit niet met de eigenaar van je mediamap, dan kunnen de apps er niet in schrijven. Vind het met `id -u` op je server."
},
{
"name": "pgid",
"type": "int",
"title": "PGID",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"description": "Group ID van de systeemgebruiker (id -g)"
"required": true
}
]
},
{
"title": "Poorten",
"title": "Sonarr — series",
"items": [
{
"name": "port_sonarr",
"type": "int",
"title": "Poort",
"default": 8989,
"required": true
}
],
"toggle": "enable_sonarr",
"toggle_default": true,
"description": "Zoekt, downloadt en hernoemt series"
},
{
"title": "Radarr — films",
"items": [
{
"name": "port_radarr",
"type": "int",
"title": "Radarr poort",
"title": "Poort",
"default": 7878,
"required": true
},
{
"name": "port_sonarr",
"type": "int",
"title": "Sonarr poort",
"default": 8989,
"required": true
},
}
],
"toggle": "enable_radarr",
"toggle_default": true,
"description": "Zoekt, downloadt en hernoemt films"
},
{
"title": "Lidarr — muziek",
"items": [
{
"name": "port_lidarr",
"type": "int",
"title": "Lidarr poort",
"title": "Poort",
"default": 8686,
"required": true
},
}
],
"toggle": "enable_lidarr",
"toggle_default": false
},
{
"title": "Readarr — boeken",
"items": [
{
"name": "port_readarr",
"type": "int",
"title": "Readarr poort",
"title": "Poort",
"default": 8787,
"required": true
},
}
],
"toggle": "enable_readarr",
"toggle_default": false,
"description": "Let op: de ontwikkeling van Readarr is stilgelegd; het draait nog wel"
},
{
"title": "Whisparr — volwassenen",
"items": [
{
"name": "port_whisparr",
"type": "int",
"title": "Poort",
"default": 6969,
"required": true
}
],
"toggle": "enable_whisparr",
"toggle_default": false
},
{
"title": "Prowlarr — indexerbeheer",
"items": [
{
"name": "port_prowlarr",
"type": "int",
"title": "Prowlarr poort",
"title": "Poort",
"default": 9696,
"required": true
},
}
],
"toggle": "enable_prowlarr",
"toggle_default": true,
"description": "Beheert je indexers centraal en duwt ze naar de andere *arr-apps"
},
{
"title": "Jackett — indexerproxy",
"items": [
{
"name": "port_jackett",
"type": "int",
"title": "Poort",
"default": 9117,
"required": true
}
],
"toggle": "enable_jackett",
"toggle_default": false,
"description": "Ouder alternatief voor Prowlarr; meestal heb je er maar één nodig"
},
{
"title": "FlareSolverr — Cloudflare omzeilen",
"items": [
{
"name": "port_flaresolverr",
"type": "int",
"title": "Poort",
"default": 8191,
"required": true
}
],
"toggle": "enable_flaresolverr",
"toggle_default": false,
"description": "Nodig voor indexers die achter een Cloudflare-controle zitten"
},
{
"title": "Bazarr — ondertitels",
"items": [
{
"name": "port_bazarr",
"type": "int",
"title": "Bazarr poort",
"title": "Poort",
"default": 6767,
"required": true
}
],
"toggle": "enable_bazarr",
"toggle_default": true
},
{
"title": "qBittorrent — torrents",
"items": [
{
"name": "port_qbittorrent",
"type": "int",
"title": "Webinterface",
"default": 8097,
"required": true
},
{
"name": "port_qbittorrent_tcp",
"type": "int",
"title": "Inkomende poort",
"default": 6881,
"required": true
}
],
"toggle": "enable_qbittorrent",
"toggle_default": true,
"description": "Torrentclient met webinterface"
},
{
"title": "SABnzbd — usenet",
"items": [
{
"name": "port_sabnzbd",
"type": "int",
"title": "Poort",
"default": 8098,
"required": true
}
],
"toggle": "enable_sabnzbd",
"toggle_default": false
},
{
"title": "NZBGet — usenet (lichter)",
"items": [
{
"name": "port_nzbget",
"type": "int",
"title": "Poort",
"default": 6789,
"required": true
}
],
"toggle": "enable_nzbget",
"toggle_default": false,
"description": "Zuiniger alternatief voor SABnzbd"
},
{
"title": "Gluetun — VPN voor de downloadclient",
"items": [
{
"name": "vpn_provider",
"type": "str",
"title": "VPN-aanbieder",
"default": "mullvad",
"required": true,
"description": "Bijvoorbeeld mullvad, nordvpn, protonvpn of surfshark"
},
{
"name": "vpn_type",
"type": "enum",
"title": "Protocol",
"default": "wireguard",
"required": true,
"config": {
"options": [
"wireguard",
"openvpn"
]
}
},
{
"name": "wireguard_private_key",
"type": "str",
"title": "WireGuard privésleutel",
"default": "",
"required": false,
"config": {
"placeholder": "alleen bij wireguard"
}
},
{
"name": "wireguard_addresses",
"type": "str",
"title": "WireGuard adressen",
"default": "",
"required": false,
"config": {
"placeholder": "10.64.0.2/32"
}
},
{
"name": "openvpn_user",
"type": "str",
"title": "OpenVPN gebruiker",
"default": "",
"required": false
},
{
"name": "openvpn_password",
"type": "str",
"title": "OpenVPN wachtwoord",
"default": "",
"required": false
}
],
"toggle": "enable_gluetun",
"toggle_default": false,
"description": "Leidt al je downloadverkeer door een VPN. De downloadclients delen dan het netwerk van deze container, dus hun poorten komen hier te liggen."
},
{
"title": "Jellyseerr — verzoeken",
"items": [
{
"name": "port_jellyseerr",
"type": "int",
"title": "Jellyseerr poort",
"title": "Poort",
"default": 5055,
"required": true,
"description": "Webinterface voor gebruikers om films en series aan te vragen"
"required": true
}
]
],
"toggle": "enable_jellyseerr",
"toggle_default": true,
"description": "Laat huisgenoten films en series aanvragen"
},
{
"title": "Recyclarr — TRaSH-instellingen",
"items": [],
"toggle": "enable_recyclarr",
"toggle_default": false,
"description": "Synchroniseert kwaliteitsprofielen uit de TRaSH-gidsen naar Sonarr en Radarr"
},
{
"title": "Unpackerr — archieven uitpakken",
"items": [],
"toggle": "enable_unpackerr",
"toggle_default": false,
"description": "Pakt gedownloade rar-archieven automatisch uit"
},
{
"title": "Tdarr — hercoderen",
"items": [
{
"name": "port_tdarr",
"type": "int",
"title": "Webinterface",
"default": 8265,
"required": true
},
{
"name": "port_tdarr_server",
"type": "int",
"title": "Serverpoort",
"default": 8266,
"required": true
}
],
"toggle": "enable_tdarr",
"toggle_default": false,
"description": "Zet je bibliotheek om naar een zuiniger formaat"
},
{
"title": "Autobrr — automatisch grijpen",
"items": [
{
"name": "port_autobrr",
"type": "int",
"title": "Poort",
"default": 7474,
"required": true
}
],
"toggle": "enable_autobrr",
"toggle_default": false,
"description": "Reageert direct op aankondigingen in plaats van periodiek te zoeken"
},
{
"title": "Maintainerr — opruimen",
"items": [
{
"name": "port_maintainerr",
"type": "int",
"title": "Poort",
"default": 6246,
"required": true
}
],
"toggle": "enable_maintainerr",
"toggle_default": false,
"description": "Ruimt media op volgens regels die je zelf instelt"
},
{
"title": "Huntarr — gaten opvullen",
"items": [
{
"name": "port_huntarr",
"type": "int",
"title": "Poort",
"default": 9705,
"required": true
}
],
"toggle": "enable_huntarr",
"toggle_default": false,
"description": "Zoekt gericht naar ontbrekende en te verbeteren afleveringen"
}
]
}

View file

@ -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

View file

@ -0,0 +1,64 @@
{
"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"
},
"categories": [
"media"
]
},
"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
}
]
}
]
}

View file

@ -0,0 +1,15 @@
services:
<< service_name >>:
image: authelia/authelia:latest
container_name: << service_name >>
ports:
- "<< port >>:9091"
environment:
- TZ=<< timezone >>
- AUTHELIA_JWT_SECRET=<< jwt_secret >>
- AUTHELIA_SESSION_SECRET=<< session_secret >>
- AUTHELIA_STORAGE_ENCRYPTION_KEY=<< storage_key >>
volumes:
# Zet hier configuration.yml en users_database.yml neer voor de eerste start.
- << data_dir >>/<< service_name >>/config:/config
restart: unless-stopped

View file

@ -0,0 +1,93 @@
{
"kind": "compose",
"metadata": {
"name": "Authelia",
"description": "Inlogportaal met tweestapsverificatie voor al je diensten achter een reverse proxy. Werkt samen met de SSO-instelling van Server Up.",
"tags": [
"beveiliging",
"sso",
"authenticatie"
],
"icon": {
"provider": "selfhst",
"id": "authelia"
},
"version": {
"name": "latest"
},
"categories": [
"beveiliging"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "authelia",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 9091,
"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": "jwt_secret",
"type": "str",
"title": "JWT-sleutel",
"default": "",
"required": true,
"description": "Gebruik de dobbelsteenknop voor een willekeurige waarde",
"config": {
"placeholder": "sterk-wachtwoord"
},
"advanced": true
},
{
"name": "session_secret",
"type": "str",
"title": "Sessiesleutel",
"default": "",
"required": true,
"description": "Gebruik de dobbelsteenknop voor een willekeurige waarde",
"config": {
"placeholder": "sterk-wachtwoord"
},
"advanced": true
},
{
"name": "storage_key",
"type": "str",
"title": "Opslagversleuteling",
"default": "",
"required": true,
"description": "Gebruik de dobbelsteenknop voor een willekeurige waarde",
"config": {
"placeholder": "sterk-wachtwoord"
},
"advanced": true
}
]
}
]
}

View file

@ -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

View file

@ -0,0 +1,94 @@
{
"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"
},
"categories": [
"gezin"
]
},
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": 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",
"advanced": true
}
]
}
]
}

View file

@ -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

View file

@ -0,0 +1,74 @@
{
"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"
},
"categories": [
"3d-printen"
]
},
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": true
}
]
}
]
}

View file

@ -0,0 +1,12 @@
services:
<< service_name >>:
image: baserow/baserow:latest
container_name: << service_name >>
ports:
- "<< port >>:80"
environment:
- TZ=<< timezone >>
- BASEROW_PUBLIC_URL=<< public_url >>
volumes:
- << data_dir >>/<< service_name >>:/baserow/data
restart: unless-stopped

View file

@ -0,0 +1,66 @@
{
"kind": "compose",
"metadata": {
"name": "Baserow",
"description": "Databases opbouwen zonder te programmeren, met formulieren en weergaven.",
"tags": [
"database",
"no-code",
"formulieren"
],
"icon": {
"provider": "selfhst",
"id": "baserow"
},
"version": {
"name": "latest"
},
"categories": [
"ontwikkeling",
"productiviteit"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "baserow",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8099,
"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": "public_url",
"type": "str",
"title": "Publieke URL",
"default": "http://localhost:8098",
"required": true,
"description": "Zonder dit kloppen links en uploads niet"
}
]
}
]
}

View file

@ -0,0 +1,23 @@
services:
<< service_name >>:
image: henrygd/beszel:latest
container_name: << service_name >>
ports:
- "<< port >>:8090"
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/data:/beszel_data
restart: unless-stopped
<< service_name >>-agent:
image: henrygd/beszel-agent:latest
container_name: << service_name >>-agent
network_mode: host
environment:
- TZ=<< timezone >>
- PORT=<< agent_port >>
- KEY=<< agent_key >>
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped

73
apps/beszel/template.json Normal file
View file

@ -0,0 +1,73 @@
{
"kind": "compose",
"metadata": {
"name": "Beszel",
"description": "Lichte servermonitoring met grafieken en alarmen, zonder een hele Prometheus-opstelling.",
"tags": [
"monitoring",
"systeem",
"lichtgewicht"
],
"icon": {
"provider": "selfhst",
"id": "beszel"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "beszel",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8100,
"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": "agent_port",
"type": "int",
"title": "Agent-poort",
"default": 45876,
"required": true,
"description": "De agent meet deze host; extra servers wijs je later aan"
},
{
"name": "agent_key",
"type": "str",
"title": "Publieke sleutel agent",
"default": "",
"required": false,
"description": "Laat leeg bij de eerste installatie; je vindt hem in de webinterface"
}
]
}
]
}

View file

@ -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

View file

@ -0,0 +1,94 @@
{
"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"
},
"categories": [
"documenten"
]
},
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": true
},
{
"name": "db_password",
"type": "str",
"title": "Database wachtwoord",
"default": "",
"required": true,
"description": "Gebruik de dobbelsteenknop voor een willekeurige waarde",
"config": {
"placeholder": "sterk-wachtwoord"
},
"advanced": true
},
{
"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"
}
]
}
]
}

View file

@ -0,0 +1,20 @@
services:
<< service_name >>:
image: caddy:2
container_name: << service_name >>
ports:
- "<< port >>:80"
- "<< https_port >>:443"
- "<< https_port >>:443/udp"
environment:
- TZ=<< timezone >>
volumes:
# Zet hier je Caddyfile neer; Caddy regelt de certificaten daarna zelf.
- << data_dir >>/<< service_name >>/Caddyfile:/etc/caddy/Caddyfile
- << service_name >>_data:/data
- << service_name >>_config:/config
restart: unless-stopped
volumes:
<< service_name >>_data:
<< service_name >>_config:

66
apps/caddy/template.json Normal file
View file

@ -0,0 +1,66 @@
{
"kind": "compose",
"metadata": {
"name": "Caddy",
"description": "Reverse proxy die HTTPS-certificaten volledig zelf regelt. Eenvoudiger in te stellen dan Traefik of Nginx.",
"tags": [
"proxy",
"netwerk",
"https"
],
"icon": {
"provider": "selfhst",
"id": "caddy"
},
"version": {
"name": "2"
},
"categories": [
"netwerk",
"beheer"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "caddy",
"required": true
},
{
"name": "port",
"type": "int",
"title": "HTTP poort",
"default": 80,
"required": true,
"help": "Caddy vraagt zelf certificaten aan bij Let's Encrypt en heeft daarvoor poort 80 nodig, ook als je alles via HTTPS aanbiedt."
},
{
"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": "https_port",
"type": "int",
"title": "HTTPS poort",
"default": 443,
"required": true
}
]
}
]
}

View file

@ -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

View file

@ -0,0 +1,83 @@
{
"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"
},
"categories": [
"media",
"documenten"
]
},
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": 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"
}
]
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Changedetection.io",
"description": "Monitor websites op wijzigingen en ontvang meldingen zodra er iets verandert.",
"tags": ["monitoring", "websites", "alerting", "automatisering"],
"icon": {"provider": "selfhst", "id": "changedetection-io"},
"version": {"name": "latest"}
"tags": [
"monitoring",
"websites",
"alerting",
"automatisering"
],
"icon": {
"provider": "selfhst",
"id": "changedetection-io"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring"
]
},
"variables": [
{
@ -44,7 +57,9 @@
"default": "",
"required": false,
"description": "Optioneel wachtwoord om de webinterface te beveiligen. Laat leeg voor geen beveiliging.",
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}

View file

@ -0,0 +1,9 @@
services:
<< service_name >>:
image: mpepping/cyberchef:latest
container_name: << service_name >>
ports:
- "<< port >>:8000"
environment:
- TZ=<< timezone >>
restart: unless-stopped

View file

@ -0,0 +1,51 @@
{
"kind": "compose",
"metadata": {
"name": "CyberChef",
"description": "Zwitsers zakmes voor coderen, decoderen, hashen en data omzetten. Draait volledig lokaal.",
"tags": [
"hulpmiddel",
"encoding",
"ontwikkelaar"
],
"icon": {
"provider": "selfhst",
"id": "cyberchef"
},
"version": {
"name": "latest"
},
"categories": [
"ontwikkeling",
"beveiliging"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "cyberchef",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8106,
"required": true
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -3,9 +3,23 @@
"metadata": {
"name": "Dozzle",
"description": "Realtime Docker container log viewer in je browser. Geen data opgeslagen, lichtgewicht.",
"tags": ["docker", "logs", "monitoring", "beheer"],
"icon": {"provider": "selfhst", "id": "dozzle"},
"version": {"name": "latest"}
"tags": [
"docker",
"logs",
"monitoring",
"beheer"
],
"icon": {
"provider": "selfhst",
"id": "dozzle"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring",
"beheer"
]
},
"variables": [
{

View file

@ -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

View file

@ -0,0 +1,88 @@
{
"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"
},
"categories": [
"opslag"
]
},
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": 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
}
]
}
]
}

View file

@ -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

View file

@ -0,0 +1,58 @@
{
"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"
},
"categories": [
"domotica",
"ontwikkeling"
]
},
"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
}
]
}
]
}

View file

@ -0,0 +1,9 @@
services:
<< service_name >>:
image: excalidraw/excalidraw:latest
container_name: << service_name >>
ports:
- "<< port >>:80"
environment:
- TZ=<< timezone >>
restart: unless-stopped

View file

@ -0,0 +1,50 @@
{
"kind": "compose",
"metadata": {
"name": "Excalidraw",
"description": "Schetsen en diagrammen tekenen in een handgetekende stijl. Draait volledig in de browser.",
"tags": [
"tekenen",
"diagram",
"samenwerken"
],
"icon": {
"provider": "selfhst",
"id": "excalidraw"
},
"version": {
"name": "latest"
},
"categories": [
"productiviteit"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "excalidraw",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8101,
"required": true
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
}
]
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "File Browser",
"description": "Webgebaseerde bestandsbeheerder voor je server: uploaden, downloaden, delen en bewerken.",
"tags": ["bestanden", "beheer", "cloud", "opslag"],
"icon": {"provider": "selfhst", "id": "filebrowser"},
"version": {"name": "latest"}
"tags": [
"bestanden",
"beheer",
"cloud",
"opslag"
],
"icon": {
"provider": "selfhst",
"id": "filebrowser"
},
"version": {
"name": "latest"
},
"categories": [
"opslag"
]
},
"variables": [
{

View file

@ -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:

View file

@ -0,0 +1,81 @@
{
"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"
},
"categories": [
"financien"
]
},
"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"
},
"advanced": true
},
{
"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"
},
"advanced": true
}
]
}
]
}

View file

@ -0,0 +1,11 @@
services:
<< service_name >>:
image: mattermost/focalboard:latest
container_name: << service_name >>
ports:
- "<< port >>:8000"
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>:/opt/focalboard/data
restart: unless-stopped

View file

@ -0,0 +1,57 @@
{
"kind": "compose",
"metadata": {
"name": "Focalboard",
"description": "Taken en projecten bijhouden in borden, tabellen en kalenders.",
"tags": [
"productiviteit",
"taken",
"projecten"
],
"icon": {
"provider": "selfhst",
"id": "focalboard"
},
"version": {
"name": "latest"
},
"categories": [
"productiviteit"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "focalboard",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8102,
"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
}
]
}
]
}

View file

@ -3,9 +3,24 @@
"metadata": {
"name": "Forgejo MCP Server",
"description": "MCP-server voor Forgejo en Gitea. Laat AI-assistenten zoals Claude je repositories, issues, pull requests en releases beheren via natuurlijke taal.",
"tags": ["mcp", "ai", "forgejo", "gitea", "claude"],
"icon": {"provider": "selfhst", "id": "forgejo"},
"version": {"name": "latest"}
"tags": [
"mcp",
"ai",
"forgejo",
"gitea",
"claude"
],
"icon": {
"provider": "selfhst",
"id": "forgejo"
},
"version": {
"name": "latest"
},
"categories": [
"ontwikkeling",
"ai"
]
},
"variables": [
{
@ -38,7 +53,9 @@
"default": "https://jouw-forgejo-url",
"required": true,
"description": "Volledige URL van je Forgejo- of Gitea-instantie",
"config": {"placeholder": "https://git.example.com"}
"config": {
"placeholder": "https://git.example.com"
}
},
{
"name": "forgejo_token",
@ -47,7 +64,9 @@
"default": "",
"required": true,
"description": "Maak een token aan via Forgejo → Instellingen → Toepassingen → Access Tokens. Geef minimaal 'repository' en 'issue' schrijfrechten.",
"config": {"placeholder": "jouw-api-token"}
"config": {
"placeholder": "jouw-api-token"
}
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Forgejo",
"description": "Lichtgewicht zelfgehoste Git-server. Actief onderhouden community-fork van Gitea.",
"tags": ["git", "code", "versiebeheer", "ontwikkeling"],
"icon": {"provider": "selfhst", "id": "forgejo"},
"version": {"name": "latest"}
"tags": [
"git",
"code",
"versiebeheer",
"ontwikkeling"
],
"icon": {
"provider": "selfhst",
"id": "forgejo"
},
"version": {
"name": "latest"
},
"categories": [
"ontwikkeling"
]
},
"variables": [
{

View file

@ -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

View file

@ -0,0 +1,72 @@
{
"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"
},
"categories": [
"documenten"
]
},
"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
}
]
}
]
}

View file

@ -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

View file

@ -0,0 +1,76 @@
{
"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"
},
"categories": [
"domotica",
"media"
]
},
"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,
"advanced": 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)",
"advanced": true,
"help": "Frigate houdt camerabeelden in gedeeld geheugen. Reken op ongeveer 64 MB per camera bovenop de standaard; te weinig leidt tot vastlopers zonder duidelijke foutmelding."
}
]
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Ghost",
"description": "Professioneel blogplatform en CMS met abonnementen, nieuwsbrieven en moderne editor.",
"tags": ["blog", "cms", "publiceren", "nieuwsbrief"],
"icon": {"provider": "selfhst", "id": "ghost"},
"version": {"name": "latest"}
"tags": [
"blog",
"cms",
"publiceren",
"nieuwsbrief"
],
"icon": {
"provider": "selfhst",
"id": "ghost"
},
"version": {
"name": "latest"
},
"categories": [
"documenten"
]
},
"variables": [
{
@ -44,7 +57,9 @@
"default": "http://localhost",
"required": true,
"description": "Volledige URL van je Ghost-site, inclusief protocol",
"config": {"placeholder": "https://mijn-blog.nl"}
"config": {
"placeholder": "https://mijn-blog.nl"
}
}
]
},
@ -57,7 +72,9 @@
"title": "Database wachtwoord",
"default": "ghost_db_pass",
"required": true,
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Gotify",
"description": "Eenvoudige push-notificatieserver voor je eigen apparaten, scripts en apps.",
"tags": ["notificaties", "push", "messaging", "alerts"],
"icon": {"provider": "selfhst", "id": "gotify"},
"version": {"name": "latest"}
"tags": [
"notificaties",
"push",
"messaging",
"alerts"
],
"icon": {
"provider": "selfhst",
"id": "gotify"
},
"version": {
"name": "latest"
},
"categories": [
"communicatie"
]
},
"variables": [
{
@ -44,7 +57,9 @@
"default": "admin",
"required": true,
"description": "Wijzig dit na de eerste aanmelding",
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Grafana",
"description": "Visualiseer metrics, logs en traces in aanpasbare dashboards.",
"tags": ["monitoring", "dashboards", "metrics", "visualisatie"],
"icon": {"provider": "selfhst", "id": "grafana"},
"version": {"name": "latest"}
"tags": [
"monitoring",
"dashboards",
"metrics",
"visualisatie"
],
"icon": {
"provider": "selfhst",
"id": "grafana"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring"
]
},
"variables": [
{
@ -51,7 +64,9 @@
"default": "admin",
"required": true,
"description": "Wijzig dit na de eerste aanmelding",
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}

View file

@ -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

74
apps/grocy/template.json Normal file
View file

@ -0,0 +1,74 @@
{
"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"
},
"categories": [
"gezin"
]
},
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": true
}
]
}
]
}

View file

@ -0,0 +1,31 @@
services:
<< service_name >>:
image: quay.io/hedgedoc/hedgedoc:latest
container_name: << service_name >>
ports:
- "<< port >>:3000"
environment:
- TZ=<< timezone >>
- CMD_DB_URL=postgres://hedgedoc:<< db_password >>@<< service_name >>-db:5432/hedgedoc
- CMD_DOMAIN=<< app_url >>
- CMD_URL_ADDPORT=false
- CMD_ALLOW_ANONYMOUS=false
volumes:
- << data_dir >>/<< service_name >>/uploads:/hedgedoc/public/uploads
depends_on:
- << service_name >>-db
restart: unless-stopped
<< service_name >>-db:
image: postgres:16-alpine
container_name: << service_name >>-db
environment:
- POSTGRES_USER=hedgedoc
- POSTGRES_PASSWORD=<< db_password >>
- POSTGRES_DB=hedgedoc
volumes:
- << service_name >>_pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
<< service_name >>_pgdata:

View file

@ -0,0 +1,77 @@
{
"kind": "compose",
"metadata": {
"name": "HedgeDoc",
"description": "Samen documenten schrijven in Markdown, realtime in de browser.",
"tags": [
"notities",
"samenwerken",
"markdown"
],
"icon": {
"provider": "selfhst",
"id": "hedgedoc"
},
"version": {
"name": "latest"
},
"categories": [
"documenten",
"productiviteit"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "hedgedoc",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 3011,
"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"
},
"advanced": true
},
{
"name": "app_url",
"type": "str",
"title": "Basis-URL",
"default": "http://localhost:3010",
"required": true
}
]
}
]
}

View file

@ -3,9 +3,21 @@
"metadata": {
"name": "Home Assistant",
"description": "Open-source domotica platform voor het automatiseren van je slimme huis.",
"tags": ["domotica", "smart-home", "automatisering"],
"icon": {"provider": "selfhst", "id": "home-assistant"},
"version": {"name": "stable"}
"tags": [
"domotica",
"smart-home",
"automatisering"
],
"icon": {
"provider": "selfhst",
"id": "home-assistant"
},
"version": {
"name": "stable"
},
"categories": [
"domotica"
]
},
"variables": [
{

View file

@ -3,9 +3,24 @@
"metadata": {
"name": "Home Assistant MCP Server",
"description": "MCP-server voor Home Assistant. Laat AI-assistenten zoals Claude je slimme apparaten bedienen, automations beheren en statussen opvragen via natuurlijke taal.",
"tags": ["mcp", "ai", "home-assistant", "smarthome", "claude"],
"icon": {"provider": "selfhst", "id": "home-assistant"},
"version": {"name": "latest"}
"tags": [
"mcp",
"ai",
"home-assistant",
"smarthome",
"claude"
],
"icon": {
"provider": "selfhst",
"id": "home-assistant"
},
"version": {
"name": "latest"
},
"categories": [
"domotica",
"ai"
]
},
"variables": [
{
@ -45,7 +60,9 @@
"default": "http://homeassistant:8123",
"required": true,
"description": "URL van je Home Assistant instantie",
"config": {"placeholder": "http://192.168.1.x:8123"}
"config": {
"placeholder": "http://192.168.1.x:8123"
}
},
{
"name": "hass_token",
@ -54,7 +71,9 @@
"default": "",
"required": true,
"description": "Maak een token aan via Home Assistant → Profiel → Lange termijn toegangstokens (onderaan de pagina)",
"config": {"placeholder": "eyJ..."}
"config": {
"placeholder": "eyJ..."
}
}
]
}

View file

@ -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

View file

@ -0,0 +1,57 @@
{
"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"
},
"categories": [
"domotica"
]
},
"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
}
]
}
]
}

View file

@ -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

View file

@ -0,0 +1,65 @@
{
"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"
},
"categories": [
"beheer"
]
},
"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"
}
]
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Immich",
"description": "Zelfgehoste foto- en videobeheer met automatische back-up. Google Photos alternatief.",
"tags": ["fotos", "media", "backup"],
"icon": {"provider": "selfhst", "id": "immich"},
"version": {"name": "release"}
"tags": [
"fotos",
"media",
"backup"
],
"icon": {
"provider": "selfhst",
"id": "immich"
},
"version": {
"name": "release"
},
"categories": [
"fotos",
"opslag"
]
},
"variables": [
{
@ -31,7 +44,8 @@
"title": "Upload directory",
"default": "/opt/serverup/appdata",
"required": true,
"description": "Basismap voor foto-uploads en database"
"description": "Basismap voor foto-uploads en database",
"help": "Hier komen je originele foto's en video's te staan. Reken op net zoveel ruimte als je telefoon en camera bij elkaar produceren, plus wat marge voor de miniaturen."
},
{
"name": "timezone",
@ -52,7 +66,10 @@
"default": "immich_db_pass",
"required": true,
"description": "Wachtwoord voor de interne PostgreSQL database",
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
},
"advanced": true
}
]
}

View file

@ -0,0 +1,17 @@
services:
<< service_name >>:
image: influxdb:2
container_name: << service_name >>
ports:
- "<< port >>:8086"
environment:
- TZ=<< timezone >>
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=<< admin_user >>
- DOCKER_INFLUXDB_INIT_PASSWORD=<< admin_password >>
- DOCKER_INFLUXDB_INIT_ORG=<< org >>
- DOCKER_INFLUXDB_INIT_BUCKET=<< bucket >>
volumes:
- << data_dir >>/<< service_name >>/data:/var/lib/influxdb2
- << data_dir >>/<< service_name >>/config:/etc/influxdb2
restart: unless-stopped

View file

@ -0,0 +1,92 @@
{
"kind": "compose",
"metadata": {
"name": "InfluxDB",
"description": "Tijdreeksdatabase voor meetwaarden van sensoren, servers en Home Assistant.",
"tags": [
"monitoring",
"database",
"metrics"
],
"icon": {
"provider": "selfhst",
"id": "influxdb"
},
"version": {
"name": "2"
},
"categories": [
"monitoring",
"ontwikkeling"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "influxdb",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8089,
"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": "org",
"type": "str",
"title": "Organisatie",
"default": "thuis",
"required": true,
"advanced": true
},
{
"name": "bucket",
"type": "str",
"title": "Bucket",
"default": "metingen",
"required": true,
"advanced": true
},
{
"name": "admin_user",
"type": "str",
"title": "Beheerder",
"default": "admin",
"required": true
},
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"description": "Minimaal 8 tekens",
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "IT-Tools",
"description": "Verzameling handige online tools voor developers: encoding, hashing, formattering, converters en meer.",
"tags": ["tools", "developer", "utilities", "encoding"],
"icon": {"provider": "selfhst", "id": "it-tools"},
"version": {"name": "latest"}
"tags": [
"tools",
"developer",
"utilities",
"encoding"
],
"icon": {
"provider": "selfhst",
"id": "it-tools"
},
"version": {
"name": "latest"
},
"categories": [
"ontwikkeling"
]
},
"variables": [
{

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Jellyfin",
"description": "Gratis en open-source mediaserver voor films, series en muziek. Geen abonnement.",
"tags": ["media", "streaming", "films", "muziek"],
"icon": {"provider": "selfhst", "id": "jellyfin"},
"version": {"name": "latest"}
"tags": [
"media",
"streaming",
"films",
"muziek"
],
"icon": {
"provider": "selfhst",
"id": "jellyfin"
},
"version": {
"name": "latest"
},
"categories": [
"media"
]
},
"variables": [
{

View file

@ -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

View file

@ -0,0 +1,57 @@
{
"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"
},
"categories": [
"media"
]
},
"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
}
]
}
]
}

View file

@ -0,0 +1,43 @@
services:
<< service_name >>:
image: ghcr.io/karakeep-app/karakeep:release
container_name: << service_name >>
ports:
- "<< port >>:3000"
environment:
- TZ=<< timezone >>
- NEXTAUTH_SECRET=<< secret >>
- MEILI_ADDR=http://<< service_name >>-meilisearch:7700
- BROWSER_WEB_URL=http://<< service_name >>-chrome:9222
- DATA_DIR=/data
- OPENAI_API_KEY=<< openai_key >>
volumes:
- << data_dir >>/<< service_name >>/data:/data
depends_on:
- << service_name >>-meilisearch
- << service_name >>-chrome
restart: unless-stopped
<< service_name >>-chrome:
image: gcr.io/zenika-hub/alpine-chrome:124
container_name: << service_name >>-chrome
command:
- --no-sandbox
- --disable-gpu
- --disable-dev-shm-usage
- --remote-debugging-address=0.0.0.0
- --remote-debugging-port=9222
- --hide-scrollbars
restart: unless-stopped
<< service_name >>-meilisearch:
image: getmeili/meilisearch:v1.41.0
container_name: << service_name >>-meilisearch
environment:
- MEILI_NO_ANALYTICS=true
volumes:
- << service_name >>_meili:/meili_data
restart: unless-stopped
volumes:
<< service_name >>_meili:

View file

@ -0,0 +1,78 @@
{
"kind": "compose",
"metadata": {
"name": "Karakeep",
"description": "Bewaart bladwijzers, artikelen en notities met volledige tekstzoekfunctie. Voorheen Hoarder.",
"tags": [
"bladwijzers",
"archief",
"lezen"
],
"icon": {
"provider": "selfhst",
"id": "karakeep"
},
"version": {
"name": "release"
},
"categories": [
"documenten"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "karakeep",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 3012,
"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": "secret",
"type": "str",
"title": "Geheime sleutel",
"default": "",
"required": true,
"description": "Willekeurige waarde voor sessies",
"config": {
"placeholder": "sterk-wachtwoord"
},
"advanced": true
},
{
"name": "openai_key",
"type": "str",
"title": "OpenAI-sleutel (optioneel)",
"default": "",
"required": false,
"description": "Alleen nodig als je automatisch labels wilt laten toekennen",
"advanced": true
}
]
}
]
}

View file

@ -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

82
apps/kavita/template.json Normal file
View file

@ -0,0 +1,82 @@
{
"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"
},
"categories": [
"media",
"documenten"
]
},
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": true
},
{
"name": "library_dir",
"type": "str",
"title": "Bibliotheekmap",
"default": "/opt/serverup/media/books",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,32 @@
services:
<< service_name >>:
image: quay.io/keycloak/keycloak:latest
container_name: << service_name >>
command: start-dev
ports:
- "<< port >>:8080"
environment:
- TZ=<< timezone >>
- KC_BOOTSTRAP_ADMIN_USERNAME=<< admin_user >>
- KC_BOOTSTRAP_ADMIN_PASSWORD=<< admin_password >>
- KC_DB=postgres
- KC_DB_URL=jdbc:postgresql://<< service_name >>-db:5432/keycloak
- KC_DB_USERNAME=keycloak
- KC_DB_PASSWORD=<< db_password >>
depends_on:
- << service_name >>-db
restart: unless-stopped
<< service_name >>-db:
image: postgres:16-alpine
container_name: << service_name >>-db
environment:
- POSTGRES_DB=keycloak
- POSTGRES_USER=keycloak
- POSTGRES_PASSWORD=<< db_password >>
volumes:
- << service_name >>_pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
<< service_name >>_pgdata:

View file

@ -0,0 +1,87 @@
{
"kind": "compose",
"metadata": {
"name": "Keycloak",
"description": "Volwaardige identiteitsserver met OIDC en SAML, gebruikersbeheer en federatie.",
"tags": [
"beveiliging",
"sso",
"oidc"
],
"icon": {
"provider": "selfhst",
"id": "keycloak"
},
"version": {
"name": "latest"
},
"categories": [
"beveiliging"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "keycloak",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8103,
"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_user",
"type": "str",
"title": "Beheerder",
"default": "admin",
"required": true
},
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"description": "Gebruik de dobbelsteenknop voor een willekeurige waarde",
"config": {
"placeholder": "sterk-wachtwoord"
}
},
{
"name": "db_password",
"type": "str",
"title": "Database wachtwoord",
"default": "",
"required": true,
"description": "Gebruik de dobbelsteenknop voor een willekeurige waarde",
"config": {
"placeholder": "sterk-wachtwoord"
},
"advanced": true
}
]
}
]
}

View file

@ -0,0 +1,32 @@
services:
<< service_name >>:
image: kimai/kimai2:apache
container_name: << service_name >>
ports:
- "<< port >>:8001"
environment:
- TZ=<< timezone >>
- DATABASE_URL=mysql://kimai:<< db_password >>@<< service_name >>-db/kimai?charset=utf8mb4&serverVersion=8.0.0
- ADMINMAIL=<< admin_email >>
- ADMINPASS=<< admin_password >>
volumes:
- << data_dir >>/<< service_name >>/data:/opt/kimai/var/data
depends_on:
- << service_name >>-db
restart: unless-stopped
<< service_name >>-db:
image: mysql:8
container_name: << service_name >>-db
command: --default-storage-engine innodb
environment:
- MYSQL_DATABASE=kimai
- MYSQL_USER=kimai
- MYSQL_PASSWORD=<< db_password >>
- MYSQL_ROOT_PASSWORD=<< db_password >>
volumes:
- << service_name >>_dbdata:/var/lib/mysql
restart: unless-stopped
volumes:
<< service_name >>_dbdata:

88
apps/kimai/template.json Normal file
View file

@ -0,0 +1,88 @@
{
"kind": "compose",
"metadata": {
"name": "Kimai",
"description": "Urenregistratie voor projecten en klanten, met rapportages en facturen.",
"tags": [
"productiviteit",
"uren",
"administratie"
],
"icon": {
"provider": "selfhst",
"id": "kimai"
},
"version": {
"name": "apache"
},
"categories": [
"productiviteit",
"financien"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "kimai",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8104,
"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"
},
"advanced": true
},
{
"name": "admin_email",
"type": "str",
"title": "Beheerder e-mail",
"default": "admin@example.com",
"required": true
},
{
"name": "admin_password",
"type": "str",
"title": "Beheerderswachtwoord",
"default": "",
"required": true,
"description": "Minimaal 8 tekens",
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}
]
}

View file

@ -0,0 +1,13 @@
services:
<< service_name >>:
image: gotson/komga:latest
container_name: << service_name >>
ports:
- "<< port >>:25600"
environment:
- TZ=<< timezone >>
user: "<< puid >>:<< pgid >>"
volumes:
- << data_dir >>/<< service_name >>/config:/config
- << library_dir >>:/data
restart: unless-stopped

81
apps/komga/template.json Normal file
View file

@ -0,0 +1,81 @@
{
"kind": "compose",
"metadata": {
"name": "Komga",
"description": "Server voor strips en manga met leesvoortgang en een ingebouwde lezer.",
"tags": [
"media",
"strips",
"manga"
],
"icon": {
"provider": "selfhst",
"id": "komga"
},
"version": {
"name": "latest"
},
"categories": [
"media"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "komga",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 25600,
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": true
},
{
"name": "library_dir",
"type": "str",
"title": "Stripboekenmap",
"default": "/opt/serverup/media/comics",
"required": true
}
]
}
]
}

View file

@ -0,0 +1,21 @@
services:
<< service_name >>:
image: kopia/kopia:latest
container_name: << service_name >>
command:
- server
- start
- --insecure
- --address=0.0.0.0:51515
- --server-username=<< ui_user >>
- --server-password=<< ui_password >>
ports:
- "<< port >>:51515"
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>/config:/app/config
- << data_dir >>/<< service_name >>/cache:/app/cache
- << source_dir >>:/data:ro
- << repo_dir >>:/repository
restart: unless-stopped

89
apps/kopia/template.json Normal file
View file

@ -0,0 +1,89 @@
{
"kind": "compose",
"metadata": {
"name": "Kopia",
"description": "Versleutelde back-ups met deduplicatie naar schijf, NAS of cloudopslag.",
"tags": [
"backup",
"opslag",
"versleuteling"
],
"icon": {
"provider": "selfhst",
"id": "kopia"
},
"version": {
"name": "latest"
},
"categories": [
"opslag"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "kopia",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 51515,
"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": "source_dir",
"type": "str",
"title": "Map om te back-uppen",
"default": "/opt/serverup",
"required": true
},
{
"name": "repo_dir",
"type": "str",
"title": "Repository",
"default": "/opt/serverup/backups/kopia",
"required": true
},
{
"name": "ui_user",
"type": "str",
"title": "Gebruikersnaam",
"default": "kopia",
"required": true
},
{
"name": "ui_password",
"type": "str",
"title": "Wachtwoord webinterface",
"default": "",
"required": true,
"description": "Gebruik de dobbelsteenknop voor een willekeurige waarde",
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}
]
}

View file

@ -0,0 +1,14 @@
services:
<< service_name >>:
image: erikvl87/languagetool:latest
container_name: << service_name >>
ports:
- "<< port >>:8010"
environment:
- TZ=<< timezone >>
- Java_Xms=512m
- Java_Xmx=2g
<%- if ngrams %>
- langtool_languageModel=/ngrams
<%- endif %>
restart: unless-stopped

View file

@ -0,0 +1,59 @@
{
"kind": "compose",
"metadata": {
"name": "LanguageTool",
"description": "Spelling- en grammaticacontrole voor tientallen talen, zonder je tekst naar een clouddienst te sturen.",
"tags": [
"taal",
"schrijven",
"privacy"
],
"icon": {
"provider": "selfhst",
"id": "languagetool"
},
"version": {
"name": "latest"
},
"categories": [
"productiviteit",
"documenten"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "languagetool",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8107,
"required": true
},
{
"name": "timezone",
"type": "str",
"title": "Tijdzone",
"default": "Europe/Amsterdam",
"required": true
},
{
"name": "ngrams",
"type": "bool",
"title": "N-gram data gebruiken",
"default": false,
"description": "Betere suggesties, maar vraagt enkele gigabytes aan schijfruimte",
"advanced": true
}
]
}
]
}

View file

@ -3,9 +3,21 @@
"metadata": {
"name": "Linkding",
"description": "Eenvoudige zelfgehoste bladwijzerbeheerder met tags, zoekfunctie en browserextensie.",
"tags": ["bladwijzers", "links", "productiviteit"],
"icon": {"provider": "selfhst", "id": "linkding"},
"version": {"name": "latest"}
"tags": [
"bladwijzers",
"links",
"productiviteit"
],
"icon": {
"provider": "selfhst",
"id": "linkding"
},
"version": {
"name": "latest"
},
"categories": [
"documenten"
]
},
"variables": [
{
@ -50,7 +62,9 @@
"title": "Admin wachtwoord",
"default": "",
"required": true,
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Mealie",
"description": "Receptenbeheer met maaltijdplanning, boodschappenlijsten en automatisch importeren van recepten.",
"tags": ["recepten", "koken", "maaltijdplanning", "voedsel"],
"icon": {"provider": "selfhst", "id": "mealie"},
"version": {"name": "latest"}
"tags": [
"recepten",
"koken",
"maaltijdplanning",
"voedsel"
],
"icon": {
"provider": "selfhst",
"id": "mealie"
},
"version": {
"name": "latest"
},
"categories": [
"gezin"
]
},
"variables": [
{

View file

@ -0,0 +1,11 @@
services:
<< service_name >>:
image: neosmemo/memos:stable
container_name: << service_name >>
ports:
- "<< port >>:5230"
environment:
- TZ=<< timezone >>
volumes:
- << data_dir >>/<< service_name >>:/var/opt/memos
restart: unless-stopped

58
apps/memos/template.json Normal file
View file

@ -0,0 +1,58 @@
{
"kind": "compose",
"metadata": {
"name": "Memos",
"description": "Snel gedachten en notities vastleggen in een tijdlijn. Als een privé-microblog.",
"tags": [
"notities",
"schrijven",
"persoonlijk"
],
"icon": {
"provider": "selfhst",
"id": "memos"
},
"version": {
"name": "stable"
},
"categories": [
"documenten",
"productiviteit"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "memos",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 5230,
"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
}
]
}
]
}

View file

@ -0,0 +1,12 @@
services:
<< service_name >>:
image: metabase/metabase:latest
container_name: << service_name >>
ports:
- "<< port >>:3000"
environment:
- TZ=<< timezone >>
- MB_DB_FILE=/metabase-data/metabase.db
volumes:
- << data_dir >>/<< service_name >>:/metabase-data
restart: unless-stopped

View file

@ -0,0 +1,58 @@
{
"kind": "compose",
"metadata": {
"name": "Metabase",
"description": "Vragen stellen aan je databases en de antwoorden als grafiek of dashboard tonen.",
"tags": [
"dashboard",
"database",
"rapportage"
],
"icon": {
"provider": "selfhst",
"id": "metabase"
},
"version": {
"name": "latest"
},
"categories": [
"monitoring",
"ontwikkeling"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "metabase",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 3013,
"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
}
]
}
]
}

View file

@ -0,0 +1,13 @@
services:
<< service_name >>:
image: ghcr.io/alexta69/metube:latest
container_name: << service_name >>
ports:
- "<< port >>:8081"
environment:
- TZ=<< timezone >>
- UID=<< puid >>
- GID=<< pgid >>
volumes:
- << downloads_dir >>:/downloads
restart: unless-stopped

82
apps/metube/template.json Normal file
View file

@ -0,0 +1,82 @@
{
"kind": "compose",
"metadata": {
"name": "MeTube",
"description": "Video's van YouTube en honderden andere sites downloaden via een webformulier.",
"tags": [
"media",
"downloaden",
"video"
],
"icon": {
"provider": "selfhst",
"id": "metube"
},
"version": {
"name": "latest"
},
"categories": [
"media",
"downloaden"
]
},
"variables": [
{
"title": "Algemeen",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Servicenaam",
"default": "metube",
"required": true
},
{
"name": "port",
"type": "int",
"title": "Web poort",
"default": 8105,
"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",
"advanced": true
},
{
"name": "pgid",
"type": "int",
"title": "Groeps-ID (PGID)",
"default": 1000,
"required": true,
"advanced": true
},
{
"name": "downloads_dir",
"type": "str",
"title": "Downloadmap",
"default": "/opt/serverup/media/downloads",
"required": true
}
]
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "Miniflux",
"description": "Minimalistische en snelle RSS-lezer, zelfgehost met een opgeruimde interface.",
"tags": ["rss", "nieuws", "lezer", "feeds"],
"icon": {"provider": "selfhst", "id": "miniflux"},
"version": {"name": "latest"}
"tags": [
"rss",
"nieuws",
"lezer",
"feeds"
],
"icon": {
"provider": "selfhst",
"id": "miniflux"
},
"version": {
"name": "latest"
},
"categories": [
"documenten"
]
},
"variables": [
{
@ -50,7 +63,9 @@
"title": "Admin wachtwoord",
"default": "",
"required": true,
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
},
@ -63,7 +78,9 @@
"title": "Database wachtwoord",
"default": "miniflux_db_pass",
"required": true,
"config": {"placeholder": "sterk-wachtwoord"}
"config": {
"placeholder": "sterk-wachtwoord"
}
}
]
}

View file

@ -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

View file

@ -0,0 +1,65 @@
{
"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"
},
"categories": [
"domotica",
"netwerk"
]
},
"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
}
]
}
]
}

View file

@ -3,9 +3,22 @@
"metadata": {
"name": "n8n",
"description": "No-code/low-code workflow automatisering. Zapier-alternatief met 400+ integraties.",
"tags": ["automatisering", "workflow", "integratie"],
"icon": {"provider": "selfhst", "id": "n8n"},
"version": {"name": "latest"}
"tags": [
"automatisering",
"workflow",
"integratie"
],
"icon": {
"provider": "selfhst",
"id": "n8n"
},
"version": {
"name": "latest"
},
"categories": [
"productiviteit",
"ontwikkeling"
]
},
"variables": [
{
@ -51,7 +64,9 @@
"default": "localhost",
"required": false,
"description": "Hostname of IP van je server voor webhook-URLs. Gebruik je domeinnaam als je een reverse proxy hebt.",
"config": {"placeholder": "mijn-server.nl"}
"config": {
"placeholder": "mijn-server.nl"
}
}
]
}

View file

@ -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

View file

@ -0,0 +1,72 @@
{
"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"
},
"categories": [
"media"
]
},
"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"
}
]
}
]
}

Some files were not shown because too many files have changed in this diff Show more