# v0.7.70-beta — Installeren via een invulmenu, geheimen naar .env **Het installatieformulier is een invulmenu geworden.** Eén lange lijst werkte slecht aan beide uiteinden: de mediane app heeft vijf velden in één groep, en de ARR-stack tweeënvijftig in eenentwintig groepen. Nu zijn er vaste stappen — Basis, Verbinden, Instellingen, Toegang, Controleren — waarbij een lege stap wordt overgeslagen. Een eenvoudige app krijgt er dus drie, de ARR-stack vijf. **Volgende** controleert de verplichte velden van díé stap en noemt wat er nog leeg is. Tot nu toe kwam die melding pas als je op Installeren drukte. De stapindicator is klikbaar om terug te springen. **Geavanceerde opties zitten achter één schakelaar** die op elke stap zichtbaar is, in plaats van een uitklapblok per groep dat je steeds opnieuw moest zoeken. Een badge toont hoeveel velden verborgen zijn. Er is ook een schakelaar **Alles op één pagina** voor wie het oude gedrag wil; beide keuzes worden onthouden. **Geheimen gaan naar `.env`.** Wachtwoorden en sleutels staan niet meer in het compose-bestand maar als `${NAAM}` met de waarde in `.env`, geschreven met rechten `0600`. Poorten, paden en tijdzone blijven leesbaar in compose, zodat je nog steeds in één bestand kunt zien wat er draait. Ook `.serverup.json` bevat geen geheimen meer en krijgt `0600` — daar stonden ze tot nu toe wereldleesbaar in. Eerlijk over de grens hiervan: compose vult `${...}` in bij het inlezen, dus `docker inspect` toont de waarde nog steeds. Wat je wint is dat het compose-bestand deelbaar wordt en dat alles op één afgeschermde plek staat. `docs/beveiliging.md` legt uit wanneer je verder wilt gaan met `secrets:` van Compose, en waarom een externe kluis hier een slecht idee is. **Compose en `.env` zijn nu ook vóór het installeren te bewerken.** De laatste stap toont het gerenderde resultaat met de waarden die je hebt ingevuld; met Bewerken pas je het aan en gaat jouw versie mee in plaats van het sjabloon. Ongeldige YAML wordt geweigerd voordat er een map is aangemaakt. De editor voor bestaande stacks heeft tabbladen gekregen (je moest hem sluiten en opnieuw openen om van compose naar `.env` te gaan) en biedt aan te herstarten na opslaan. De `.env`-editor weigert nu een regel zonder `=` — compose negeert die stilzwijgend, waarna er ineens een wachtwoord mist — en schrijft een auditregel. **Nieuw: Inloggegevens per app.** Een knop op de stackkaart toont het adres, de gebruikersnaam en het wachtwoord, met een oog-knop en een kopieerknop. Bij een gegenereerd wachtwoord kon je dat nergens meer terugvinden. Apps die je bij het eerste bezoek zelf een account laten aanmaken (Vaultwarden, Immich) melden dat. Alleen beheerders kunnen erbij en elk bekijken komt in het auditlog. **Herstel van een regressie uit v0.7.60.** Ik had de genereerknop toen aan `generate` gehangen in plaats van aan `secret`, waardoor vijftien velden hun knop kwijtraakten. Acht daarvan zijn inlogwachtwoorden (InfluxDB, Kimai, Linkding, Miniflux, PhotoPrism, Pi-hole, Technitium, ChangeDetection) en die hebben hem terug. De zeven waar een verzonnen waarde juist fout is — een WireGuard- privésleutel, een Beszel-agentsleutel, tokens uit een andere app — houden geen knop. Bij het uitzoeken bleek ook dat ik in v0.7.60 twee dingen verkeerd heb gemeld. De genereerknop wérkte destijds wel (de vlag werd gezet in `app.py`, niet in `boilerplates.py` waar ik keek), en er wás al een compose- en `.env`-editor. De logica van het invulmenu wordt nu getest door de echte component uit `index.html` in Node uit te voeren; die test slaat zichzelf over waar Node ontbreekt, zoals in het bouwimage. 2295 tests groen. --- # v0.7.60-beta — App-instellingen die niet meer stilzwijgend misgaan Aanleiding: Homepage weigerde na installatie met **"Host validation failed"**. De standaardwaarde van `allowed_hosts` noemde poort 3001 terwijl het poortveld op 3002 stond — en `localhost` werkt sowieso niet als je je server op zijn IP-adres benadert. Dat bleek geen los geval, dus alle 96 sjablonen zijn nagelopen. **De dobbelsteenknop bestond niet.** Zeventien apps zeggen in hun uitleg "gebruik de dobbelsteenknop voor een willekeurige waarde". Die knop hing af van een veld `secret` dat de backend nooit meestuurde, dus hij verscheen nooit — en wachtwoorden stonden als gewone leesbare tekst in het formulier. Nu worden geheimen herkend, en 27 velden die je toch nooit zelf typt (databasewachtwoorden, JWT- en versleutelingssleutels) worden meteen ingevuld met een willekeurige waarde. Inlogwachtwoorden krijgen alleen de knop: die moet je zelf noteren. De knop hangt nu aan "hier past een willekeurige waarde" en niet aan "dit is geheim" — voor een WireGuard-privésleutel of een token uit een andere app is iets verzonnens juist fout. **Verplichte velden werden niet gecontroleerd.** Het formulier zette een sterretje achter een verplicht veld en hield verder niets tegen: je kon een app installeren met een leeg databasewachtwoord of een lege sleutel. De container start dan niet, of draait met een leeg geheim. Dat wordt nu geweigerd, bij installeren én bij het achteraf wijzigen van instellingen, met de melding welke velden nog leeg zijn. Velden achter een uitgeschakelde groepsschakelaar tellen niet mee. **Acht apps hadden een vast wachtwoord in het sjabloon**: `immich_db_pass`, `ghost_db_pass` en soortgelijke voor iedereen die het installeert, en `admin` als beheerderswachtwoord van Grafana en Gotify. De databasewachtwoorden worden nu gegenereerd; bij Grafana en Gotify moet je zelf een wachtwoord kiezen. **Wachtwoorden stonden in het installatielog**, dat in de interface zichtbaar is en vaak in een bugmelding geplakt wordt. Die worden nu gemaskeerd. Verder rechtgezet: - `baserow`, `hedgedoc` en `ntfy` hadden dezelfde poortfout als Homepage: de URL in de standaardwaarde wees naar een poort waar niets luistert, waardoor links en terugkeer-na-inloggen niet werkten. - Zes velden stonden in het formulier maar kwamen nergens terecht: `data_dir` bij Keycloak, MeTube, Miniflux en TeslaMate (die gebruiken een Docker-volume, dus je gegevens stonden niet waar je dacht), en `puid`/`pgid` bij FreshRSS, die het image helemaal niet kent. - De tijdzone van Prometheus werd wel gevraagd maar niet doorgegeven. Nieuw is `tests/test_app_templates.py`: elke app wordt met zijn standaardwaarden gerenderd en gecontroleerd op ongedefinieerde variabelen, velden zonder werking, niet-gedeclareerde volumes, `depends_on` naar een onbekende service, dubbele containernamen, poortconflicten binnen een stack, poortnummers in standaardwaarden die niet kloppen met het poortveld, en vaste wachtwoorden. 2236 tests groen. --- # v0.7.50-beta — Je hoort het als er iets misgaat Het onbewaakte deel was de zwakke plek. Geplande backups, updatechecks en de zelf-update draaien 's nachts zonder toezicht; ging er iets mis, dan kwam dat alleen in het auditlog terecht. Een backup die drie weken stilletjes faalt, ontdek je op het slechtst denkbare moment. **Meldingen** via ntfy, een webhook of e-mail — alle drie met wat er al in Server Up zat, dus geen extra software. Je krijgt bericht bij een mislukte backup, een archief dat niet leesbaar blijkt, een vastgelopen geplande taak, weinig schijfruimte, een beschikbare update en een container die zou moeten draaien maar dat niet doet. Standaard staan alleen de storingen aan. Eén bericht per ronde, niet één per stack: bij een volle schijf faalt alles tegelijk en dan wil je geen twintig meldingen. Bij weinig ruimte en gestopte containers wordt alleen de overgang gemeld, anders krijg je elk uur hetzelfde. Er is een testknop die eerst je instellingen opslaat, zodat je test wat je net hebt ingevuld. Een storing in het meldingskanaal kan de taak die de melding veroorzaakte nooit alsnog laten omvallen. **Schijfruimte** wordt nu bewaakt. Vóór elke backup wordt gekeken of het er redelijkerwijs in past; zo niet, dan wordt hij geweigerd in plaats van halverwege afgebroken — een half archief ziet er in een lijst compleet uit. Daarbovenop blijft `DISK_MIN_FREE_GB` gereserveerd, want een volle schijf op een Docker-host breekt alles en niet alleen Server Up. In de backuplijst staat per schijf een balkje, dat rood kleurt onder `DISK_WARN_PCT`. **Backups worden gecontroleerd.** Elk nieuw archief wordt meteen helemaal uitgelezen: dat controleert de tar-structuur én de gzip-checksum. Die checksum staat aan het eind, dus een archief dat halverwege is afgebroken valt door de mand terwijl het in een directorylisting compleet lijkt. Er is ook een diepe variant die echt uitpakt naar een tijdelijke map, met dezelfde beperkingen als een echt herstel — de enige manier om te weten dat terugzetten werkt vóórdat je het nodig hebt. Achter elk archief staat een schildje: groen als het gelezen is, rood met de foutmelding als het beschadigd is. **Backups kunnen de deur uit.** Er is een downloadknop, en met `BACKUP_OFFSITE_DIR` wordt elke nieuwe backup gekopieerd naar een tweede bestemming: een gemounte schijf, NFS- of SMB-share. Tot nu toe stonden backups uitsluitend op dezelfde schijf als de data die ze moeten beschermen, en kon je ze er niet eens afhalen. Het kopiëren gaat via een `.part`-bestand dat daarna hernoemd wordt, en valt de mount weg — dan bestaat het pad vaak nog als lege map op je systeemschijf — dan wordt eerst gekeken of er ruimte is, zodat je niet ongemerkt de verkeerde schijf vult. Het token en het SMTP-wachtwoord zijn write-only, net als de git- en registry-tokens: instellen kan, teruglezen niet. Nieuw: [`docs/meldingen.md`](docs/meldingen.md), en `docs/backups.md` is uitgebreid met controleren, schijfruimte, de tweede bestemming en downloaden. Getest tegen een draaiende server met een echte ontvanger voor de meldingen: een gehalveerd archief wordt herkend, een volle schijf weigert de backup zonder iets achter te laten, het gedownloade bestand is een uitpakbare tarball, en een padtruc in de bestandsnaam levert een 400 op. 1064 tests groen. --- # v0.7.40-beta — Account en bereikbaarheid meteen goed Na het installeren was je nog niet klaar: je moest zelf naar de webinterface om een beheerdersaccount te claimen, en stond `BIND` op `127.0.0.1`, dan kón je daar niet eens bij zonder eerst `.env` aan te passen. Het script regelt nu allebei. **Beheerdersaccount.** Op een terminal vraagt het script of je er meteen één wil, en zo ja om een naam en wachtwoord (twee keer, met de echo uit). Automatisch gaat het met `--admin NAAM` plus `--admin-password-file PAD` of `SU_ADMIN_PASSWORD`. Is er geen wachtwoordbron én geen terminal, dan maakt het script er zelf een van 24 tekens en zet die op het scherm. Het wachtwoord kan bewust *niet* als optie mee: `--admin-password` weigert met uitleg. Opdrachtregelargumenten zijn voor elke gebruiker op de server zichtbaar met `ps` en blijven in je shell-geschiedenis staan. Onderweg naar de server gaat het via stdin naar `curl`, niet als argument. Nieuw is ook **`--create-admin`**: alleen het account aanmaken, bij een installatie die al draait. Voor als het de eerste keer is misgegaan of je het hebt overgeslagen. De poort komt daarbij uit `.env`. **Bereikbaarheid.** Het script vraagt nu waarop de interface moet luisteren: alleen deze server, het hele netwerk (met het gedetecteerde adres erbij), of een adres dat je zelf opgeeft. Installeer je opnieuw over een bestaande map, dan is je huidige instelling het uitgangspunt en houdt enter die vast. Daarbij zat een fout die je zelf tegenkwam: een bestaande `.env` bleef *altijd* ongemoeid, ook als je `--bind` meegaf. De installatie leek dan te lukken terwijl de server op het oude adres bleef luisteren. Nu wordt doorgevoerd wat je expliciet meegeeft, en blijft de rest staan. Andersom rekent het script bij `--update` voortaan met wat er écht in `.env` staat: draaide je op poort 8080, dan wachtte het daarvoor op poort 5000 en meldde het het verkeerde adres. Verder: `--dry-run` riep `sudo docker` aan en vroeg dus om een wachtwoord, terwijl het net beloofd had niets te doen — zonder terminal liep het daar vast. Een proefdraai spreekt de Docker-daemon nu niet meer aan. En een ongeldig bind-adres of een poort buiten 1–65535 wordt meteen geweigerd in plaats van als een onbegrijpelijke fout uit `docker compose` terug te komen. De vragen worden allemaal vooraf gesteld, zodat je kunt weglopen terwijl het image gebouwd wordt. Getest met een echte pseudo-terminal voor de vragen, en tegen een echt draaiende server voor het aanmaken van het account — inclusief een wachtwoord vol aanhalingstekens en backslashes, een tweede account dat geweigerd hoort te worden, en een server die niet reageert. --- # v0.7.30 — 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. Een derde kwam pas op de bouwserver boven: ontbrak `curl`, dan stopte ook een proefdraai meteen. Juist dan wil je zien wat er gaat gebeuren, dus dat is nu een waarschuwing in plaats van een harde fout — net als bij een ontbrekende Docker. Dit is de eerste stabiele release sinds v0.4.60. Alles uit de v0.5-, v0.6- en v0.7-beta's zit erin: inloggen met rollen, backups, netwerken met een eigen IP-adres, 96 apps met categorieën, apps onderling koppelen, instellingen achteraf wijzigen, en zelf bijwerken. Draaide je nog v0.4.60, lees dan eerst `docs/beveiliging.md`: daar stond de interface nog open zonder login, en na het bijwerken moet je meteen een beheerdersaccount aanmaken. --- # 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 ``-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 Het patch-deel (laatste cijfer) gebruikt nu twee cijfers en loopt in tientallen: `0.4.60`, `0.4.61`, … `0.4.69`, `0.4.70`. Blijft semver-compatibel (het deel wordt als geheel getal vergeleken, dus `0.4.60` > `0.4.6`). --- # v0.4.6 — Kaal versienummer op alle builds ## Wijziging in v0.4.6 De deploy-workflows tonen nu overal het kale versienummer uit het `VERSION`- bestand (of de tagnaam), zonder `-dev`/`-rc.`-suffix. Eenvoudiger te lezen; bump bij elke wijziging gewoon `VERSION`. --- # v0.4.5 — Releases & in-app update-check ## Nieuw in v0.4.5 ### 🏷️ Automatische releases bij een tag Push je een `v*`-tag, dan maakt de nieuwe `release.yml`-workflow automatisch een Forgejo-**Release** aan met notes uit de bijbehorende `CHANGELOG.md`-sectie plus een commit-overzicht sinds de vorige tag. Pre-release tags (bv. `v0.8.4-beta1`) worden als pre-release gemarkeerd. Zo krijg je een Releases-pagina met duidelijke changelog per versie, vergelijkbaar met GitHub Releases. ### 🔔 In-app "update beschikbaar" Server Up vergelijkt de draaiende versie met de laatste release via de Forgejo/ GitHub Releases-API (semver-vergelijking, pre-releases optioneel). Is er een nieuwere versie, dan verschijnt een melding in de topbar en een blok in Instellingen → Updates met versie, release-notes en een link. Instelbaar via `UPDATE_API_URL` (of env `SU_UPDATE_API`) en de optie "pre-releases meenemen". ### 🔖 Versiebeleid Bump bij elke wijziging het centrale `VERSION`-bestand; tags zijn de bron voor release-versies (`vX.Y.Z`, of `-beta`/`-rc` voor pre-releases). --- # v0.4.4 — Nette versienummers op alle builds ## Nieuw in v0.4.4 Centraal `VERSION`-bestand (semver) is nu de bron voor het versienummer. De deploy-workflows lezen het en bouwen: - tag `v0.4.4` → `0.4.4` (productie, toont `v0.4.4`) - push naar `main` → `0.4.4-rc.` - push naar `dev` → `0.4.4-dev.` Zo zie je voortaan het échte versienummer in de topbar i.p.v. alleen de git-SHA (`vdev-1eab7c07`). Bump bij elke wijziging alleen nog `VERSION` (en voor de zekerheid de fallback in `app.py`/`index.html` voor lokale runs). --- # v0.4.3 — Auto-logo's legacy-apps + fix lege Docker Images ## Nieuw / fixes in v0.4.3 ### 🖼️ Automatische logo's voor legacy-apps Legacy-apps (eigen `app.json`/`stack.json`-stacks) krijgen nu automatisch een logo via de dashboard-icons CDN, afgeleid uit de app-naam (bv. "Nextcloud" → `nextcloud.png`). Een expliciete logo-URL in de metadata wint; bestaat het geraden icoon niet, dan valt de UI terug op het emoji-icoon. Geldt voor zowel de App Store als geïnstalleerde stacks. ### 🐛 Fix: Docker Images-pagina was leeg De afbeeldingenlijst gebruikte `:key="img.id"`, maar meerdere tags kunnen dezelfde image-ID delen → dubbele Alpine-keys waardoor de tabel niet rendert (en de "geen images"-melding ook niet, want er waren wél images). De `x-for` gebruikt nu de index als key. --- # v0.4.2 — App-logo's bij stacks ## Nieuw in v0.4.2 Geïnstalleerde stacks tonen nu het app-logo (of een emoji-icoon als fallback), zowel op het dashboard als op de Stacks-pagina. Bij installatie wordt het logo/icoon van de bron-app opgeslagen in `.serverup.json` in de stack-map. Bestaande installaties krijgen hun logo via een naam-match met de App Store, dus ze hoeven niet opnieuw geïnstalleerd te worden. De store toonde al logo's voor boilerplate-apps (via de selfhst/dashboard-icons CDN). --- # v0.4.1 — Fix: styling werd niet toegepast ## Fix bovenop v0.4.0 De volledige Tailwind-stylesheet faalde stil omdat `.modal` via `@apply` de eigen CSS-animatieklasse `anim` toepaste. Tailwind's `@apply` accepteert alleen Tailwind-utilities, geen losse CSS-klassen, waardoor de hele Play-CDN-compilatie afbrak en de pagina ongestyled (kaal HTML) werd geladen. De animatie staat nu als gewone CSS-regel (`.anim, .modal { animation: … }`) los van `@apply`. --- # v0.4.0 — Volledig nieuw UI-ontwerp (Modern SaaS) ## Nieuw in v0.4.0 ### 🎨 Compleet herontworpen interface Volledig nieuw, licht en ruim "Modern SaaS"-ontwerp (Inter-font, indigo accent, zachte schaduwen, ronde 2xl-kaarten). Nieuwe app-shell: verticale sidebar met merk-header, gegroepeerde navigatie met actieve indicator en live status-footer; slanke sticky topbar met dynamische paginatitel en status-chips. Dashboard, stacks, app store, images-/audittabellen, instellingen, modals, wizard, toasts en het log-paneel zijn allemaal opnieuw vormgegeven. Inklapbaar menu en grote touch-targets blijven behouden; volledig mobielvriendelijk. ### 🌗 Thema volgt systeemvoorkeur Standaard volgt het thema de OS-voorkeur (licht/donker) en reageert live op wijzigingen. Handmatige override via Auto / Light / Dark in Instellingen. --- # v0.3.1 — UI-restyle + taalmodule (add-on) ## Nieuw in v0.3.1 ### 🎨 Grondige UI-restyle Grotere, touch-vriendelijke knoppen (min. 44px), ruimere spacing, grotere typografie en kaarten. Layout gecentreerd met max-breedte voor meer lucht op grote schermen. Volledig mobielvriendelijk. ### 📐 Inklapbaar menu (ook op desktop) De hamburger klapt de sidebar nu ook op desktop in/uit; voorkeur wordt onthouden (`localStorage`), hoofdinhoud en log-paneel schuiven mee. Op mobiel een tap-to-close overlay. ### 🌐 Taalmodule als add-on Nieuwe sectie in Instellingen → "Taalmodule": talen toevoegen, bewerken, verwijderen en ingebouwde talen (NL/EN) dupliceren als startpunt. Editor toont per sleutel de Engelse referentie met zoekfilter. Backend-endpoints: `POST/DELETE /api/i18n`, `GET /api/i18n/keys`, `GET /api/i18n//raw`. Toegevoegde talen komen als JSON in `translations/` en zijn direct beschikbaar. --- # v0.3.01 — Boilerplates fixes + git-driven versioning ## Fixes bovenop v0.3.0 ### 🔁 Auto-migratie van default-repos voor upgraders Bestaande installaties (vanaf v0.2.x) hadden de Boilerplates-repo niet zichtbaar in de App Store omdat hun `config.json` in de `su-data` volume al bestond en de nieuwe `DEFAULTS["APP_REPOS"]` daardoor werd overschreven. `core/__init__.py` `load()` doet nu een eenmalige migratie: ontbrekende default-repos worden bij opstart aangevuld op basis van id, en gemarkeerd in `MIGRATIONS_DONE: ["v0.3.0_default_repos"]`. Wordt direct gepersisteerd. Verwijdert een gebruiker de Boilerplates-repo expliciet, dan komt-ie niet automatisch terug. ### 🧹 YAML-poetsstap na boilerplate-render Bij stacks waar de meeste optionele groepen uit staan (Authentik, Nextcloud, etc.) liet de Jinja-render verlaten mapping-sleutels achter (`volumes:`, `networks:`, etc. zonder kinderen). Docker-compose faalt daarop met *"block sequence entries are not allowed in this context"*. `core/boilerplates.py` `_tidy()` heeft nu een `_drop_empty_mappings()` substep die in meerdere passes mapping-sleutels verwijdert die alleen worden gevolgd door whitespace/commentaar of een sibling op gelijke/lagere indent. Werkt in cascade. ### 🏷️ Versie komt nu uit git `Dockerfile` accepteert `ARG SU_VERSION=dev` en bakt die in `ENV SU_VERSION` + OCI image-label. Met `docker build --build-arg SU_VERSION=$(git describe ...)` weet het image zijn eigen versie. De Server Up UI toont automatisch de juiste waarde, ongeacht wat de productie-compose meegeeft. Forgejo Actions kan een tag-push automatisch verwerken — zie `server-up-deploy/README.md`. ## Migratie vanaf v0.3.0 ```bash docker compose build --no-cache docker compose up -d ``` --- # v0.3.0 — UI rebuild + Boilerplates support ## Hoogtepunten ### 🎨 Nieuwe UI (Tailwind + Alpine + HTMX) - `templates/index.html` is volledig herschreven. De handgeschreven CSS (`--bg/--s1/...` variabelen, ad-hoc grid-classes) is vervangen door Tailwind utility-classes met een gematchte donker/licht-palette. - Statebeheer via Alpine.js: één `app()` component bovenop het hele document, geen `$=document.getElementById`-spaghetti meer. - HTMX is geladen voor toekomstige server-rendered partials. Het bestaande fetch-RPC patroon blijft werken; HTMX kan progressief worden ingezet. - Modals, toasts, terminal-overlay en first-run wizard zitten allemaal in één Alpine-state — geen losse globale variabelen meer. - Mobiele sidebar gedraagt zich nu correct (slide-in i.p.v. layout-flip). ### 🧩 ChristianLempa Boilerplates ondersteund Server Up herkent nu twee stack-formaten naast elkaar: | Formaat | Detectie | Bron | |---|---|---| | **Compose** (origineel) | `compose.yml` / `docker-compose.yml` (+ optioneel `stack.json`) | bes-r/server-up | | **Boilerplate** (nieuw) | `template.json` + `files/` | ChristianLempa/boilerplates-library | Nieuw bestand `app/core/boilerplates.py`: - `is_boilerplate(d)` — detectie - `metadata(d)` — converteert `template.json["metadata"]` (incl. selfhst-icons) naar Server-Up formaat - `fields(d)` — flattened variable-schema voor de install-modal - `render_to_dir(src, dest, values)` — rendert `files/*.yaml` met de Jinja-achtige `<< var >>` + `<%- if expr %>` syntax die de boilerplates gebruiken (Jinja2 met custom delimiters). Niet-tekst bestanden worden verbatim gekopieerd. `app/core/git.py` → `_scan_compose_dirs` herkent beide formaten en zet `format: "boilerplate"` in de stack-entry zodat de UI er een badge bij kan tonen. `app/app.py`: - `_find_stack_src` accepteert ook boilerplate-mappen. - `POST /api/store/preview` retourneert voor boilerplates het variabelen-schema (`fields`) plus een gerenderde preview met defaults. - `POST /api/store/install` met body `{values: {...}}` rendert de templates voor je voordat de stack gestart wordt. ### ⚙️ Default-repos Een nieuwe Server Up komt nu uit de doos met twee app-repositories: 1. `bes-r/server-up` — eigen stacks, submap `apps` 2. `ChristianLempa/boilerplates-library` — community templates, submap `compose` ### 📦 Versie / Dockerfile - `SU_VERSION` = `0.3.0` in `Dockerfile` en `docker-compose.yml` - `requirements.txt`: Jinja2 expliciet toegevoegd (was al een Flask-dep) ## Migratie vanaf v0.2.29 - Build opnieuw: `docker compose up -d --build --no-cache` - Bestaande stacks blijven werken (legacy formaat is intact). - De Boilerplates-repo wordt automatisch toegevoegd voor verse installs. Bestaande gebruikers kunnen de repo handmatig toevoegen via Instellingen → Git Repositories met submap `compose`. ## Bekende beperkingen - Boilerplate-templates met onbekende custom-filters of complexe Ansible-style conditionals kunnen falen — de fout verschijnt in het terminal-paneel. - `volumes:` blokken die conditioneel zijn (`<%- if volume_mode == 'local' %>`) werken; complexere render-logica (loops over services) is nog niet getest. - HTMX is geladen maar de meeste interacties draaien nog op fetch-RPC. Verdere migratie naar server-rendered partials kan stapsgewijs in volgende releases.