v0.8.05-beta - een onbereikbaar pad liet zich gewoon opslaan
Some checks failed
Deploy server-up (dev) / deploy (push) Has been cancelled

Oorzaak van de lege appslijst: LIBRARY_DIR stond op /srv/serverup/stacks
terwijl alleen /opt/serverup gekoppeld is. Dat pad bestaat binnen de container
niet, dus geen apps in de lijst en alle containers onder "handmatig gestart" —
want beheerd leest dezelfde map. Nergens stond waarom.

paden.bereikbaar() bestond al sinds v0.7.92 maar hing alleen aan het veld
Hoofdmap en aan het verhuizen. De drie padvelden schrijven rechtstreeks naar
PUT /api/settings, en daar stond geen wacht.

- PUT /api/settings weigert nu een pad waar Server Up niet bij kan, met de
  regel voor .env erbij. Een ongewijzigd pad blokkeert niets, anders kun je met
  een kapotte instelling niets meer wijzigen
- een lege appslijst zegt nu waarom in plaats van "Geen apps gevonden"

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
This commit is contained in:
Ramon 2026-08-03 22:22:29 +02:00
parent 2ce42f507d
commit e61f90745b
5 changed files with 123 additions and 3 deletions

View file

@ -1,3 +1,27 @@
# v0.8.05-beta — Een onbereikbaar pad liet zich gewoon opslaan
Dit is de oorzaak van de lege appslijst waar de vorige vier uitgaven omheen
hebben gezocht.
`LIBRARY_DIR` stond op `/srv/serverup/stacks`, terwijl alleen `/opt/serverup` in
de container gekoppeld is. Dat pad bestaat binnen de container niet eens — dus
geen enkele app in de lijst, en alle containers onder "handmatig gestart", want
`beheerd` leest dezelfde map. Nergens stond waarom.
De controle daarop bestond al sinds v0.7.92: `paden.bereikbaar()` weet dat de
container alleen bij paden onder `BASE_DIR` kan, en geeft de exacte regel die je
in `.env` moet zetten. Hij hing alleen aan het veld **Hoofdmap** en aan het
**verhuizen**. De drie padvelden eronder schrijven rechtstreeks naar
`PUT /api/settings` — en daar stond geen wacht. Een pad buiten de koppeling liet
zich dus gewoon opslaan, waarna alles stilviel.
- `PUT /api/settings` weigert nu een `LIBRARY_DIR`, `DATA_DIR` of `BACKUP_DIR`
waar Server Up niet bij kan, met dezelfde uitleg: welke regel in `.env`, en
welk commando erna. Een ongewijzigd pad blokkeert niets, anders kun je met een
al kapotte instelling niets meer wijzigen.
- Is de lijst leeg, dan zegt het scherm nu **waarom**: niet "Geen apps
gevonden", maar dat Server Up er niet bij kan en wat eraan te doen is.
# v0.8.04-beta — `--doctor` vraagt het de app in plaats van het af te leiden # v0.8.04-beta — `--doctor` vraagt het de app in plaats van het af te leiden
Drie keer op rij heb ik de verkeerde oorzaak aangewezen voor een lege Drie keer op rij heb ik de verkeerde oorzaak aangewezen voor een lege

View file

@ -1 +1 @@
0.8.04-beta 0.8.05-beta

View file

@ -2937,6 +2937,26 @@ def api_settings_put():
if "NOTIFY_CHANNEL" in updates: if "NOTIFY_CHANNEL" in updates:
if (updates.get("NOTIFY_CHANNEL") or "") not in notify.KANALEN: if (updates.get("NOTIFY_CHANNEL") or "") not in notify.KANALEN:
return jsonify(ok=False, msg="Onbekend meldingskanaal"), 400 return jsonify(ok=False, msg="Onbekend meldingskanaal"), 400
# Kan Server Up bij de mappen die je opgeeft? De container ziet alleen wat
# via BASE_DIR gekoppeld is. Een pad daarbuiten laat zich hier prima
# opslaan, en daarna kijkt de app in een map die binnen de container niet
# bestaat: lege appslijst, alle containers onder "handmatig gestart", en
# nergens een melding.
#
# Deze controle bestond al voor de hoofdmap en voor het verhuizen, maar de
# drie velden schrijven rechtstreeks hierheen — dus liep het langs de wacht.
for sleutel in ("LIBRARY_DIR", "DATA_DIR", "BACKUP_DIR"):
if sleutel not in updates:
continue
nieuw_pad = str(updates[sleutel] or "").strip().rstrip("/")
if not nieuw_pad or nieuw_pad == (current.get(sleutel) or "").rstrip("/"):
continue
controle = paden.bereikbaar(nieuw_pad)
if not controle.get("ok"):
return jsonify(**{**controle, "veld": sleutel,
"msg": _padfout_tekst(controle)}), 400
updates[sleutel] = nieuw_pad
if "NOTIFY_EVENTS" in updates: if "NOTIFY_EVENTS" in updates:
ev = updates["NOTIFY_EVENTS"] ev = updates["NOTIFY_EVENTS"]
if not isinstance(ev, list) or any(e not in notify.EVENTS for e in ev): if not isinstance(ev, list) or any(e not in notify.EVENTS for e in ev):

View file

@ -480,7 +480,11 @@ tailwind.config = {
</div> </div>
</template> </template>
</div> </div>
<div x-show="!filteredStacks.length && !filteredLos.length" class="empty"><span class="mdi mdi-server-network"></span><p x-text="t('no_stacks')">Geen apps gevonden</p></div> <div x-show="!filteredStacks.length && !filteredLos.length" class="empty">
<span class="mdi" :class="padWaarschuwing ? 'mdi-folder-alert-outline text-wr' : 'mdi-server-network'"></span>
<p x-show="!padWaarschuwing" x-text="t('no_stacks')">Geen apps gevonden</p>
<p x-show="padWaarschuwing" class="text-wr max-w-xl mx-auto" x-text="padWaarschuwing"></p>
</div>
<p x-show="filteredLos.length" class="text-xs text-t2 dark:text-t2-dark mt-4" <p x-show="filteredLos.length" class="text-xs text-t2 dark:text-t2-dark mt-4"
x-text="t('manual_hint')"></p> x-text="t('manual_hint')"></p>
@ -2528,6 +2532,7 @@ function app() {
storeQuery: '', storeTag: '', storeCat: '', storeQuery: '', storeTag: '', storeCat: '',
filterOpen: false, filterTagQuery: '', stackMenu: '', stackFilterOpen: false, filterOpen: false, filterTagQuery: '', stackMenu: '', stackFilterOpen: false,
hoofdmap: '', afgeleidePaden: {}, padCheck: {}, padenVoorWijziging: {}, hoofdmap: '', afgeleidePaden: {}, padCheck: {}, padenVoorWijziging: {},
padWaarschuwing: '',
padVelden: [ padVelden: [
{ sleutel: 'LIBRARY_DIR', label: 'library_dir' }, { sleutel: 'LIBRARY_DIR', label: 'library_dir' },
{ sleutel: 'DATA_DIR', label: 'appdata_dir' }, { sleutel: 'DATA_DIR', label: 'appdata_dir' },
@ -2869,7 +2874,19 @@ function app() {
}, },
async loadDashboard() { await this.loadDocker(); await this.loadStacks(); }, async loadDashboard() { await this.loadDocker(); await this.loadStacks(); },
async loadStacks() { const d = await this.rpc('/api/stacks'); this.stacks = Array.isArray(d) ? d : (d.stacks || []); }, async loadStacks() {
const d = await this.rpc('/api/stacks');
this.stacks = Array.isArray(d) ? d : (d.stacks || []);
// Geen apps? Dan is de vraag of dat klopt of dat Server Up er niet bij
// kan. Een lege lijst met "Geen apps gevonden" liet dat verschil weg, en
// een pad buiten de gekoppelde map ziet er in de instellingen prima uit.
this.padWaarschuwing = '';
if (!this.stacks.length && this.settings.LIBRARY_DIR) {
const p = await this.rpc('/api/paths/check?path='
+ encodeURIComponent(this.settings.LIBRARY_DIR));
if (p && !p.ok) this.padWaarschuwing = p.msg || '';
}
},
async loadCategories() { async loadCategories() {
const d = await this.rpc('/api/categories?lang=' + (localStorage.getItem('lang') || 'nl')); const d = await this.rpc('/api/categories?lang=' + (localStorage.getItem('lang') || 'nl'));
this.categories = d.categories || []; this.categories = d.categories || [];

View file

@ -367,3 +367,62 @@ def test_puid_volgt_het_account_van_server_up(client, env):
velden = {f["name"]: f for f in d["fields"]} velden = {f["name"]: f for f in d["fields"]}
assert velden["puid"]["default"] == os.getuid() assert velden["puid"]["default"] == os.getuid()
assert velden["pgid"]["default"] == os.getgid() assert velden["pgid"]["default"] == os.getgid()
# ── Een pad opslaan dat de app niet kan bereiken ─────────────────────────────
# De controle bestond al voor de hoofdmap en voor het verhuizen, maar de drie
# padvelden schrijven rechtstreeks naar PUT /api/settings — dus liep het langs
# de wacht. Sla je daar /srv/... op terwijl alleen /opt/serverup gekoppeld is,
# dan kijkt de app in een map die binnen de container niet bestaat: lege
# appslijst, alle containers onder "handmatig gestart", nergens een melding.
def test_onbereikbaar_pad_wordt_niet_opgeslagen(client, env, monkeypatch):
from core import paden
csrf = login(client)
# De testomgeving zet LIBRARY_DIR als omgevingsvariabele, en die wint van
# het configuratiebestand — dan zou je niet zien of er iets is opgeslagen.
monkeypatch.delenv("LIBRARY_DIR", raising=False)
monkeypatch.setattr(paden, "_in_container", lambda: True)
monkeypatch.setattr(paden, "_mountpunten", lambda: ["/opt/serverup", "/data"])
r = client.put("/api/settings", json={"LIBRARY_DIR": "/srv/serverup/stacks"},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 400, r.get_json()
d = r.get_json()
assert d["veld"] == "LIBRARY_DIR"
assert "BASE_DIR=/srv/serverup" in d["msg"], d["msg"]
import core
assert core.load()["LIBRARY_DIR"] != "/srv/serverup/stacks", \
"het pad is toch opgeslagen"
def test_een_pad_binnen_de_koppeling_mag_gewoon(client, env, monkeypatch, tmp_path):
from core import paden
csrf = login(client)
monkeypatch.delenv("LIBRARY_DIR", raising=False)
monkeypatch.setattr(paden, "_in_container", lambda: True)
monkeypatch.setattr(paden, "_mountpunten", lambda: [str(tmp_path)])
doel = tmp_path / "serverup" / "stacks"
r = client.put("/api/settings", json={"LIBRARY_DIR": str(doel)},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 200, r.get_json()
import core
assert core.load()["LIBRARY_DIR"] == str(doel)
def test_ongewijzigde_paden_blokkeren_het_opslaan_niet(client, env, monkeypatch):
"""Anders kun je met een al kapot pad geen enkele instelling meer wijzigen."""
from core import paden
import core
csrf = login(client)
huidig = core.load()["LIBRARY_DIR"]
monkeypatch.setattr(paden, "_in_container", lambda: True)
monkeypatch.setattr(paden, "_mountpunten", lambda: ["/nergens"])
r = client.put("/api/settings",
json={"LIBRARY_DIR": huidig, "TIMEZONE": "Europe/Amsterdam"},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 200, r.get_json()
assert core.load()["TIMEZONE"] == "Europe/Amsterdam"