diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 61e648f5..9728a155 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -761,6 +761,7 @@ class (an image `VOLUME` at an unmounted path) is still live — `immich-server` | **R-583** | **[P3-LOW] The test-notification mail was the one customer mail that did not follow the language — and it is the mail an operator would use to CHECK that the language works.** FOUND 2026-09-18 during hub v0.118.0's own live proof: I went to press "send test notification" for the English demo box, read `sendTestEmail` first, and found it composes its own hardcoded Hungarian subject and body instead of going through `FormatCustomerEmail`. Every other customer mail had been localised. **The general form worth keeping: the surface you would use to CHECK a feature is the one most worth checking first — a broken instrument that reports success is worse than a broken feature.** FIXED and shipped as hub v0.118.1 in the same session: the two sentences extracted byte-for-byte into `mail.test.subject` / `mail.test.body`, red-proofed against the hardcoded version, and proven live in both languages 74 seconds apart on demo-hp. | **CLOSED 2026-09-18 - hub v0.118.1** | | **R-584** | **[P2-MED] Credential-bearing probe scripts were left in a live guest's `/tmp` for hours, across three releases.** FOUND 2026-09-18 while cleaning up after slice 3's live proof: `probeB.sh`, `probeB2.sh`, `probeB3.sh`, `probeC.sh` and `probeD.sh` were still in demo-hp guest 9201's `/tmp` from slice 2's releases B, C and D earlier the same session, and all five carried the controller password INLINE (`-d "password=$PW"` with the value substituted). The standing rule in `.claude/rules/ui-hungarian.md` says exactly this: *delete any credential-bearing helper from `/tmp` (host AND guest) when done*. They were not deleted at the end of each phase; they were found by grepping my own litter for secret-shaped strings before removing it, which is a check that happened only because a later phase went looking. All five are shredded. **Why it is a row and not a note: the rule existed, was loaded, and was still not followed three times running — so the rule is not the mechanism.** The password is the shared demo one (Tier-0 boxes, `~/.config/credentials`), unchanged; rotating it is cheap and is the operator's call. **Fix shape:** never inline a credential in a helper — write it to a mode-0600 file and read it with curl's `@file` form, which this run did and which is why this run's own scripts were clean; and make the last act of a phase that pushed a script to a box the `shred -u` of it, the same way R-320 made evidence-copying the last act of a phase. | **READY - rank P2-MED; owner: CC (the mechanism), operator (whether to rotate)** | | **R-585** | **[P3-LOW] Six event producers still send Hungarian only, so an English household can see one Hungarian line in some mails.** FOUND 2026-09-18 by localisation slice 3 Part B (R-558, controller v0.256.0), which converted 19 of the customer-facing producers and left these: `backup_failed`, `db_dump_failed`, `backup_integrity_ok`, `backup_integrity_failed`, `offbox_enlarge_blocked` and `local_api_endpoint_drift`. **Why they were left:** each receives its sentence already FINISHED from another package, so the key and its arguments no longer exist by the time the notifier sees it — converting them means changing their callers, not the notifier. The 15 operator-tier types are deliberately excluded and are NOT part of this row: the operator reads Hungarian. **Why it matters more than it looks: `offbox_enlarge_blocked` has no `customerMessages` entry on the hub**, so its raw sentence IS the household's mail rather than an extra line under a translated headline — for that one type an English household gets a wholly Hungarian mail, not a mostly-English one. **Fix shape:** push the key and its arguments down from each caller (the shape slice 2 release B already used for errors, `util.MsgError`), then add each to the `convertedProducers` table in `internal/notify/message_customer_test.go`, which is the list both language tests walk. | **READY - rank P3-LOW; owner: CC** | +| **R-586** | **[P2-MED] The ISO bootstrap harness captured the console to a FILE, so each banner erased the one before it — two checks were RED for two days and nobody saw.** FOUND 2026-09-18 starting slice 4 (R-559): running `scripts/iso/test/bootstrap-modes.sh` unchanged at `183727db9c44` reported `FAIL: R-496: banner painted to the console seam` and `FAIL: R-496: banner names the Tulajdonosi jelmondat`. **Cause:** the script paints each banner with `> "$CONSOLE_DEV"`. On a real console that is a character device and truncation is a no-op, so every paint appears; pointed at a plain file, as the harness did, each paint TRUNCATES. Commit `c033b3b` (ISO 1.28.0, R-535, 2026-09-16) added `print_bound_banner`, which paints immediately after the pairing banner in the same invocation — from that commit the pairing banner was wiped before the check read it. `c033b3b` did not touch the harness. **Why it survived: the harness is in NO gate and NO CI run** — not in `repo_gates.py`, not in `.gitea/`; it runs only when a person runs it, and between 09-16 and 09-18 nobody did. FIXED in the same session: the harness points `FELHOM_CONSOLE_DEV` at a FIFO with a background reader, which restores device semantics (opening a FIFO with `>` truncates nothing) and lets a test see EVERY paint — which slice 4's golden checks then needed anyway. Production code untouched. **What is still open: the harness remains outside every gate.** It needs a container, so it cannot join `repo_gates.py --fast`, which is what both the pre-push hook and CI run — meaning a non-fast entry would still never execute. **Fix shape:** either give CI a container-capable job that runs it, or make the ISO release gate's G16 the place it is required (done for G16 this session — so it now runs at least once per ISO release, which is better than never but later than a push). | **READY - rank P2-MED; owner: CC** | | **R-537** | **[P1-HIGH] The app-backup page labels the tier-1 backup „DB + Konfig + Adatok" and prints the app's data-drive size next to it — but the tier-1 unit contains NO drive-side app data at all.** MEASURED 2026-09-16 on the drill box (fresh install, controller 0.243.0, one drive, tier 2 and tier 3 both „Nincs beállítva"): five photos (3 000 000 B) were uploaded into Nextcloud through its own WebDAV interface, then the customer-visible „Mentés most" was pressed (`POST /api/backup/run` → 200, the unit grew 25 337 B → 978 MB). The resulting unit's `manifest.json` lists `db-dumps` + three **docker volume** dumps and nothing else; listing the 781 MB `nextcloud_nextcloud_html.tar` (29 346 entries, positive control `version.php` = 3 hits) gives **`Fotok` = 0 and `nyaralas` = 0**, and `./data/` is the empty bind-mount point. A `find` over the whole `backups/` tree for `*appdata*` / `*Fotok*` returns nothing. The page nevertheless renders „1. mentés … DB + Konfig + Adatok" and „Nextcloud Adatlemez 65.1 MB" — a size measured on exactly the data it does not copy (`internal/web/handlers.go:1176-1178`, `BackupContents`). **This is a truth defect, not a design defect:** `07-backup-architecture.md` §6.2 places nextcloud's file leg at **Tier 2 and Tier 3 only**, and its „[FACT] What the whole-guest tiers do NOT carry" says `mp8 /mnt/felhom-drives` is out of vzdump scope (confirmed live: „excluding bind mount point mp8 … (not a volume)"). So on a one-drive box with no off-site tier — the state every fresh install starts in — the household's files are in **no backup**, while the page says „Adatok". Same family as R-517/R-518. **Fix shape:** render tier-1 contents from the capture set actually written (`ComputeCaptureSet`), so a unit with no file leg reads „DB + Konfig" and the drive size is not shown beside it; and say on the page that the app's files need tier 2 or tier 3. Evidence: `audits/evidence-drill-0243-2026-09-16/phase2-f10.txt`. **CLOSED 2026-09-16 — controller v0.244.0, proven live.** The contents label is computed PER TIER from what that tier captures: Tier 1 says „Adatok" only when the app's data really is in the volumes the unit captured, and a class-A app carries one sentence saying where its files ARE protected. Proven on demo-hp through the page the customer opens: Paperless-ngx reads „1. mentés … DB + Konfig" with „Az alkalmazás fájljait a távoli másolat (és a második meghajtó) védi …", while its „2. mentés" row still reads „DB + Konfig + Adatok". Red-proof: restoring the old app-shaped label fails `TestAppBackupRows_Tier1LabelDoesNotClaimFilesItCannotHold`. **RE-PROVEN 2026-09-16 on a FRESH box** (installed from the built ISO 1.28.0, controller 0.244.0, off-site on by default): the Nextcloud row read „1. mentés … DB + Konfig" with the new sentence, „2. mentés … Nincs 2. (off-drive) másolat", „3. mentés Sikeres restic → …your-storagebox.de"; „DB + Konfig + Adatok" appeared ZERO times while the local unit held no file leg. | **CLOSED 2026-09-16 — controller v0.244.0 (proven live on demo-hp)** | | **R-538** | **[P1-HIGH] A tier-1 app restore reports plain success and leaves Nextcloud listing files whose bytes were never in the backup — and it destroys the app's own trash, the customer's last copy.** MEASURED 2026-09-16 on the drill box, F10 („a child deletes the photo folder"): the five photos were deleted through Nextcloud (DELETE 204, PROPFIND 404), then restored through the page exactly as a customer would (`POST /backup/restore` `stack_name=nextcloud` `snapshot_id=helyi` → 302, finished in **35 s**, „A(z) nextcloud: 3 adatkötet és az adatbázis visszaállítva — az alkalmazás újraindult."). Afterwards the folder is back and **lists all five photos**, and **none of them opens**: `GET nyaralas-1..5` = 404 / 503×4 with `Sabre\DAV\Exception\NotFound`, while the positive controls at the same moment pass (`status.php` 200, WebDAV PUT 201, GET 200). Cause: the replayed MariaDB dump (11:01:45Z) knows the photos, the bytes live on `mp8` and were never captured (R-537). **Worse:** the bytes were still on the drive in Nextcloud's own trash (`appdata/nextcloud/admin/files_trashbin/files/Fotok.d1789556707/nyaralas-1..5.jpg`, all five present) and the restored database no longer references them — the trash listing comes back **empty**, so „restore from trash", the one route that would have worked, is gone. The customer is left with five unopenable photos, a success message, and no warning. **Fix shape:** before replaying a database whose app has an uncaptured file leg, refuse or warn („ennek az alkalmazásnak a fájljai nincsenek ebben a mentésben — a visszaállítás után a fájlok hiányozni fognak"); and never present a DB-only restore of a class-A app as a complete one. Evidence: `audits/evidence-drill-0243-2026-09-16/phase2-f10.txt`. **CLOSED 2026-09-16 — controller v0.244.0, proven live.** A unit restore refuses before anything is touched when the unit cannot return the app's drive-side files, and names the route that can. Fired live on demo-hp: `POST /backup/restore` for paperless-ngx → 302 with „Ez a mentés nem tartalmazza az alkalmazás fájljait, ezért nem állítjuk vissza az adatbázist föléjük — a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza …", and the app read `running` before AND after, so nothing was stopped and no trash was made unreachable. The database-and-settings-only path exists as a separately worded second step. Red-proof: disabling the guard fails `TestUnitRestore_RefusesWhenTheUnitCannotHoldTheFiles`. **RE-PROVEN 2026-09-16 on a FRESH box, and this time the refusal had somewhere to point:** after five photos were deleted, `POST /backup/restore` was refused with „…a fájlok így a helyükön maradnak. A fájlok a távoli másolatból állíthatók vissza: … „Teljes visszaállítás (fájlok + adatbázis)"", the app read `running` before AND after, and the wastebasket was untouched. The off-site route then returned all five photos — 200 with the exact uploaded sizes and sha256 IDENTICAL to the originals, 5/5, with a negative control. Evidence: `audits/evidence-backup-promise-2026-09-16/phaseE-photos.txt`. | **CLOSED 2026-09-16 — controller v0.244.0 (proven live on demo-hp)** | | **R-525** | **[P3-LOW] FileBrowser has its own login; putting it behind the dashboard session (traefik forwardAuth or Quantum proxy auth) is a new mechanism nobody has measured.** Filed 2026-09-15 by the P1-fixes task (B.5). R-513 closed the default-password hole with a generated password; a household still has two logins. **What it needs:** a spike on a scratch guest — forwardAuth to the controller session, and what FileBrowser Quantum does with a trusted header. | **READY — rank P3-LOW; owner: CC (spike)** | diff --git a/documentation/runbooks/VOLUNTEER-first-hour.md b/documentation/runbooks/VOLUNTEER-first-hour.md index b58c472d..9ca4b75d 100644 --- a/documentation/runbooks/VOLUNTEER-first-hour.md +++ b/documentation/runbooks/VOLUNTEER-first-hour.md @@ -10,6 +10,9 @@ > > 1. **Where the installer is** — the Hungarian download page `felhom.eu/letoltes` (published with ISO > v1.27.0 after the operator's yes; `iso.felhom.eu/` itself still has no index — R-504). +> **An English twin exists from 2026-09-18 (R-559): `felhom.eu/en/download`.** Same installer, same +> checksum — a site gate refuses the two pages naming different ones. The rest of the marketing site +> stays Hungarian (operator ruling 1b); this guide is Hungarian until slice 6 (R-561). > 2. **That the installer screens are English Proxmox screens**, and what to type on each. > 3. **A hostname to type** — the installer refuses its own default `pve.example.invalid`. > 4. **An e-mail to type** — the installer prefills `mail@example.invalid`. diff --git a/documentation/runbooks/iso-release-gate.md b/documentation/runbooks/iso-release-gate.md index fd0bc250..f20ec51f 100644 --- a/documentation/runbooks/iso-release-gate.md +++ b/documentation/runbooks/iso-release-gate.md @@ -278,18 +278,41 @@ systemctl is-enabled pvebanner.service # masked text — the operator admin UI, in English — was the first thing a household read. The reboot is part of the criterion because an overwrite without the mask passes once and fails on the next boot. -### G16 — every Felhom-authored string on the volunteer's path is Hungarian, and names each secret once +### G16 — every Felhom-authored string is Hungarian FIRST, and names each secret once ```bash -grep -n "printf ' " | grep -viE 'á|é|í|ó|ö|ő|ú|ü|ű|Felhom|%s' # review every hit -grep -c 'jelszavad' # 0 -grep -c 'Tulajdonosi jelmondat' # >= 1 +# 1. the Hungarian is unchanged, and it is a golden rather than a grep +docker run --rm -v /scripts/iso:/work felhom-iso-assistant:trixie bash /work/test/bootstrap-modes.sh +# -> the four R-559 golden/English/width checks per banner, and the /etc/issue pair, all ok +# 2. the secret names, on the extracted script +grep -c 'jelszavad' # 0 +grep -c 'Tulajdonosi jelmondat' # >= 1 +grep -c 'Owner passphrase' # >= 1 (its English twin) ``` -**PASS =** no English sentence in the review list, `jelszavad` absent, the R-323 name present. Search -with ASCII fragments where possible and keep one positive and one negative control in the record. -*Scope, stated so it is not read wider:* the **Proxmox installer's own screens are English** and stay so -under the 2026-07-31 ruling; G14 requires the guide to answer each of them. This criterion covers what -Felhom writes: the GRUB menu, the console banner and `/etc/issue`, the first-boot screens. +**PASS =** the harness green on every R-559 check, `jelszavad` absent, and each secret named **once +per language**. Search with ASCII fragments and keep one positive and one negative control in the +record. + +> **AMENDED 2026-09-18 (ISO 1.29.0, R-559), and the amendment is the point of this note.** +> +> This criterion used to read *"every Felhom-authored string on the volunteer's path is **Hungarian**"* +> with **PASS = no English sentence in the review list**. It encoded the 2026-07-31 scope, and it was +> right when it was written. +> +> **Operator ruling 1b of 2026-09-17 supersedes that scope**: *"The console banner and the download +> page ARE in scope"* (`documentation/architecture/10-localisation.md` §11). Slice 4 makes the three +> console texts **bilingual** — the Hungarian block byte-for-byte as before, an English block appended +> inside the same frame. Under the old wording that work would have FAILED this gate and the +> publication would have stopped, which is the gate doing exactly its job on an instruction that had +> moved on without it. +> +> **The criterion was therefore rewritten, not waived.** What it protects is unchanged and is now +> stronger: the Hungarian is pinned by a GOLDEN captured before any English existed, rather than by a +> reviewer reading a list. What it no longer forbids is an English sentence *below* the Hungarian one. +> +> *Scope, stated so it is not read wider:* the **Proxmox installer's own screens are English** and stay +> so under the 2026-07-31 ruling; G14 requires the guide to answer each of them. This criterion covers +> what Felhom writes: the GRUB menu, the console banners and `/etc/issue`. --- diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index 82eb89c0..6f670089 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,3 +1,51 @@ +## ISO 1.29.0 source — the box's own screen speaks English too (2026-09-18, R-559) + +**Source only. The image is NOT built or published by this commit** — see the release note at the end. + +Three texts a person meets before they ever see a dashboard become **bilingual**: Hungarian block +first, byte for byte as before, then one blank line, then English, inside the same frame. + +- `print_pairing_banner`, `print_bound_banner`, `install_felhom_issue` — and the `postinst`'s copy of + the issue text, which is byte-coupled to the bootstrap's (the harness `cmp`s them). +- The one-line fallbacks carry both: `Párosító kód / Pairing code: %s`. +- The GRUB entries gain an English half: `Felhom telepítés / Install Felhom` (and the text-mode twin). + `iso-repack.sh:405` greps a substring, so it still matches. +- `/etc/issue` stays ASCII in its English half for the same reason the Hungarian half avoids ő/ű: the + login screen is painted before the Latin-2 console font loads. + +**The Hungarian is a GOLDEN, not a grep.** `test/golden/{pairing,bound,issue}.hu.txt` were captured +from the script at `183727db9c44` before one English line existed; the harness asserts each banner's +first N lines are exactly the golden. A grep for one phrase would pass a banner whose other nine lines +had been reworded. Red-proofed by a single changed byte. + +Also asserted per banner: the English block exists, carries **no Hungarian letter** (with the +Hungarian block as the positive control), and **every line fits 80 columns** — the console's width, +counted in characters, because the Hungarian lines are multi-byte and `wc -c` would convict them +wrongly. The pairing code appears once per language; the bound banner carries none. + +### A harness defect found on the way in, and it had been red for two days (R-586) + +Running the harness UNCHANGED at the base commit failed two R-496 checks. The script paints with +`> "$CONSOLE_DEV"`; on a real console that is a device and truncation is a no-op, but the harness +pointed it at a plain FILE, so each banner erased the one before it. ISO 1.28.0's new bound banner +(commit `c033b3b`) paints right after the pairing banner — and did not touch the harness. **Nobody +saw it because the harness is in no gate and no CI run.** The harness now uses a FIFO, which restores +device semantics; production code is untouched. + +### The release gate had to be amended, not waived + +`iso-release-gate.md` **G16** read *"every Felhom-authored string on the volunteer's path is +Hungarian — PASS = no English sentence"*. It encoded the 2026-07-31 scope and would have **stopped +this publication**. Operator ruling **1b of 2026-09-17** supersedes that scope ("the console banner +and the download page ARE in scope"). G16 is rewritten: Hungarian FIRST, pinned by the golden, each +secret named once per language. What it protects is now stronger, not weaker. + +**RELEASE STATE: the image is not built and not published.** `ISO_VERSION` is `1.29.0` in the build +script and the source is ready, but publishing to `iso.felhom.eu` is public and irreversible and its +runbook requires a proof install on **both** menu entries plus the 16-criterion gate against the exact +uploaded bytes. That is the operator's step. The download pages therefore still name **1.28.0** — the +image that is actually published — and a new gate refuses them naming different things. + ## i18n inventory + wire-contract allowlist entry (2026-09-17, localisation starter) - **`i18n_inventory.py` (new, a survey — never fails a build).** Counts every customer-visible diff --git a/scripts/iso/build-felhom-iso.sh b/scripts/iso/build-felhom-iso.sh index bcedd0fb..2b95512a 100755 --- a/scripts/iso/build-felhom-iso.sh +++ b/scripts/iso/build-felhom-iso.sh @@ -48,7 +48,7 @@ set -euo pipefail # does not exist: the ISO is a frozen artifact, while felhom-host-install.sh is fetched at RUN TIME # from the website's git-sync of `main` (R-94/R-110), so whatever version an ISO carries, the script a # box runs is always current. Coupling them would invent a constraint. The claim is corrected instead. -ISO_VERSION="1.28.0" # the ISO's own version. INDEPENDENT of felhom-host-install.sh's SCRIPT_VERSION, +ISO_VERSION="1.29.0" # the ISO's own version. INDEPENDENT of felhom-host-install.sh's SCRIPT_VERSION, # which is fetched at run time from main and is not frozen into the image. IMAGE="${FELHOM_ISO_ASSISTANT_IMAGE:-felhom-iso-assistant:trixie}" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/iso/felhom-bootstrap.sh b/scripts/iso/felhom-bootstrap.sh index 99bf3d7b..89c41e76 100644 --- a/scripts/iso/felhom-bootstrap.sh +++ b/scripts/iso/felhom-bootstrap.sh @@ -72,9 +72,15 @@ print_pairing_banner() { printf ' ezt a kódot és a Tulajdonosi jelmondatodat\n' printf ' (az 5 szót a Felhom üzemeltetőjétől kaptad).\n\n' printf ' Ez a képernyő magától frissül — nincs teendő a\n' - printf ' doboznál, és nyugodtan itt hagyhatod bekapcsolva.\n' + printf ' doboznál, és nyugodtan itt hagyhatod bekapcsolva.\n\n' + printf ' Pairing code: %s\n\n' "$code" + printf ' Open the link from your e-mail and enter this code\n' + printf ' together with your Owner passphrase (the 5 words you\n' + printf ' received from your Felhom operator).\n\n' + printf ' This screen refreshes itself. There is nothing to do\n' + printf ' at the box; you can leave it switched on.\n' printf '================================================\n\n' - } > "$CONSOLE_DEV" 2>/dev/null || printf 'Párosító kód: %s\n' "$code" + } > "$CONSOLE_DEV" 2>/dev/null || printf 'Párosító kód / Pairing code: %s\n' "$code" } # print_bound_banner (R-535, v1.28.0) — the pairing banner is the LAST thing the console shows, and it @@ -93,9 +99,13 @@ print_bound_banner() { printf ' Felhom — a doboz össze van kötve. ✔\n\n' printf ' A beállítás magától folytatódik, ez néhány percig tart.\n' printf ' A vezérlőpult címét az e-mailben kapott levél tartalmazza.\n\n' - printf ' Ezen a gépen nincs több teendőd.\n' + printf ' Ezen a gépen nincs több teendőd.\n\n' + printf ' Felhom — the box is linked.\n\n' + printf ' Setup carries on by itself; this takes a few minutes.\n' + printf ' Your dashboard address is in the e-mail you received.\n\n' + printf ' There is nothing left to do on this machine.\n' printf '================================================\n\n' - } > "$CONSOLE_DEV" 2>/dev/null || printf 'A doboz össze van kötve.\n' + } > "$CONSOLE_DEV" 2>/dev/null || printf 'A doboz össze van kötve. / The box is linked.\n' } # install_felhom_issue (R-496, v1.27.0) — the text above the console login prompt is Felhom's, not @@ -122,6 +132,9 @@ install_felhom_issue() { printf ' Felhom otthoni szerver\n\n' printf ' Ezen a gépen most nincs dolgod, és bejelentkezni sem kell.\n' printf ' A beállításhoz kövesd a Felhomtól kapott útmutatót.\n\n' + printf ' Felhom home server\n\n' + printf ' There is nothing to do on this machine, and no need to log in.\n' + printf ' Follow the guide you received from Felhom.\n\n' } > "$tmp" 2>/dev/null && mv -f "$tmp" "$ISSUE_FILE" 2>/dev/null; then log "console: $ISSUE_FILE is the Felhom text (no admin URL)" else diff --git a/scripts/iso/grub/grub-release.cfg.tmpl b/scripts/iso/grub/grub-release.cfg.tmpl index 4b58cd01..bac0bd2d 100644 --- a/scripts/iso/grub/grub-release.cfg.tmpl +++ b/scripts/iso/grub/grub-release.cfg.tmpl @@ -65,14 +65,14 @@ set timeout_style=menu set timeout=15 set default=0 -menuentry 'Felhom telepítés' --class felhom --class os { +menuentry 'Felhom telepítés / Install Felhom' --class felhom --class os { echo 'A Felhom telepítése indul — válassza ki a lemezt a telepítőben...' @@LINUX_GFX@@ echo 'Rendszerbetöltő betöltése...' @@INITRD@@ } -menuentry 'Felhom telepítés (szöveges mód)' --class felhom --class os { +menuentry 'Felhom telepítés (szöveges mód) / Install Felhom (text mode)' --class felhom --class os { echo 'A Felhom telepítése indul szöveges módban...' @@LINUX_TUI@@ echo 'Rendszerbetöltő betöltése...' diff --git a/scripts/iso/grub/grub.cfg.tmpl b/scripts/iso/grub/grub.cfg.tmpl index 195b739f..998d0117 100644 --- a/scripts/iso/grub/grub.cfg.tmpl +++ b/scripts/iso/grub/grub.cfg.tmpl @@ -68,7 +68,7 @@ set timeout_style=menu set timeout=5 set default=0 -menuentry 'Felhom telepítés' --class felhom --class os { +menuentry 'Felhom telepítés / Install Felhom' --class felhom --class os { echo 'A Felhom telepítése indul...' @@LINUX@@ echo 'Rendszerbetöltő betöltése...' diff --git a/scripts/iso/pkg/debian/postinst b/scripts/iso/pkg/debian/postinst index 65c24955..d55e2ead 100755 --- a/scripts/iso/pkg/debian/postinst +++ b/scripts/iso/pkg/debian/postinst @@ -42,6 +42,9 @@ if [ "${1:-}" = "configure" ]; then printf ' Felhom otthoni szerver\n\n' printf ' Ezen a gépen most nincs dolgod, és bejelentkezni sem kell.\n' printf ' A beállításhoz kövesd a Felhomtól kapott útmutatót.\n\n' + printf ' Felhom home server\n\n' + printf ' There is nothing to do on this machine, and no need to log in.\n' + printf ' Follow the guide you received from Felhom.\n\n' } > /etc/issue.felhom-tmp 2>/dev/null && mv -f /etc/issue.felhom-tmp /etc/issue 2>/dev/null; then echo "felhom-bootstrap postinst: /etc/issue is the Felhom text" >> "$LOG" 2>&1 || true else diff --git a/scripts/iso/test/bootstrap-modes.sh b/scripts/iso/test/bootstrap-modes.sh index 6e2a60eb..77c49f3c 100644 --- a/scripts/iso/test/bootstrap-modes.sh +++ b/scripts/iso/test/bootstrap-modes.sh @@ -26,6 +26,32 @@ FAKE=/work/fakebin; rm -rf "$FAKE"; mkdir -p "$FAKE" CALLS=/work/curl.log export PATH="$FAKE:$PATH" fail=0 +# --- console capture (R-586) ------------------------------------------------------------------- +# +# THE SCRIPT WRITES EACH BANNER WITH `>`, WHICH TRUNCATES A FILE. On a real console that is a +# character device and truncation is a no-op — every paint simply appears. Pointed at a plain FILE, +# as this harness did until now, each banner ERASES the one before it. +# +# That is not hypothetical: ISO 1.28.0 (commit c033b3b, R-535) added print_bound_banner, which paints +# right after the pairing banner in the same invocation. From that commit onward +# `grep 'Párosító kód' /work/console.out` looked for a banner the NEXT paint had already wiped, and +# the two R-496 checks below have been FAILING ever since. Nobody saw it: this harness is not in CI +# and is not in repo_gates.py, so it runs only when someone runs it. +# +# A FIFO restores the device semantics: opening it with `>` truncates nothing, and a background +# reader accumulates every paint. `console_reset` starts a fresh capture per scenario. +CONSOLE_FIFO=/work/console.fifo +console_reset() { + [ -n "${CONSOLE_CAT_PID:-}" ] && kill "$CONSOLE_CAT_PID" 2>/dev/null + rm -f /work/console.out "$CONSOLE_FIFO" + mkfifo "$CONSOLE_FIFO" + # The reader holds the FIFO open so the script's short-lived `>` opens never block or EOF it. + ( while :; do cat "$CONSOLE_FIFO" >> /work/console.out 2>/dev/null || true; done ) & + CONSOLE_CAT_PID=$! + : > /work/console.out +} +console_settle() { sleep 0.2; } # let the reader drain before a check reads the file + say() { echo "TEST: $*"; } check() { if eval "$2"; then echo " ok: $1"; else echo " FAIL: $1"; fail=1; fi; } @@ -200,12 +226,68 @@ FELHOM_HUB_URL=https://hub.example ENV echo 204 > /work/poll-mode echo 3 > /work/sleep.flip # after 3 in-script waits the hub "binds" (poll flips to 200) -rm -f /work/console.out -env FELHOM_CONSOLE_DEV=/work/console.out FELHOM_ISSUE_FILE=/work/issue bash "$BSTRAP"; rc=$? +console_reset +env FELHOM_CONSOLE_DEV="$CONSOLE_FIFO" FELHOM_ISSUE_FILE=/work/issue bash "$BSTRAP"; rc=$? +console_settle # R-496: the pairing banner names the secret the way the self-bind mail and page do (R-323). check "R-496: banner painted to the console seam" "grep -q 'Párosító kód' /work/console.out" check "R-496: banner names the Tulajdonosi jelmondat" "grep -q 'Tulajdonosi jelmondat' /work/console.out" check "R-496: banner no longer says 'jelszavad'" "! grep -q 'jelszavad' /work/console.out" + +# ===== R-559 (slice 4): the console is BILINGUAL — Hungarian frozen, English real, both fit 80 cols ===== +# +# THE HUNGARIAN IS A GOLDEN, NOT A GREP. /work/golden/*.hu.txt were captured from the script at +# 183727db9c44, before a single English line was added. A grep for one phrase would pass a banner +# whose other nine lines had been reworded; the golden is the whole block, byte for byte. +# +# The golden is the banner WITHOUT its closing frame line, because the English block is appended +# INSIDE the frame and the closing line therefore moves down. So the assertion is: the banner's first +# N lines are exactly the golden, where N is the golden's own length. +split_banners() { + # $1 = the console capture. Writes /work/b.pairing and /work/b.bound, each the whole banner from + # its opening frame line to its closing one inclusive. Frames are counted, not guessed: the + # capture holds exactly two banners and therefore four frame lines. + awk -v out=/work/b ' + /^=+$/ { f++ + name = (f<=2 ? "pairing" : "bound") + print > (out "." name) + next } + { if (f==1 || f==3) print > (out "." (f==1 ? "pairing" : "bound")) } + ' "$1" +} +hu_head_matches() { # $1 = banner file, $2 = golden file + local n; n=$(wc -l < "$2") + head -n "$n" "$1" | diff -u "$2" - >/dev/null +} +split_banners /work/console.out +for b in pairing bound; do + check "R-559: the $b banner's Hungarian block is byte-identical to the golden" \ + "hu_head_matches /work/b.$b /work/golden/$b.hu.txt" + # The English block is everything after the Hungarian block, above the closing frame. + n=$(wc -l < "/work/golden/$b.hu.txt") + tail -n +$((n+1)) "/work/b.$b" | grep -v '^={40,}$' > "/work/en.$b" + check "R-559: the $b English block exists" "[ -s /work/en.$b ]" + # ASCII-fragment search with BOTH controls (rule 9.8): the English block must carry no Hungarian + # letter, and the Hungarian block must carry one — or the check is matching nothing at all. + check "R-559: the $b English block has no Hungarian letter" "! grep -q '[áéíóöőúüűÁÉÍÓÖŐÚÜŰ]' /work/en.$b" + check "R-559: CONTROL — the $b Hungarian block does have one" "grep -q '[áéíóöőúüűÁÉÍÓÖŐÚÜŰ]' /work/golden/$b.hu.txt" + # 80 columns is the console's width, counted in CHARACTERS not bytes: the Hungarian lines are + # multi-byte, so `wc -c` would convict them wrongly. + check "R-559: every $b line fits 80 columns" \ + "[ \"\$(awk '{ print length(\$0) }' /work/b.$b | sort -rn | head -1)\" -le 80 ]" +done +# The pairing code appears once per language — a person reads one block, and must find it there. +check "R-559: the pairing code appears in BOTH blocks" \ + "[ \"\$(grep -c 'TST-CDE' /work/b.pairing)\" -eq 2 ]" +check "R-559: the bound banner carries no pairing code" "! grep -q 'TST-CDE' /work/b.bound" + +# /etc/issue: the Hungarian half frozen, the English half added, still no ő/ű and still no admin URL. +check "R-559: /etc/issue still opens with the golden Hungarian" \ + "head -n \$(wc -l < /work/golden/issue.hu.txt) /work/issue | diff -q /work/golden/issue.hu.txt - >/dev/null" +check "R-559: /etc/issue has an English half" "grep -q 'Felhom home server' /work/issue" +check "R-559: /etc/issue English half has no Hungarian letter" \ + "! sed -n '/Felhom home server/,\$p' /work/issue | grep -q '[áéíóöőúüűÁÉÍÓÖŐÚÜŰ]'" +check "R-559: /etc/issue names no admin URL" "! grep -q '8006' /work/issue" check "single invocation ran to done (exit 0)" "[ $rc -eq 0 ]" check "POSTed /appliance/register" "grep -q '/appliance/register' $CALLS" check "appliance token persisted 0600" "[ -f /etc/felhom/.bootstrap-done ] || { [ -f /etc/felhom/appliance-token ] && [ \"\$(stat -c %a /etc/felhom/appliance-token)\" = 600 ]; }" diff --git a/scripts/iso/test/golden/bound.hu.txt b/scripts/iso/test/golden/bound.hu.txt new file mode 100644 index 00000000..281b3824 --- /dev/null +++ b/scripts/iso/test/golden/bound.hu.txt @@ -0,0 +1,7 @@ +================================================ + Felhom — a doboz össze van kötve. ✔ + + A beállítás magától folytatódik, ez néhány percig tart. + A vezérlőpult címét az e-mailben kapott levél tartalmazza. + + Ezen a gépen nincs több teendőd. diff --git a/scripts/iso/test/golden/issue.hu.txt b/scripts/iso/test/golden/issue.hu.txt new file mode 100644 index 00000000..7c409913 --- /dev/null +++ b/scripts/iso/test/golden/issue.hu.txt @@ -0,0 +1,6 @@ + + Felhom otthoni szerver + + Ezen a gépen most nincs dolgod, és bejelentkezni sem kell. + A beállításhoz kövesd a Felhomtól kapott útmutatót. + diff --git a/scripts/iso/test/golden/pairing.hu.txt b/scripts/iso/test/golden/pairing.hu.txt new file mode 100644 index 00000000..86039583 --- /dev/null +++ b/scripts/iso/test/golden/pairing.hu.txt @@ -0,0 +1,11 @@ +================================================ + Felhom — a doboz készen áll, és a párosításra vár. + + Párosító kód: TST-CDE + + Nyisd meg az e-mailben kapott linket, és add meg + ezt a kódot és a Tulajdonosi jelmondatodat + (az 5 szót a Felhom üzemeltetőjétől kaptad). + + Ez a képernyő magától frissül — nincs teendő a + doboznál, és nyugodtan itt hagyhatod bekapcsolva. diff --git a/scripts/site_gates.py b/scripts/site_gates.py index 78064d4a..37cc7cf6 100644 --- a/scripts/site_gates.py +++ b/scripts/site_gates.py @@ -21,7 +21,17 @@ W = os.path.join(ROOT, "website") PAGES = ["index.html", "kapcsolat.html", "alkalmazasok.html", "technologiak.html", "biztonsagimentes.html", "gyik.html", "szolgaltatasok-nonpublic.html", - "letoltes.html"] + "letoltes.html", + # R-559 (2026-09-18): the English download page. The marketing site stays Hungarian by + # operator ruling 1b; this one page is its English twin because a volunteer who reads no + # Hungarian still has to fetch the installer. + os.path.join("en", "download.html")] + +# EN_PAGES carry their OWN nav and footer, in English, and must not be compared with the Hungarian +# set. Everything else — BOM, emoji, banned tokens, analytics, no