diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c677d1..9bed7f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
Drie keer op rij heb ik de verkeerde oorzaak aangewezen voor een lege
diff --git a/VERSION b/VERSION
index d909f80..9344553 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.8.04-beta
+0.8.05-beta
diff --git a/server-up/app.py b/server-up/app.py
index cfd99c8..8c6c357 100644
--- a/server-up/app.py
+++ b/server-up/app.py
@@ -2937,6 +2937,26 @@ def api_settings_put():
if "NOTIFY_CHANNEL" in updates:
if (updates.get("NOTIFY_CHANNEL") or "") not in notify.KANALEN:
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:
ev = updates["NOTIFY_EVENTS"]
if not isinstance(ev, list) or any(e not in notify.EVENTS for e in ev):
diff --git a/server-up/templates/index.html b/server-up/templates/index.html
index 23c7cdc..28ab5f2 100644
--- a/server-up/templates/index.html
+++ b/server-up/templates/index.html
@@ -480,7 +480,11 @@ tailwind.config = {
-
+
+
+
Geen apps gevonden
+
+
@@ -2528,6 +2532,7 @@ function app() {
storeQuery: '', storeTag: '', storeCat: '',
filterOpen: false, filterTagQuery: '', stackMenu: '', stackFilterOpen: false,
hoofdmap: '', afgeleidePaden: {}, padCheck: {}, padenVoorWijziging: {},
+ padWaarschuwing: '',
padVelden: [
{ sleutel: 'LIBRARY_DIR', label: 'library_dir' },
{ sleutel: 'DATA_DIR', label: 'appdata_dir' },
@@ -2869,7 +2874,19 @@ function app() {
},
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() {
const d = await this.rpc('/api/categories?lang=' + (localStorage.getItem('lang') || 'nl'));
this.categories = d.categories || [];
diff --git a/tests/test_paden.py b/tests/test_paden.py
index 2cfa6b7..1247d45 100644
--- a/tests/test_paden.py
+++ b/tests/test_paden.py
@@ -367,3 +367,62 @@ def test_puid_volgt_het_account_van_server_up(client, env):
velden = {f["name"]: f for f in d["fields"]}
assert velden["puid"]["default"] == os.getuid()
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"