ISO 1.29.0 source + an English download page (R-559 slice 4, source only)
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
THE IMAGE IS NOT BUILT AND NOT PUBLISHED BY THIS COMMIT. 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 run 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 site gate refuses the two pages naming different files or hashes. Three texts a person meets before any dashboard become bilingual: Hungarian block first, byte for byte as before, then English, inside the same frame. The pairing banner, the bound banner, /etc/issue (and the postinst's byte-coupled copy), plus an English half on the GRUB entries. The Hungarian is a GOLDEN, not a grep: test/golden/*.hu.txt were captured from the script at183727db9cbefore one English line existed, and the harness asserts each banner's first N lines are exactly the golden. Red-proofed by one changed byte, by an "a" planted in the English block, and by an over-wide line. R-586, found on the way in: running the harness UNCHANGED at the base commit failed two R-496 checks. The script paints with `>`, which truncates a FILE but is a no-op on a console device; ISO 1.28.0's new bound banner (c033b3b) paints straight after the pairing one and wiped it before the check read it.c033b3bdid not touch the harness, and nobody saw it because the harness is in no gate and no CI run. Fixed with a FIFO; production code untouched. The harness being ungated is still open. The release gate's G16 required every Felhom string to be Hungarian and would have STOPPED this publication. Operator ruling 1b of 2026-09-17 supersedes that scope, so G16 is rewritten rather than waived: Hungarian FIRST, pinned by the golden, each secret named once per language. letoltes.html changes by four lines only. The English link is not in the nav - the nav is a shared block site_gates.py pins across every page, and the gate convicted the first attempt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -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)** |
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 ' " <extracted felhom-bootstrap.sh> | grep -viE 'á|é|í|ó|ö|ő|ú|ü|ű|Felhom|%s' # review every hit
|
||||
# 1. the Hungarian is unchanged, and it is a golden rather than a grep
|
||||
docker run --rm -v <repo>/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' <extracted felhom-bootstrap.sh> # 0
|
||||
grep -c 'Tulajdonosi jelmondat' <extracted felhom-bootstrap.sh> # >= 1
|
||||
grep -c 'Owner passphrase' <extracted felhom-bootstrap.sh> # >= 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`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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...'
|
||||
|
||||
@@ -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...'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ]; }"
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
+42
-2
@@ -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 <style>, cache-busted assets —
|
||||
# applies to them exactly as to any other page. Stated as a SET rather than a filename test so a
|
||||
# second English page cannot join by accident.
|
||||
EN_PAGES = {os.path.join("en", "download.html")}
|
||||
ANALYTICS_EXEMPT = {"szolgaltatasok-nonpublic.html"}
|
||||
ANALYTICS_MARK = "https://stats.felhom.eu/script.js"
|
||||
|
||||
@@ -79,9 +89,11 @@ for p, s in emoji_targets.items():
|
||||
if total_emoji:
|
||||
print(" emoji total: %d" % total_emoji)
|
||||
|
||||
# gate 3: nav + footer consistency
|
||||
# gate 3: nav + footer consistency (Hungarian set only — see EN_PAGES)
|
||||
ref_nav = ref_footer = None
|
||||
for p, s in pages.items():
|
||||
if p in EN_PAGES:
|
||||
continue
|
||||
nm = re.search(r"<nav>.*?</nav>", s, re.DOTALL)
|
||||
fm = re.search(r"<footer>.*?</footer>", s, re.DOTALL)
|
||||
if not nm or not fm:
|
||||
@@ -122,6 +134,34 @@ for p, s in pages.items():
|
||||
fail("%s: %d embedded <style> block(s)" % (p, c))
|
||||
|
||||
# gate 8: cache-busting on shared assets
|
||||
|
||||
# gate 12 (R-559): the two download pages name the SAME installer file and the SAME checksum.
|
||||
#
|
||||
# THE FAILURE THIS EXISTS FOR IS A HALF-DONE RELEASE. Publishing a new ISO means editing a filename,
|
||||
# a size and a 64-character hash on TWO pages now. Update one and forget the other and an English
|
||||
# reader downloads yesterday's image, or — worse — checks today's image against yesterday's hash,
|
||||
# fails the comparison, and is told by the page itself not to use the file.
|
||||
#
|
||||
# It compares what the pages SAY, not what is published: a gate cannot reach the bucket, and a
|
||||
# published-file check belongs to the ISO release gate (G11), which does exactly that.
|
||||
_HU_DL, _EN_DL = "letoltes.html", os.path.join("en", "download.html")
|
||||
if _HU_DL in pages and _EN_DL in pages:
|
||||
_iso_re = re.compile(r"felhom-installer-[0-9.]+-pve[0-9.\-]+\.iso")
|
||||
_sha_re = re.compile(r"\b[0-9a-f]{64}\b")
|
||||
hu_iso = sorted(set(_iso_re.findall(pages[_HU_DL])))
|
||||
en_iso = sorted(set(_iso_re.findall(pages[_EN_DL])))
|
||||
hu_sha = sorted(set(_sha_re.findall(pages[_HU_DL])))
|
||||
en_sha = sorted(set(_sha_re.findall(pages[_EN_DL])))
|
||||
if not hu_iso:
|
||||
fail("%s: names no installer file at all" % _HU_DL)
|
||||
if not hu_sha:
|
||||
fail("%s: names no SHA-256 at all" % _HU_DL)
|
||||
if hu_iso != en_iso:
|
||||
fail("the download pages name DIFFERENT installer files: hu=%s en=%s" % (hu_iso, en_iso))
|
||||
if hu_sha != en_sha:
|
||||
fail("the download pages name DIFFERENT checksums: hu=%s en=%s" % (hu_sha, en_sha))
|
||||
else:
|
||||
print(" download pages agree: %s, sha %s…" % (", ".join(hu_iso), (hu_sha or ["-"])[0][:12]))
|
||||
for p, s in pages.items():
|
||||
for m in re.finditer(r"/assets/(site\.css|icons\.svg)([^\"'#\s>]*)", s):
|
||||
if not m.group(2).startswith("?v="):
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
## 2026-09-18 — an English download page (R-559)
|
||||
|
||||
- **`website/en/download.html`** — the English twin of `letoltes.html`. Same installer, same checksum,
|
||||
same structure; `<html lang="en">`, its own English nav and footer, `hreflang` alternates both ways
|
||||
(`x-default` points at the Hungarian), and an entry in `sitemap.xml`.
|
||||
- **`letoltes.html` changed by FOUR lines and nothing else**: three `hreflang` alternates and one
|
||||
`English` link in the page header. The link is deliberately **not** in the nav — the nav is a shared
|
||||
block that `site_gates.py` pins across every page, and the gate convicted the first attempt, which
|
||||
is how the link found its right home.
|
||||
- **The rest of the marketing site stays Hungarian** (operator ruling 1b, 2026-09-17).
|
||||
- **New gate 12: the two download pages must name the SAME installer file and the SAME checksum.**
|
||||
Publishing an ISO now means editing a filename, a size and a 64-character hash on two pages; update
|
||||
one and forget the other and an English reader checks a new image against an old hash, fails, and is
|
||||
told by the page itself not to use the file. Red-proofed both ways.
|
||||
- `en/download.html` is registered in `PAGES` and in the new `EN_PAGES` set, which exempts it from the
|
||||
Hungarian nav/footer comparison and from nothing else.
|
||||
|
||||
**Published:** the site's normal path — push to `main`, git-sync picks it up in ~1–2 min. No
|
||||
`site.css` change, so no cache-bust bump was needed.
|
||||
|
||||
# website/ changelog (newest on top)
|
||||
|
||||
## 2026-09-15 — New page: /letoltes (the installer download)
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Download — the Felhom installer | Felhom.eu</title>
|
||||
<meta name="description" content="The Felhom home server installer: download, checksum, and what to check before you install.">
|
||||
<meta name="robots" content="noindex, follow">
|
||||
<link rel="canonical" href="https://felhom.eu/en/download">
|
||||
<link rel="alternate" hreflang="hu" href="https://felhom.eu/letoltes">
|
||||
<link rel="alternate" hreflang="en" href="https://felhom.eu/en/download">
|
||||
<link rel="alternate" hreflang="x-default" href="https://felhom.eu/letoltes">
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://felhom.eu/en/download">
|
||||
<meta property="og:title" content="Download the Felhom installer">
|
||||
<meta property="og:description" content="Download, checksum, and what to check before you install.">
|
||||
<meta property="og:image" content="https://felhom.eu/assets/og-image.png">
|
||||
<meta property="og:locale" content="en_GB">
|
||||
<meta property="og:site_name" content="Felhom.eu">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Download the Felhom installer">
|
||||
<meta name="twitter:description" content="Download, checksum, and what to check before you install.">
|
||||
<meta name="twitter:image" content="https://felhom.eu/assets/og-image.png">
|
||||
<!-- shared assets: bump ?v= together on any site.css/icons.svg change -->
|
||||
<link rel="stylesheet" href="/assets/site.css?v=2">
|
||||
<link rel="icon" type="image/png" href="/assets/logo_favicon_2.svg">
|
||||
<script defer src="https://stats.felhom.eu/script.js" data-website-id="d419db57-5e1c-48df-bdb3-c29f25fb1661"></script>
|
||||
</head>
|
||||
<body class="page-letoltes">
|
||||
<nav>
|
||||
<div class="container">
|
||||
<a href="/" class="logo">
|
||||
<img src="/assets/logo.png" alt="Felhom.eu logo">
|
||||
<span class="logo-text">felhom.eu</span>
|
||||
</a>
|
||||
<ul class="nav-links">
|
||||
</ul>
|
||||
<button class="hamburger" id="hamburger" aria-label="Open menu" aria-expanded="false">
|
||||
<span class="hamburger-box">
|
||||
<span class="hamburger-line"></span>
|
||||
<span class="hamburger-line"></span>
|
||||
<span class="hamburger-line"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="mobile-overlay" id="mobileOverlay"></div>
|
||||
|
||||
<main>
|
||||
<section class="page-header">
|
||||
<div class="container">
|
||||
<h1>The Felhom <span>installer</span></h1>
|
||||
<p>This page is for someone who has received an installation guide from their operator. The installer turns a whole machine into a Felhom server.</p>
|
||||
<p><a href="/letoltes" lang="hu" hreflang="hu">Magyar</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<div class="container">
|
||||
<div class="info-card">
|
||||
<h3><svg class="ico-lg" aria-hidden="true"><use href="/assets/icons.svg?v=1#i-triangle-alert"/></svg> Before you download</h3>
|
||||
<p>Installing <strong>erases every piece of data</strong> on the disk you select. The installer never chooses for you: it lists the disks with their size and type, and overwrites the one you pick.</p>
|
||||
<p>Unplug the external drive you use for backups before you start. If you are not certain which disk is the right one, <strong>stop and call your operator</strong>.</p>
|
||||
<p>The guide from your operator describes every step, screen by screen. Do not start without it.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section alt-bg">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>Download</h2>
|
||||
<p>Felhom installer 1.28.0 (based on Proxmox VE 9.2)</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p><a href="https://iso.felhom.eu/felhom-installer-1.28.0-pve9.2-1.iso"><strong>felhom-installer-1.28.0-pve9.2-1.iso</strong></a> — 1.6 GB (1,705,322,496 bytes)</p>
|
||||
<p>Checksum (SHA-256):</p>
|
||||
<div class="highlight-box">
|
||||
<p><code>a4cd9b6ddcb55bae3700ab307084d2b699330cc20710688d5816318f04f6d635</code></p>
|
||||
</div>
|
||||
<p>The same checksum as a file: <a href="https://iso.felhom.eu/felhom-installer-1.28.0-pve9.2-1.iso.sha256">felhom-installer-1.28.0-pve9.2-1.iso.sha256</a></p>
|
||||
<p>To check it on Linux or macOS: <code>sha256sum felhom-installer-1.28.0-pve9.2-1.iso</code>. On Windows, in PowerShell: <code>Get-FileHash felhom-installer-1.28.0-pve9.2-1.iso</code>. The string it prints must match the one above. If it does not match, do not use the file — download it again.</p>
|
||||
<p>The downloaded file has to be written to a USB stick of at least 4 GB (with Balena Etcher or Rufus, for example). The guide covers that too.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 felhom.eu — Your own cloud, your own rules</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
(function() {
|
||||
var hamburger = document.getElementById('hamburger');
|
||||
var navLinks = document.querySelector('.nav-links');
|
||||
var overlay = document.getElementById('mobileOverlay');
|
||||
var body = document.body;
|
||||
|
||||
if (!hamburger || !navLinks || !overlay) {
|
||||
console.warn('[mobile-menu] Missing elements:', {
|
||||
hamburger: !!hamburger,
|
||||
navLinks: !!navLinks,
|
||||
overlay: !!overlay
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
function openMenu() {
|
||||
hamburger.classList.add('is-active');
|
||||
navLinks.classList.add('is-open');
|
||||
overlay.classList.add('is-visible');
|
||||
body.classList.add('menu-open');
|
||||
hamburger.setAttribute('aria-expanded', 'true');
|
||||
hamburger.setAttribute('aria-label', 'Close menu');
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
hamburger.classList.remove('is-active');
|
||||
navLinks.classList.remove('is-open');
|
||||
overlay.classList.remove('is-visible');
|
||||
body.classList.remove('menu-open');
|
||||
hamburger.setAttribute('aria-expanded', 'false');
|
||||
hamburger.setAttribute('aria-label', 'Open menu');
|
||||
}
|
||||
|
||||
hamburger.addEventListener('click', function() {
|
||||
if (navLinks.classList.contains('is-open')) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
});
|
||||
|
||||
overlay.addEventListener('click', closeMenu);
|
||||
|
||||
navLinks.querySelectorAll('a').forEach(function(link) {
|
||||
link.addEventListener('click', closeMenu);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && navLinks.classList.contains('is-open')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 900 && navLinks.classList.contains('is-open')) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -7,6 +7,9 @@
|
||||
<meta name="description" content="A Felhom otthoni szerver telepítője: letöltés, ellenőrző összeg, és mire figyelj a telepítés előtt.">
|
||||
<meta name="robots" content="noindex, follow">
|
||||
<link rel="canonical" href="https://felhom.eu/letoltes">
|
||||
<link rel="alternate" hreflang="hu" href="https://felhom.eu/letoltes">
|
||||
<link rel="alternate" hreflang="en" href="https://felhom.eu/en/download">
|
||||
<link rel="alternate" hreflang="x-default" href="https://felhom.eu/letoltes">
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://felhom.eu/letoltes">
|
||||
@@ -56,6 +59,7 @@
|
||||
<div class="container">
|
||||
<h1>A Felhom <span>telepítő</span></h1>
|
||||
<p>Ez az oldal annak szól, aki az üzemeltetőtől telepítési útmutatót kapott. A telepítő egy teljes gépet alakít át Felhom szerverré.</p>
|
||||
<p><a href="/en/download" lang="en" hreflang="en">English</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -35,4 +35,9 @@
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://felhom.eu/en/download</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
Reference in New Issue
Block a user