Het onbewaakte deel was de zwakke plek: geplande taken draaiden 's nachts en
een storing kwam alleen in het auditlog terecht.
Meldingen (core/notify.py):
- ntfy, webhook (Discord/Slack/Gotify) en e-mail, alle drie met de
standaardbibliotheek.
- Gebeurtenissen: mislukte backup, onleesbaar archief, vastgelopen taak,
weinig schijfruimte, beschikbare update, gestopte container.
- Eén bericht per ronde; bij schijfruimte en gestopte containers alleen bij
de overgang, zodat je niet elk uur hetzelfde krijgt.
- Testknop die eerst opslaat, zodat je test wat je net hebt ingevuld.
- send() gooit nooit: het kanaal mag de taak die de melding veroorzaakte niet
alsnog laten omvallen.
Schijfruimte (core/diskspace.py):
- Controle vóór elke backup; past het niet, dan weigeren in plaats van
halverwege afbreken.
- DISK_MIN_FREE_GB blijft gereserveerd, DISK_WARN_PCT kleurt de balk rood.
- Per filesystem één regel in de backuplijst.
Backups controleren:
- Elk nieuw archief wordt helemaal uitgelezen (tar-structuur plus
gzip-checksum, die aan het eind staat).
- Diepe variant pakt echt uit naar een tijdelijke map, met dezelfde
beperkingen als een echt herstel.
- Resultaat staat in de metadata en als schildje in de lijst.
Backups de deur uit:
- Downloadknop.
- BACKUP_OFFSITE_DIR kopieert elke nieuwe backup naar een gemounte schijf,
NFS- of SMB-share, via .part zodat een afgebroken kopie herkenbaar onaf is.
- Ruimtecontrole op de bestemming, want een weggevallen mount laat vaak een
lege map op de systeemschijf achter.
NOTIFY_TOKEN en NOTIFY_EMAIL_PASSWORD zijn write-only.
Nieuw: docs/meldingen.md; docs/backups.md uitgebreid. Getest tegen een
draaiende server met een echte ontvanger. 1064 tests groen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
Het installatiescript kan nu allebei zelf regelen, zodat je na afloop niet
meer naar de webinterface hoeft om een account te claimen en niet meer in
.env hoeft te duiken om erbij te kunnen.
Beheerdersaccount:
- Op een terminal wordt gevraagd of je er meteen een wil, met naam en
wachtwoord (twee keer, echo uit).
- Automatisch via --admin NAAM plus --admin-password-file of
SU_ADMIN_PASSWORD; zonder bron en zonder terminal maakt het script er
zelf een van 24 tekens en toont die.
- --admin-password weigert bewust: argumenten zijn zichtbaar in 'ps'. Het
wachtwoord gaat via stdin naar curl, niet als argument.
- Nieuw --create-admin voor een installatie die al draait.
Bereikbaarheid:
- Keuzemenu voor BIND (deze server / hele netwerk / eigen adres), met het
gedetecteerde serveradres erbij.
- Bij opnieuw installeren is de bestaande instelling het uitgangspunt, zodat
enter je server niet ongemerkt terugzet op loopback.
Opgeloste fouten:
- Een bestaande .env bleef altijd ongemoeid, ook met --bind: de installatie
leek te lukken terwijl de server op het oude adres bleef luisteren.
- --update rekende met de standaardpoort in plaats van met wat er in .env
staat, waardoor de wachtlus en het slotadres niet klopten.
- --dry-run riep 'sudo docker' aan en vroeg dus om een wachtwoord terwijl
het net beloofd had niets te doen; zonder terminal liep het daarop vast.
- Ongeldig bind-adres en poort buiten 1-65535 worden nu meteen geweigerd.
Getest met een 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 geweigerd tweede account en een server
die niet reageert. De e2e-tests slaan zichzelf over waar curl ontbreekt,
zoals in het CI-image. 992 tests groen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
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
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
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
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
- 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
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
- 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
- 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