diff --git a/tests/test_appstore_vorm.py b/tests/test_appstore_vorm.py index ccbdea3..f998e6b 100644 --- a/tests/test_appstore_vorm.py +++ b/tests/test_appstore_vorm.py @@ -44,8 +44,12 @@ STORE = os.path.join(REPO, "umbrel-app-store.yml") # De volgorde uit de packaging-documentatie. Wat de spec niet noemt (icon, # backupIgnore, submitter, submission) mag erachter, niet ertussen. +# +# `storage`, `folderAccess` en `environment` zijn er sinds umbrelOS 2.0 bij en +# staan direct na `id` (skill umbrel-package-app, opgehaald 09-10-2026). VOORGESCHREVEN = [ - "manifestVersion", "id", "category", "name", "version", "tagline", + "manifestVersion", "id", "storage", "folderAccess", "environment", + "category", "name", "version", "tagline", "description", "releaseNotes", "developer", "website", "dependencies", "repo", "support", "port", "gallery", "path", ] @@ -327,6 +331,63 @@ def test_data_onder_data(u, app): not ontbreekt, "ontbreekt: %r" % ontbreekt) +# De mount-opties die umbreld toestaat op een bron onder ${APP_DATA_DIR}/data +# zodra de datamap verplaatsbaar is. Elke andere optie laat de compose falen met +# [apps-data-root-invalid-compose]. Uit app.ts van umbreld, 09-10-2026. +DATAROOT_OPTIES = { + "ro", "rw", "private", "rprivate", "shared", "rshared", "slave", "rslave", + "z", "Z", "consistent", "cached", "delegated", +} + + +def test_verplaatsbare_opslag(u, app): + """Een app met `storage:` heeft precies `dataRoot: data`, en mounts die umbreld kan omleiden. + + Dit is het enige manifestveld dat umbreld wél streng valideert: elke andere + waarde dan `data` maakt het manifest ongeldig, en dan verschijnt de app niet. + Met het veld erin herschrijft umbreld elke bron onder ${APP_DATA_DIR}/data naar + de plek die de gebruiker kiest, en weigert dan een pad dat uit data/ ontsnapt + of een mount-optie die niet in DATAROOT_OPTIES staat. + + Een app zonder `storage:` valt hier buiten; het veld is optioneel. Maar het + kan er niet meer uit als het er eenmaal in staat: een installatie die al + verplaatst is, start dan niet meer. + """ + tekst = lees(os.path.join(REPO, app, "umbrel-app.yml")) + regels = tekst.splitlines() + if not any(r.startswith("storage:") for r in regels): + return + + i = next(i for i, r in enumerate(regels) if r.startswith("storage:")) + blok = [] + for r in regels[i + 1:]: + if r and not r[0].isspace(): + break + if r.strip() and not r.strip().startswith("#"): + blok.append(r.strip()) + u.check("%s: storage heeft precies dataRoot: data" % app, + blok == ["dataRoot: data"], "gevonden: %r" % blok) + + pad = os.path.join(REPO, app, "docker-compose.yml") + if not os.path.isfile(pad): + return + fout = [] + for regel in lees(pad).splitlines(): + kaal = regel.strip() + if not kaal.startswith("- ${APP_DATA_DIR}/data"): + continue + delen = kaal[2:].strip().strip('"').split(":") + bron = delen[0][len("${APP_DATA_DIR}/"):] + if ".." in bron.split("/"): + fout.append(kaal) + if len(delen) > 2: + opties = delen[2].split(",") + if any(o not in DATAROOT_OPTIES for o in opties): + fout.append(kaal) + u.check("%s: elke mount onder data/ kan umbreld omleiden" % app, + not fout, "geweigerd: %r" % fout) + + def test_containernamen_zijn_volledig(u, app): """Verwijzingen tussen containers gebruiken de volledige naam. @@ -534,6 +595,7 @@ def main(): test_compose_bestaat_en_hangt_samen(u, app) test_image_tag_is_de_gebouwde(u, app) test_data_onder_data(u, app) + test_verplaatsbare_opslag(u, app) test_containernamen_zijn_volledig(u, app) test_geen_werkbestanden_in_de_app_map(u, app) test_templates_hebben_geen_losse_dollars(u, app) diff --git a/whatsnext-atreides-services/docker-compose.yml b/whatsnext-atreides-services/docker-compose.yml index 48b29d7..cda93a5 100644 --- a/whatsnext-atreides-services/docker-compose.yml +++ b/whatsnext-atreides-services/docker-compose.yml @@ -57,4 +57,10 @@ services: # De datamap: db.sqlite, de minuutbestanden per maand en secrets.json. Een # koersreeks per moment is niet opnieuw op te halen, dus dit is de map die # in de back-up van umbrelOS hoort. + # + # Sinds `storage: dataRoot: data` in het manifest (0.0.3) herschrijft + # umbrelOS 2.0 deze bron naar de plek die de gebruiker kiest, en zet hij + # er `create_host_path: false` bij. Daarom moet data/app in de repo staan + # (.gitkeep), en mag hier geen mount-optie bij die umbreld niet kent. + # Staat de map op een USB-schijf, dan gaat hij NIET mee in de back-up. - ${APP_DATA_DIR}/data/app:/data diff --git a/whatsnext-atreides-services/umbrel-app.yml b/whatsnext-atreides-services/umbrel-app.yml index ecf1dc0..079f1fa 100644 --- a/whatsnext-atreides-services/umbrel-app.yml +++ b/whatsnext-atreides-services/umbrel-app.yml @@ -6,9 +6,17 @@ # Wat de spec niet noemt, staat onderaan dit bestand en niet ertussen. manifestVersion: 1 id: whatsnext-atreides-services +# umbrelOS 2.0: de gebruiker kan de hele datamap (${APP_DATA_DIR}/data) in de +# app-instellingen naar een USB-schijf verplaatsen. Alleen ext4 en geen +# netwerkshare; die laatste weigert umbrelOS voor app-opslag, en de SQLite- +# bestanden in WAL-stand horen er ook niet op. De waarde moet letterlijk `data` +# zijn, en het veld kan er niet meer uit: een installatie die al verplaatst is, +# start dan niet meer. Zie Umbrel-appstore-spec.md §7. +storage: + dataRoot: data category: finance name: Atreides Services -version: "0.0.2" +version: "0.0.3" tagline: Your own price feed, recorded around the clock description: >- A price service that runs on your Umbrel and keeps what it records. It fetches prices day @@ -26,8 +34,9 @@ description: >- mempool.space, and brings the administration app with its System page and an optional login of its own. The API for other apps follows in a later version. releaseNotes: >- - First version in this store: spot prices every minute from Bitvavo and mempool.space, and - the administration app. The API for other apps is not there yet. + On umbrelOS 2.0 the app's storage can now be moved to a USB drive (ext4) in the app + settings. Keep in mind that umbrelOS does not back up an app whose storage is on a USB + drive. developer: "WhatsNext?" website: https://sc.kamenier-hamer.nl/sysop/UmbrelApps dependencies: []