Website: machine estimator v3 — the bar shows what the box's memory is used for (still unlisted, R-997)
gates / gates (push) Successful in 6m29s

The bar keeps its length (the fits-well box memory) and is split into base system
(box - guest), Felhom's own services, each app's peak (largest first, the dashboard's
palette minus amber and its first blue; beyond 8 apps one group) and spare room
(guest - peak) — all from the one rule; a test proves the parts sum to the bar within
1 MB. A legend under it (not sticky) and one sentence explain the recommended size.
Typical figure moved into the method fold-out. English: 'an 8 GB machine'.
This commit is contained in:
admin committed 2026-10-13 16:08:00 +02:00
1 parent 16dca64834
commit 92e9d45cf5
10 files changed
+186 -38

No files matched your search

+1 -1
View File
@@ -121,7 +121,7 @@ stopping line that lies.
| **R-516** | Install & onboarding | P3 | **[P3-LOW] English a customer meets on a fresh box and its apps' first screens — enumerated by the big night.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, ISO 1.27.1, controller 0.242.0). Felhom-owned: (1) the dashboard menu item **„Debug"**; (2) the dashboard CPU tile **„Load: 0.29 / 0.39 / 0.37"**; (3) the launcher tile **„Filebrowser"** opens a login in English with no Felhom text (R-513); (4) the storage page mixes formal „Adjon hozzá / Csatlakoztasson" with the product's „te". App first screens a household meets before any Felhom text helps: (5) **Uptime Kuma 2.4 opens on „Which database would you like to use?"** (SQLite / Embedded MariaDB, „Next") — the app card's „Első lépések" does not mention it; (6) PrivateBin, Gokapi, AdventureLog and FileBrowser UIs are English (the apps' own). Already rows: the Proxmox installer screens (R-495, answered by the guide), `wiki.DOMAIN` (R-498). **Fix shape:** rename „Debug"/„Load" (controller); add the Uptime Kuma database step to its card, or pre-seed `db-config.json` for SQLite in the template (catalog). **Added by F4 (20:04:54Z):** (7) the storage page prints the disconnect time as a raw ISO UTC string „Leválasztva: 2026-09-14T19:58:02Z"; (8) the „Meghajtó leválasztva" banner appears twice on every page; (9) „4 telepített alkalmazás nem fut — nézze meg a rendszermonitort" uses the formal form. **Added by F7 (20:50–21:00Z, system disk at 95 %):** (10) a banner on every page in English, „**SSD disk usage high: 90%**"; (11) the dashboard tile reads „Rendszer (/) 61.8 GB / 68.7 GB (**90%**)" while `df` reports **95 %** (reserved blocks ignored), and „(/)" labels the data volume `/mnt/sys_drive`; the deploy page says nothing about free disk. **Added by the i18n spike (2026-09-17, controller v0.247.0):** (12) **six formal („ön") forms in the converted dashboard copy** — „Olvassa be telefonnal" (launcher QR hint), „Biztosan kikapcsolja a megosztást?" (launcher), „Ha újratelepíti … importálnia kell" (the layout's remove-app modal), „Kérjük, vegye fel a kapcsolatot" (backups empty state). They were NOT fixed — a localisation release may not change Hungarian bytes — and are now COUNTED by `controller/scripts/i18n_missing_gate.py` (`HU_FORMAL_CEILING` = 6, a ratchet: a new one convicts, fixing one lowers it). The inventory (`audits/I18N-INVENTORY-2026-09-17.md`) is the list this row closes against in localisation slice 6 (R-561). **Extended 2026-09-17 by slice 1 (controller v0.248.0–v0.250.0):** the converted copy now counts **16** formal forms (`HU_FORMAL_CEILING` 6 → 10 → 12 → 16, each raise stated, Hungarian unchanged by rule) — and the count is an UNDER-count: the gate's stem list sees 4 on the release C pages (storage, drive wizards, debug), while a wider list („írja be”, „adja meg”, „adjon hozzá”, „válassza ki”, „biztosan eltávolítja”, „engedélyezze”, „hozzon létre”, „kattintson”) finds at least **22** keys there alone (login „Adja meg a jelszavát”, the NAS guide, the storage confirms). Widen the stems when this row is worked; the ceiling rises with them. **NARROWED 2026-09-20 by localisation slice 6's walk, item by item** (`audits/i18n-slice6-2026-09-20/R-516-item-by-item.md`): item 2 FIXED (slice 1), items 3 and 6 APP-OWNED (and for an English household item 6 is an advantage), item 11 is a wrong NUMBER rather than a language and needs its own row, item 1 is correct English for an English reader and still open for a Hungarian one. **Items 4, 7, 8, 9 and 10 are NOT DECIDABLE FROM AN ENGLISH WALK** - they are about Hungarian copy quality, a disconnected drive and a full disk, none of which this walk had. **What this row is now waiting for is a HUNGARIAN walk on a box with a second drive**, not another English one; saying it closed would be the kind of closure that makes a register stop meaning anything. **-- 2026-10-08:** the recovery screen's own answers are Go literals in Hungarian only — 14 `renderRecovery` messages in `controller/internal/web/recovery_handlers.go` (seen: the safe default „A művelet nem fejeződött be…" rendered on an English page in a red-proof run). Today's new R-304 sentence is a bundle key in both languages; the 14 old ones are not. | **NARROWED 2026-09-20 - rank P3-LOW; owner: CC; needs a Hungarian walk** **2026-10-05 (burn-down night): items 1 and 4 FIXED on controller `main`** (`d7efa7b`): the menu says „Hibakeresés” (English „Debug”); the storage pages use the te-form (formal-form ceiling 17 → 14; 116 parity fixtures changed by exactly those bytes). Item 2 was already fixed. **Left:** items 5–6 (apps' own screens) and the rest. **2026-10-06: items 1 and 4 DELIVERED** (controller v0.298.0). **2026-10-06 (burn-down night, later): items 7–10 and 12 FIXED on controller `main`** (`e4774e6`, `2e5ef7e`): the disconnect time in local time; one banner per unpluLine truncated | **R-516** | Install & onboarding | P3 | **[P3-LOW] English a customer meets on a fresh box and its apps' first screens — enumerated by the big night.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, ISO 1.27.1, controller 0.242.0). Felhom-owned: (1) the dashboard menu item **„Debug"**; (2) the dashboard CPU tile **„Load: 0.29 / 0.39 / 0.37"**; (3) the launcher tile **„Filebrowser"** opens a login in English with no Felhom text (R-513); (4) the storage page mixes formal „Adjon hozzá / Csatlakoztasson" with the product's „te". App first screens a household meets before any Felhom text helps: (5) **Uptime Kuma 2.4 opens on „Which database would you like to use?"** (SQLite / Embedded MariaDB, „Next") — the app card's „Első lépések" does not mention it; (6) PrivateBin, Gokapi, AdventureLog and FileBrowser UIs are English (the apps' own). Already rows: the Proxmox installer screens (R-495, answered by the guide), `wiki.DOMAIN` (R-498). **Fix shape:** rename „Debug"/„Load" (controller); add the Uptime Kuma database step to its card, or pre-seed `db-config.json` for SQLite in the template (catalog). **Added by F4 (20:04:54Z):** (7) the storage page prints the disconnect time as a raw ISO UTC string „Leválasztva: 2026-09-14T19:58:02Z"; (8) the „Meghajtó leválasztva" banner appears twice on every page; (9) „4 telepített alkalmazás nem fut — nézze meg a rendszermonitort" uses the formal form. **Added by F7 (20:50–21:00Z, system disk at 95 %):** (10) a banner on every page in English, „**SSD disk usage high: 90%**"; (11) the dashboard tile reads „Rendszer (/) 61.8 GB / 68.7 GB (**90%**)" while `df` reports **95 %** (reserved blocks ignored), and „(/)" labels the data volume `/mnt/sys_drive`; the deploy page says nothing about free disk. **Added by the i18n spike (2026-09-17, controller v0.247.0):** (12) **six formal („ön") forms in the converted dashboard copy** — „Olvassa be telefonnal" (launcher QR hint), „Biztosan kikapcsolja a megosztást?" (launcher), „Ha újratelepíti … importálnia kell" (the layout's remove-app modal), „Kérjük, vegye fel a kapcsolatot" (backups empty state). They were NOT fixed — a localisation release may not change Hungarian bytes — and are now COUNTED by `controller/scripts/i18n_missing_gate.py` (`HU_FORMAL_CEILING` = 6, a ratchet: a new one convicts, fixing one lowers it). The inventory (`audits/I18N-INVENTORY-2026-09-17.md`) is the list this row closes against in localisation slice 6 (R-561). **Extended 2026-09-17 by slice 1 (controller v0.248.0–v0.250.0):** the converted copy now counts **16** formal forms (`HU_FORMAL_CEILING` 6 → 10 → 12 → 16, each raise stated, Hungarian unchanged by rule) — and the count is an UNDER-count: the gate's stem list sees 4 on the release C pages (storage, drive wizards, debug), while a wider list („írja be”, „adja meg”, „adjon hozzá”, „válassza ki”, „biztosan eltávolítja”, „engedélyezze”, „hozzon létre”, „kattintson”) finds at least **22** keys there alone (login „Adja meg a jelszavát”, the NAS guide, the storage confirms). Widen the stems when this row is worked; the ceiling rises with them. **NARROWED 2026-09-20 by localisation slice 6's walk, item by item** (`audits/i18n-slice6-2026-09-20/R-516-item-by-item.md`): item 2 FIXED (slice 1), items 3 and 6 APP-OWNED (and for an English household item 6 is an advantage), item 11 is a wrong NUMBER rather than a language and needs its own row, item 1 is correct English for an English reader and still open for a Hungarian one. **Items 4, 7, 8, 9 and 10 are NOT DECIDABLE FROM AN ENGLISH WALK** - they are about Hungarian copy quality, a disconnected drive and a full disk, none of which this walk had. **What this row is now waiting for is a HUNGARIAN walk on a box with a second drive**, not another English one; saying it closed would be the kind of closure that makes a register stop meaning anything. **-- 2026-10-08:** the recovery screen's own answers are Go literals in Hungarian only — 14 `renderRecovery` messages in `controller/internal/web/recovery_handlers.go` (seen: the safe default „A művelet nem fejeződött be…" rendered on an English page in a red-proof run). Today's new R-304 sentence is a bundle key in both languages; the 14 old ones are not. | **NARROWED 2026-09-20 - rank P3-LOW; owner: CC; needs a Hungarian walk** **2026-10-05 (burn-down night): items 1 and 4 FIXED on controller `main`** (`d7efa7b`): the menu says „Hibakeresés” (English „Debug”); the storage pages use the te-form (formal-form ceiling 17 → 14; 116 parity fixtures changed by exactly those bytes). Item 2 was already fixed. **Left:** items 5–6 (apps' own screens) and the rest. **2026-10-06: items 1 and 4 DELIVERED** (controller v0.298.0). **2026-10-06 (burn-down night, later): items 7–10 and 12 FIXED on controller `main`** (`e4774e6`, `2e5ef7e`): the disconnect time in local time; one banner per unpluLine truncated
| **R-991** | Install & onboarding | P3 | **Nothing a buyer can read says which machines Felhom installs on, and a quarter of the forum beginners own only machines it cannot install on.** FOUND 2026-10-12 (forum research, `audits/forum-research-2026-10-12/FELHOM.md` §2). Searched `documentation/architecture/`, `website/` (`gyik`, `technologiak`, `letoltes`) and `scripts/felhom-host-install.sh`: no CPU-architecture, RAM, disk or network floor is written anywhere. The download page says only that the installer is built on Proxmox VE 9.2 — x86-64 only (inferred from Proxmox, not stated on the page). Of the 33 forum beginners who name what they own, 8 own only a Synology/QNAP/other NAS box, a router with a USB disk or a Raspberry Pi. The selfh.st survey reading reached the same gap independently (R-989, its recommendation 1). **Needs (operator):** the floor itself (RAM and disk minimum; whether thin clients and old laptops with 4–8 GB are accepted) — then one short section on the download page and in the FAQ. Not changed here: the research task forbade website edits. | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator naming the floor | Operator: name the minimum (RAM, disk, x86-64); then CC writes the section on `letoltes.html` and `gyik.html` (both languages). If nothing is done: a buyer with a Pi or a Synology downloads an ISO that cannot run | operator | | **R-991** | Install & onboarding | P3 | **Nothing a buyer can read says which machines Felhom installs on, and a quarter of the forum beginners own only machines it cannot install on.** FOUND 2026-10-12 (forum research, `audits/forum-research-2026-10-12/FELHOM.md` §2). Searched `documentation/architecture/`, `website/` (`gyik`, `technologiak`, `letoltes`) and `scripts/felhom-host-install.sh`: no CPU-architecture, RAM, disk or network floor is written anywhere. The download page says only that the installer is built on Proxmox VE 9.2 — x86-64 only (inferred from Proxmox, not stated on the page). Of the 33 forum beginners who name what they own, 8 own only a Synology/QNAP/other NAS box, a router with a USB disk or a Raspberry Pi. The selfh.st survey reading reached the same gap independently (R-989, its recommendation 1). **Needs (operator):** the floor itself (RAM and disk minimum; whether thin clients and old laptops with 4–8 GB are accepted) — then one short section on the download page and in the FAQ. Not changed here: the research task forbade website edits. | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator naming the floor | Operator: name the minimum (RAM, disk, x86-64); then CC writes the section on `letoltes.html` and `gyik.html` (both languages). If nothing is done: a buyer with a Pi or a Synology downloads an ISO that cannot run | operator |
| **R-993** | Install & onboarding | P3 | **Operator: approve the minimum machine in `documentation/audits/sizing-2026-10-13/SIZING.md` §5** (the input R-991's hardware page needs). MEASURED 2026-10-13 on the N100 demo box (61 apps deployed and removed through the product, 15 min at rest each; heavy apps with one real action). Proposal: **minimum** x86-64, 4 cores (N100 class), **8 GB**, 256 GB SSD; **recommended 16 GB**, 512 GB SSD; out: ARM / Raspberry Pi, Synology-QNAP, routers, cloud VPS. Worked from the floor (2.5 GB for the whole box with no apps) + Immich, Jellyfin, Nextcloud, Home Assistant, Vaultwarden, Paperless-ngx at their peaks (5.0 GB) + 50 % headroom ≈ 12.3 GB. 8 GB runs that set without Immich; 16 GB runs all of it twice over. Data for the estimator: `sizing.json`. **2026-10-13 (estimator session) — the installer accepts a host BELOW this minimum:** `autosize_guest_caps` has no floor on the host, only on the guest; on a 4 GB box (MemTotal ≈ 3.5 GiB) the guest gets host − 1 GB ≈ 2.4 GB and Proxmox keeps ≈ 1 GB, where it used ≈ 2.0 GB on the N100 (SIZING.md §1). Whatever minimum is approved, nothing enforces it at install today. The three-verdict wording of §5 now matches the estimator page (`/gepigeny`, R-997). | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's decision | Operator: approve or change §5. If nothing is done: no hardware page; a buyer with a Raspberry Pi can still download the installer (R-991 stays open) | operator | | **R-993** | Install & onboarding | P3 | **Operator: approve the minimum machine in `documentation/audits/sizing-2026-10-13/SIZING.md` §5** (the input R-991's hardware page needs). MEASURED 2026-10-13 on the N100 demo box (61 apps deployed and removed through the product, 15 min at rest each; heavy apps with one real action). Proposal: **minimum** x86-64, 4 cores (N100 class), **8 GB**, 256 GB SSD; **recommended 16 GB**, 512 GB SSD; out: ARM / Raspberry Pi, Synology-QNAP, routers, cloud VPS. Worked from the floor (2.5 GB for the whole box with no apps) + Immich, Jellyfin, Nextcloud, Home Assistant, Vaultwarden, Paperless-ngx at their peaks (5.0 GB) + 50 % headroom ≈ 12.3 GB. 8 GB runs that set without Immich; 16 GB runs all of it twice over. Data for the estimator: `sizing.json`. **2026-10-13 (estimator session) — the installer accepts a host BELOW this minimum:** `autosize_guest_caps` has no floor on the host, only on the guest; on a 4 GB box (MemTotal ≈ 3.5 GiB) the guest gets host − 1 GB ≈ 2.4 GB and Proxmox keeps ≈ 1 GB, where it used ≈ 2.0 GB on the N100 (SIZING.md §1). Whatever minimum is approved, nothing enforces it at install today. The three-verdict wording of §5 now matches the estimator page (`/gepigeny`, R-997). | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's decision | Operator: approve or change §5. If nothing is done: no hardware page; a buyer with a Raspberry Pi can still download the installer (R-991 stays open) | operator |
| **R-997** | Install & onboarding | P3 | **Operator: try the machine estimator at https://felhom.eu/gepigeny (English: /en/sizing) and say „list it” or what to change.** BUILT 2026-10-13: a household ticks apps and sees, for an 8, 16 and 32 GB box, fits well / fits, but tight / does not fit, the totals, and which apps make the CPU work hard for a short time; numbers from the 2026-10-13 N100 measurement (`documentation/audits/sizing-2026-10-13/`). LIVE but UNLISTED: noindex, `Disallow` in robots.txt, not in the sitemap, linked from nowhere — `site_gates.py` gate 25 keeps it that way. It names no minimum machine (R-993) and gives no power or noise figure. Its JavaScript was never run by CC (no browser or JS engine on DooPlex): the operator's try is its first real run. **To list it** (one website task, after the power readings): move the pair from `UNLISTED` to `TWINS`, drop noindex and the two Disallow lines, add both URLs to the sitemap, link it from the menu and the FAQ. **v2 live 2026-10-13 afternoon:** fold-out categories and a memory bar (solid = fits well, thin mark = tight) with one line naming the box to buy; the reviewer's v1 browser check (Playwright: console clean, 390 px without sideways scroll, only felhom.eu + stats.felhom.eu) asked for the shorter page. | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's try | Operator: open the link, tick a few apps, then say „list it” or what to change. If nothing is done: the page stays unlisted and nobody finds it | operator | | **R-997** | Install & onboarding | P3 | **Operator: try the machine estimator at https://felhom.eu/gepigeny (English: /en/sizing) and say „list it” or what to change.** BUILT 2026-10-13: a household ticks apps and sees, for an 8, 16 and 32 GB box, fits well / fits, but tight / does not fit, the totals, and which apps make the CPU work hard for a short time; numbers from the 2026-10-13 N100 measurement (`documentation/audits/sizing-2026-10-13/`). LIVE but UNLISTED: noindex, `Disallow` in robots.txt, not in the sitemap, linked from nowhere — `site_gates.py` gate 25 keeps it that way. It names no minimum machine (R-993) and gives no power or noise figure. Its JavaScript was never run by CC (no browser or JS engine on DooPlex): the operator's try is its first real run. **To list it** (one website task, after the power readings): move the pair from `UNLISTED` to `TWINS`, drop noindex and the two Disallow lines, add both URLs to the sitemap, link it from the menu and the FAQ. **v2 live 2026-10-13 afternoon:** fold-out categories and a memory bar (solid = fits well, thin mark = tight) with one line naming the box to buy; the reviewer's v1 browser check (Playwright: console clean, 390 px without sideways scroll, only felhom.eu + stats.felhom.eu) asked for the shorter page. **v3 live 2026-10-13 evening:** the bar shows what the box memory is used for (base system, Felhom, each app, spare room) with a legend and one explaining sentence; „an 8 GB machine” fixed. The reviewer's v2 browser check: console clean, 1,677 px tall at 390 px (was 4,592), the bar sticky at 53 px and 124 px tall. | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's try | Operator: open the link, tick a few apps, then say „list it” or what to change. If nothing is done: the page stays unlisted and nobody finds it | operator |
| **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC | | **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC |
| **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator | | **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator |
+9
View File
@@ -1,3 +1,12 @@
## 2026-10-13 (evening) — estimator v3: segments and the English article under test
- `build_sizing_web.py`: `bar_app_colours` (8) in the data file, so the script holds no number of its own.
- `test_build_sizing_web.py` (10 tests): the bar's segments (base system = box − guest, Felhom, each app's peak,
spare = guest − peak) add up to the bar within 1 MB for the four cases and for nothing selected; base and spare are
never negative; the household preset by hand (spare = 5,515 × 0.5, base = 4,096 + 612); every English
recommendation gets „an 8" / „a 12" … Red-proofed: the „an 8" rule removed, the JS computing spare from the typical
figure, and segments off by 50 MB each convicted.
## 2026-10-13 (afternoon) — estimator v2: `build_sizing_web.py` box sizes, tests for the inverse ## 2026-10-13 (afternoon) — estimator v2: `build_sizing_web.py` box sizes, tests for the inverse
- `build_sizing_web.py`: `boxes_gb` = 8, 12, 16, 24, 32, 48, 64; new `bar_marks_gb` and `bar_max_gb`. - `build_sizing_web.py`: `boxes_gb` = 8, 12, 16, 24, 32, 48, 64; new `bar_marks_gb` and `bar_max_gb`.
+2
View File
@@ -36,6 +36,7 @@ GUEST_RULE = {"reserve_mib": 4096, "max_gap_mib": 2048, "min_mib": 4096, "ceilin
BOXES_GB = [8, 12, 16, 24, 32, 48, 64] # the common sizes a recommendation names (v2: 12, 24, 48, 64 added) BOXES_GB = [8, 12, 16, 24, 32, 48, 64] # the common sizes a recommendation names (v2: 12, 24, 48, 64 added)
BAR_MARKS_GB = [8, 16, 24, 32, 48, 64] # the memory bar's marks; the last one reads "64+" BAR_MARKS_GB = [8, 16, 24, 32, 48, 64] # the memory bar's marks; the last one reads "64+"
BAR_MAX_GB = 64 # the bar's linear scale is 0..BAR_MAX_GB BAR_MAX_GB = 64 # the bar's linear scale is 0..BAR_MAX_GB
BAR_APP_COLOURS = 8 # v3: apps get their own colour up to this many; the rest join one segment
BUILTIN = {"filebrowser"} BUILTIN = {"filebrowser"}
# One short line per medium / heavy-in-bursts app (the 9 of 2026-10-13). sizing.json's English notes, tidied. # One short line per medium / heavy-in-bursts app (the 9 of 2026-10-13). sizing.json's English notes, tidied.
@@ -119,6 +120,7 @@ def build():
boxes_gb=BOXES_GB, boxes_gb=BOXES_GB,
bar_marks_gb=BAR_MARKS_GB, bar_marks_gb=BAR_MARKS_GB,
bar_max_gb=BAR_MAX_GB, bar_max_gb=BAR_MAX_GB,
bar_app_colours=BAR_APP_COLOURS,
categories=[dict(id=h[0], id_en=e[0], name_hu=h[1], name_en=e[1]) for h, e in zip(hu_cats, en_cats)], categories=[dict(id=h[0], id_en=e[0], name_hu=h[1], name_en=e[1]) for h, e in zip(hu_cats, en_cats)],
apps=apps, apps=apps,
) )
+38 -1
View File
@@ -59,6 +59,16 @@ def estimate(d, slugs): # mirrors sizing.js estimate()
return peak, out return peak, out
def segments(d, slugs): # mirrors estimate().segments in sizing.js (v3)
by = {a["slug"]: a for a in d["apps"]}
apps = sorted((by[s] for s in slugs if not by[s].get("builtin")), key=lambda a: -a["peak_mb"])
peak = d["platform_mb"] + sum(a["peak_mb"] for a in apps)
well = min_box_gb(d, peak * d["headroom"])
box, guest = well * 1024, guest_mib(d, well)
return well, [("base", box - guest), ("platform", d["platform_mb"])] + \
[(a["slug"], a["peak_mb"]) for a in apps] + [("spare", guest - peak)]
def first(d, verdicts, want): # mirrors recommend_gb / tight_box_gb in sizing.js def first(d, verdicts, want): # mirrors recommend_gb / tight_box_gb in sizing.js
return next((gb for gb, v in zip(d["boxes_gb"], verdicts) if v == want), None) return next((gb for gb, v in zip(d["boxes_gb"], verdicts) if v == want), None)
@@ -112,6 +122,32 @@ class SizingWeb(unittest.TestCase):
if rec is not None: if rec is not None:
self.assertGreaterEqual(rec, gb - 1 / 1024) self.assertGreaterEqual(rec, gb - 1 / 1024)
def test_segments_add_up_to_the_bar(self):
for name, sel in {"household": HOUSEHOLD, "household without immich": [x for x in HOUSEHOLD if x != "immich"],
"immich alone": ["immich"], "all 62": self.all, "nothing": []}.items():
well, segs = segments(self.d, sel)
self.assertAlmostEqual(sum(mb for _, mb in segs), well * 1024, delta=1, msg=name)
self.assertGreaterEqual(dict(segs)["base"], 0, name + ": base system negative")
self.assertGreaterEqual(dict(segs)["spare"], 0, name + ": spare negative")
# the household preset, by hand: peak 5,515 MiB; the bar holds 5,515 * 1.5 = 8,272.5 in the guest
well, segs = segments(self.d, HOUSEHOLD)
self.assertAlmostEqual(dict(segs)["spare"], 5515 * 0.5, delta=1)
self.assertAlmostEqual(dict(segs)["base"], 4708, delta=1) # 4,096 for Proxmox + 612 MemTotal shortfall
def test_english_article(self):
with io.open(os.path.join(ROOT, "website", "en", "sizing.html"), encoding="utf-8-sig") as f:
t = json.loads(re.search(r'id="sizer-strings">(.*?)</script>', f.read(), re.S).group(1))
def say(tpl, gb): # mirrors fill() in sizing.js
art = t["an"] if str(gb) in t["an_before"] else t["a"]
return tpl.replace("{a}", art).replace("{gb}", str(gb))
got = {gb: say(t["recommend"], gb) for gb in self.d["boxes_gb"]}
self.assertEqual(got[8], "Recommended: an 8 GB machine.")
for gb in self.d["boxes_gb"]:
if gb != 8:
self.assertEqual(got[gb], "Recommended: a %d GB machine." % gb)
for k in ("tight_on", "only_tight", "none_fits"):
self.assertNotRegex(say(t[k], 8), r"\ba 8\b", k)
def test_sizing_md_table_agrees_with_the_rule(self): def test_sizing_md_table_agrees_with_the_rule(self):
with io.open(SIZING_MD, encoding="utf-8") as f: with io.open(SIZING_MD, encoding="utf-8") as f:
s = f.read() s = f.read()
@@ -146,7 +182,8 @@ class SizingWeb(unittest.TestCase):
nums = set(re.findall(r"(?<![\w.])\d+(?:\.\d+)?", code)) nums = set(re.findall(r"(?<![\w.])\d+(?:\.\d+)?", code))
allowed = {"0", "1", "2", "100", "1024"} # 100 = percent, 1024 = MiB per GiB; no rule constant allowed = {"0", "1", "2", "100", "1024"} # 100 = percent, 1024 = MiB per GiB; no rule constant
self.assertTrue(nums <= allowed, "sizing.js holds its own numbers: %s" % sorted(nums - allowed)) self.assertTrue(nums <= allowed, "sizing.js holds its own numbers: %s" % sorted(nums - allowed))
for need in ("data.bar_max_gb", "data.bar_marks_gb", "function minBoxGb", "guestMib(data, mid) >= needMib"): for need in ("data.bar_max_gb", "data.bar_marks_gb", "function minBoxGb", "guestMib(data, mid) >= needMib",
"data.bar_app_colours", "boxMb - guestMb", "guestMb - peak"):
self.assertIn(need, js) self.assertIn(need, js)
def test_data_has_no_peak_below_typical(self): def test_data_has_no_peak_below_typical(self):
+18 -1
View File
@@ -1,4 +1,21 @@
## 2026-10-13 (afternoon) — machine estimator v2: fold-out categories and the memory bar ## 2026-10-13 (evening) — machine estimator v3: the bar shows what the box's memory is used for
v2 said the apps use 5 GB at peak and then recommended 16 GB; the reason was invisible. Still unlisted (R-997).
- **The bar is split** into the parts that make up its length (the „fits well" box memory, unchanged): the machine's
base system (grey, as the dashboard's „Rendszer"), Felhom's own services (accent blue), each selected app at its
peak (largest first, one colour each — the dashboard's „Memória eloszlás" palette without its amber and its
first blue; beyond 8 apps the smallest join „további N alkalmazás"), and spare room (striped). Every part has a
`title` with its name and MB; the thin „tight" mark stays; `role="meter"` keeps its value.
- **A legend** under the bar (not sticky): a dot, the name, the size in GB with one decimal, in that order, plus the
„tight from" mark. Only the bar and the recommendation line stay on screen while scrolling.
- **One explaining sentence** replaces the typical/peak line: the peak, the base system and the 50 % spare room —
three numbers from the rule — and why that makes the recommended size. The typical figure moved into
„Hogyan számol az oldal?".
- **English article:** „an 8 GB machine" (the article comes from the page's own strings).
- Cache versions `?v=3` for `sizing.css`, `sizing.js` and the data file.
## 2026-10-13 (afternoon) — machine estimator v2: fold-out categories and the memory bar
The page was 4,592 px tall on a phone (the reviewer's browser check of v1). Still unlisted (R-997). The page was 4,592 px tall on a phone (the reviewer's browser check of v1). Still unlisted (R-997).
+1
View File
@@ -34,6 +34,7 @@
64 64
], ],
"bar_max_gb": 64, "bar_max_gb": 64,
"bar_app_colours": 8,
"categories": [ "categories": [
{ {
"id": "fajlkezeles", "id": "fajlkezeles",
+22 -2
View File
@@ -8,12 +8,32 @@
.sizer-sticky { position: sticky; top: 0; z-index: 5; background: var(--bg-0); border-bottom: 1px solid var(--line); padding: 0.75rem 0 0.6rem; margin: 0 0 1rem; } .sizer-sticky { position: sticky; top: 0; z-index: 5; background: var(--bg-0); border-bottom: 1px solid var(--line); padding: 0.75rem 0 0.6rem; margin: 0 0 1rem; }
.sizer-meter { padding: 0 0 1.4rem; } .sizer-meter { padding: 0 0 1.4rem; }
.sizer-track { position: relative; height: 12px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); } .sizer-track { position: relative; height: 12px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); }
.sizer-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: var(--blue); border-radius: var(--radius); transition: width 0.25s ease; } .sizer-stack { position: absolute; inset: 0; display: flex; overflow: hidden; border-radius: var(--radius); }
.sizer-seg { flex: 0 0 auto; height: 100%; transition: width 0.25s ease; }
.sizer-tightmark { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; background: var(--warn); transition: left 0.25s ease; } .sizer-tightmark { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; background: var(--warn); transition: left 0.25s ease; }
.sizer-scale { position: relative; height: 1rem; margin-top: 0.3rem; } .sizer-scale { position: relative; height: 1rem; margin-top: 0.3rem; }
.sizer-mark { position: absolute; transform: translateX(-100%); padding-right: 2px; border-right: 1px solid var(--line); font-family: var(--font-data); font-size: 0.72rem; color: var(--text-3); white-space: nowrap; line-height: 1rem; } .sizer-mark { position: absolute; transform: translateX(-100%); padding-right: 2px; border-right: 1px solid var(--line); font-family: var(--font-data); font-size: 0.72rem; color: var(--text-3); white-space: nowrap; line-height: 1rem; }
.sizer-line { margin: 0; color: var(--text-1); font-weight: 600; line-height: 1.45; } .sizer-line { margin: 0; color: var(--text-1); font-weight: 600; line-height: 1.45; }
.sizer-legend { margin: 0.2rem 0 0; color: var(--text-3); font-size: 0.78rem; }
/* v3: what the box's memory is used for — the controller's „Memória eloszlás” bar, same palette (monitoring.html),
minus its amber (amber is the warning colour here) and its first blue (Felhom's own services wear the accent) */
.sizer { --sz-c1: #8E7CE8; --sz-c2: #5EC4B6; --sz-c3: #D67BB5; --sz-c4: #7FA6E8;
--sz-c5: #59B88C; --sz-c6: #E8935E; --sz-c7: #6FCADF; --sz-c8: #B3A1F0; }
.sizer-c1 { background: var(--sz-c1); } .sizer-c2 { background: var(--sz-c2); } .sizer-c3 { background: var(--sz-c3); }
.sizer-c4 { background: var(--sz-c4); } .sizer-c5 { background: var(--sz-c5); } .sizer-c6 { background: var(--sz-c6); }
.sizer-c7 { background: var(--sz-c7); } .sizer-c8 { background: var(--sz-c8); }
.sizer-seg-base { background: var(--text-3); opacity: 0.5; }
.sizer-seg-platform { background: var(--blue); }
.sizer-seg-more { background: var(--text-2); }
.sizer-seg-spare { background: repeating-linear-gradient(135deg, var(--line) 0 4px, var(--bg-2) 4px 8px); }
.sizer-seg-tightmark { background: var(--warn); }
.sizer-why-line { margin: 0 0 0.5rem; color: var(--text-2); line-height: 1.6; }
.sizer-why-line .sizer-num { font-family: var(--font-ui); }
.sizer-legend { list-style: none; margin: 0 0 1rem; padding: 0; display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; font-size: 0.85rem; color: var(--text-2); }
.sizer-legend-item { display: inline-flex; align-items: center; gap: 0.4rem; }
.sizer-dot { width: 10px; height: 10px; border-radius: var(--radius); flex: 0 0 auto; }
.sizer-dot.sizer-seg-spare { border: 1px solid var(--line); }
.sizer-dot.sizer-seg-tightmark { width: 3px; height: 14px; }
.sizer-result { margin: 0 0 1.25rem; } .sizer-result { margin: 0 0 1.25rem; }
.sizer-totals { color: var(--text-2); margin: 0; line-height: 1.7; } .sizer-totals { color: var(--text-2); margin: 0; line-height: 1.7; }
+77 -19
View File
@@ -11,7 +11,10 @@
* tight peak <= guest < peak * headroom * tight peak <= guest < peak * headroom
* no guest < peak * no guest < peak
* the memory bar: minBoxGb() inverts guestMib() by SEARCHING it — the smallest box memory whose guest * the memory bar: minBoxGb() inverts guestMib() by SEARCHING it — the smallest box memory whose guest
* holds peak * headroom (solid fill) and peak (thin marker). It never restates the installer's rule. * holds peak * headroom (the bar's length) and peak (thin marker). It never restates the installer's rule.
* v3: the bar is split into what that box memory is used for, every part from the same rule:
* base system = box - guest(box) · Felhom = platform · each app = its peak · spare = guest(box) - peak
* so the parts add up to the box exactly (scripts/test_build_sizing_web.py proves it).
* *
* The page text is not here: each page carries its own strings (#sizer-strings), so this file is the * The page text is not here: each page carries its own strings (#sizer-strings), so this file is the
* same for both languages. * same for both languages.
@@ -64,10 +67,19 @@
if (tightBox === null && b.verdict === 'tight') { tightBox = b.gb; } if (tightBox === null && b.verdict === 'tight') { tightBox = b.gb; }
}); });
var heavy = busy.filter(function (a) { return a.cpu_class === 'heavy-in-bursts'; }); var heavy = busy.filter(function (a) { return a.cpu_class === 'heavy-in-bursts'; });
var wellGb = minBoxGb(data, peak * data.headroom);
var boxMb = wellGb * 1024, guestMb = guestMib(data, wellGb);
var apps = slugs.map(function (s) { return bySlug[s]; })
.filter(function (a) { return a && !a.builtin; })
.sort(function (x, y) { return y.peak_mb - x.peak_mb; });
var segments = [{ kind: 'base', mb: boxMb - guestMb }, { kind: 'platform', mb: data.platform_mb }];
apps.forEach(function (a) { segments.push({ kind: 'app', app: a, mb: a.peak_mb }); });
segments.push({ kind: 'spare', mb: guestMb - peak });
return { return {
typical_mb: typical, peak_mb: peak, boxes: boxes, busy: busy, heavy: heavy, unmeasured: unmeasured, typical_mb: typical, peak_mb: peak, boxes: boxes, busy: busy, heavy: heavy, unmeasured: unmeasured,
well_gb: minBoxGb(data, peak * data.headroom), tight_gb: minBoxGb(data, peak), well_gb: wellGb, tight_gb: minBoxGb(data, peak),
recommend_gb: recommend, tight_box_gb: tightBox recommend_gb: recommend, tight_box_gb: tightBox,
segments: segments, base_mb: boxMb - guestMb, apps_peak_mb: peak - data.platform_mb
}; };
} }
@@ -106,6 +118,13 @@
var meter = document.getElementById('sizer-meter'); var meter = document.getElementById('sizer-meter');
var line = document.getElementById('sizer-line'); var line = document.getElementById('sizer-line');
var result = document.getElementById('sizer-result'); var result = document.getElementById('sizer-result');
var stack = document.getElementById('sizer-stack');
var legend = document.getElementById('sizer-legend');
var why = document.getElementById('sizer-why-line');
var typicalLine = document.getElementById('sizer-typical');
// English needs "an 8 GB machine"; the article comes from the page's strings (none in Hungarian)
var article = function (gb) { return (T.an_before || []).indexOf(String(gb)) !== -1 ? T.an : T.a; };
var fill = function (tpl, gb) { return tpl.replace('{a}', article(gb) || '').replace('{gb}', gb); };
fetch(host.getAttribute('data-src')).then(function (r) { return r.json(); }).then(function (data) { fetch(host.getAttribute('data-src')).then(function (r) { return r.json(); }).then(function (data) {
var max = data.bar_max_gb; var max = data.bar_max_gb;
@@ -160,11 +179,44 @@
g.count.textContent = n ? T.selected.replace('{n}', n) : ''; g.count.textContent = n ? T.selected.replace('{n}', n) : '';
}); });
// the bar: solid fill = fits well, thin marker = fits, but tight // the bar: its length is the "fits well" box memory, split into what that memory is used for
var wellPct = Math.min(r.well_gb / max, 1) * 100; // (estimate().segments); beyond the colour count the smallest apps join one segment
var tightPct = Math.min(r.tight_gb / max, 1) * 100; var basis = Math.max(r.well_gb, max) * 1024;
document.getElementById('sizer-fill').style.width = wellPct + '%'; var segs = [], rest = { kind: 'more', mb: 0, n: 0 }, nApps = 0;
document.getElementById('sizer-tight').style.left = tightPct + '%'; r.segments.forEach(function (sg) {
if (sg.kind === 'app') {
nApps += 1;
if (nApps > data.bar_app_colours) { rest.mb += sg.mb; rest.n += 1; return; }
sg.colour = 'sizer-c' + nApps;
}
if (sg.kind === 'spare' && rest.n) { segs.push(rest); }
segs.push(sg);
});
var label = function (sg) {
if (sg.kind === 'app') { return lang === 'en' ? sg.app.name_en : sg.app.name_hu; }
if (sg.kind === 'more') { return T.seg_more.replace('{n}', sg.n); }
return T['seg_' + sg.kind];
};
stack.textContent = '';
legend.textContent = '';
segs.forEach(function (sg) {
var cls = sg.kind === 'app' ? sg.colour : 'sizer-seg-' + sg.kind;
var part = el('div', 'sizer-seg ' + cls);
part.style.width = (sg.mb / basis * 100) + '%';
part.title = label(sg) + ': ' + Math.round(sg.mb) + ' MB';
stack.appendChild(part);
var li = el('li', 'sizer-legend-item');
li.appendChild(el('span', 'sizer-dot ' + cls));
li.appendChild(el('span', '', label(sg) + ' '));
li.appendChild(el('span', 'sizer-num', gbText(sg.mb)));
legend.appendChild(li);
});
var tli = el('li', 'sizer-legend-item');
tli.appendChild(el('span', 'sizer-dot sizer-seg-tightmark'));
tli.appendChild(el('span', '', T.seg_tight + ' '));
tli.appendChild(el('span', 'sizer-num', dec(r.tight_gb) + ' GB'));
legend.appendChild(tli);
document.getElementById('sizer-tight').style.left = (Math.min(r.tight_gb / max, 1) * 100) + '%';
var over = r.well_gb > max; var over = r.well_gb > max;
var needText = over ? T.over_max.replace('{gb}', max) : dec(r.well_gb) + ' GB'; var needText = over ? T.over_max.replace('{gb}', max) : dec(r.well_gb) + ' GB';
meter.setAttribute('aria-valuenow', String(Math.min(r.well_gb, max).toFixed(1))); meter.setAttribute('aria-valuenow', String(Math.min(r.well_gb, max).toFixed(1)));
@@ -173,23 +225,29 @@
// one line: which box to buy, and where it is tight // one line: which box to buy, and where it is tight
var biggest = data.boxes_gb[data.boxes_gb.length - 1]; var biggest = data.boxes_gb[data.boxes_gb.length - 1];
if (r.recommend_gb !== null) { if (r.recommend_gb !== null) {
line.textContent = T.recommend.replace('{gb}', r.recommend_gb) + line.textContent = fill(T.recommend, r.recommend_gb) +
(r.tight_box_gb !== null ? ' ' + T.tight_on.replace('{gb}', r.tight_box_gb) : ''); (r.tight_box_gb !== null ? ' ' + fill(T.tight_on, r.tight_box_gb) : '');
} else if (r.tight_box_gb !== null) { } else if (r.tight_box_gb !== null) {
line.textContent = T.only_tight.replace('{gb}', r.tight_box_gb); line.textContent = fill(T.only_tight, r.tight_box_gb);
} else { } else {
line.textContent = T.none_fits.replace('{gb}', biggest); line.textContent = fill(T.none_fits, biggest);
} }
// under the bar: totals, why tight is tight, the CPU lines // under the bar: totals, why tight is tight, the CPU lines
result.textContent = ''; result.textContent = '';
var totals = el('p', 'sizer-totals'); // the explaining sentence: three numbers, all from the rule; bold parts are {b}…{/b}
totals.appendChild(el('span', '', T.typical + ': ')); why.textContent = '';
totals.appendChild(el('strong', 'sizer-num', gbText(r.typical_mb))); var tpl = r.apps_peak_mb > 0
totals.appendChild(el('span', '', ' · ' + T.peak + ': ')); ? (r.recommend_gb !== null ? T.why : T.why_over)
totals.appendChild(el('strong', 'sizer-num', gbText(r.peak_mb))); : T.why_empty;
result.appendChild(totals); var spare = Math.round((data.headroom - 1) * 100);
result.appendChild(el('p', 'sizer-fine', T.platform_note)); tpl.replace('{peak}', dec(r.peak_mb / 1024)).replace('{base}', dec(r.base_mb / 1024))
.replace('{base_platform}', dec((r.base_mb + data.platform_mb) / 1024))
.replace('{spare}', spare).replace('{rec}', r.recommend_gb === null ? max : r.recommend_gb)
.split(/\{b\}|\{\/b\}/).forEach(function (part, i) {
why.appendChild(i % 2 ? el('strong', 'sizer-num', part) : document.createTextNode(part));
});
typicalLine.textContent = T.typical_line.replace('{typical}', gbText(r.typical_mb)).replace('{peak}', gbText(r.peak_mb));
if (r.tight_box_gb !== null) { result.appendChild(el('p', 'sizer-why', T.tight_why)); } if (r.tight_box_gb !== null) { result.appendChild(el('p', 'sizer-why', T.tight_why)); }
if (r.busy.length) { if (r.busy.length) {
+9 -7
View File
@@ -18,8 +18,8 @@
<link rel="stylesheet" href="/assets/shell.css?v=25"> <link rel="stylesheet" href="/assets/shell.css?v=25">
<link rel="icon" type="image/png" href="/assets/logo_favicon_2.svg"> <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> <script defer src="https://stats.felhom.eu/script.js" data-website-id="d419db57-5e1c-48df-bdb3-c29f25fb1661"></script>
<link rel="stylesheet" href="/assets/sizing.css?v=2"> <link rel="stylesheet" href="/assets/sizing.css?v=3">
<script defer src="/assets/sizing.js?v=2"></script> <script defer src="/assets/sizing.js?v=3"></script>
</head> </head>
<body class="shell page-gepigeny"> <body class="shell page-gepigeny">
<header class="mobile-topbar"> <header class="mobile-topbar">
@@ -73,7 +73,7 @@
<p class="page-lede">Tick what you would run on the box, and the page shows how much machine memory it needs. This is an estimate from my own measurements on an N100 mini PC (13 October 2026), not a promise.</p> <p class="page-lede">Tick what you would run on the box, and the page shows how much machine memory it needs. This is an estimate from my own measurements on an N100 mini PC (13 October 2026), not a promise.</p>
<noscript><p class="sizer-noscript">This page needs JavaScript. Turn it on and reload the page.</p></noscript> <noscript><p class="sizer-noscript">This page needs JavaScript. Turn it on and reload the page.</p></noscript>
<section class="shell-section sizer" id="sizer" data-src="/assets/sizing-data.json?v=2" hidden> <section class="shell-section sizer" id="sizer" data-src="/assets/sizing-data.json?v=3" hidden>
<div class="sizer-presets" role="group" aria-label="Quick picks"> <div class="sizer-presets" role="group" aria-label="Quick picks">
<button type="button" class="btn btn-outline btn-sm" data-preset="immich,jellyfin,nextcloud,home-assistant,vaultwarden,paperless-ngx">Family starter set</button> <button type="button" class="btn btn-outline btn-sm" data-preset="immich,jellyfin,nextcloud,home-assistant,vaultwarden,paperless-ngx">Family starter set</button>
<button type="button" class="btn btn-outline btn-sm" data-preset="nextcloud,vaultwarden">Just files and passwords</button> <button type="button" class="btn btn-outline btn-sm" data-preset="nextcloud,vaultwarden">Just files and passwords</button>
@@ -81,12 +81,13 @@
</div> </div>
<div class="sizer-sticky"> <div class="sizer-sticky">
<div class="sizer-meter" id="sizer-meter" role="meter" aria-label="Machine memory needed" aria-valuemin="0" aria-valuemax="64" aria-valuenow="0"> <div class="sizer-meter" id="sizer-meter" role="meter" aria-label="Machine memory needed" aria-valuemin="0" aria-valuemax="64" aria-valuenow="0">
<div class="sizer-track"><div class="sizer-fill" id="sizer-fill"></div><div class="sizer-tightmark" id="sizer-tight"></div></div> <div class="sizer-track"><div class="sizer-stack" id="sizer-stack"></div><div class="sizer-tightmark" id="sizer-tight"></div></div>
<div class="sizer-scale" id="sizer-scale" aria-hidden="true"></div> <div class="sizer-scale" id="sizer-scale" aria-hidden="true"></div>
</div> </div>
<p class="sizer-line" id="sizer-line" aria-live="polite"></p> <p class="sizer-line" id="sizer-line" aria-live="polite"></p>
<p class="sizer-legend">Solid bar: fits well · Thin mark: fits, but tight</p>
</div> </div>
<p class="sizer-why-line" id="sizer-why-line"></p>
<ul class="sizer-legend" id="sizer-legend"></ul>
<div class="sizer-result" id="sizer-result"></div> <div class="sizer-result" id="sizer-result"></div>
<h2 class="sizer-sub">Applications</h2> <h2 class="sizer-sub">Applications</h2>
<div class="sizer-cats" id="sizer-apps"></div> <div class="sizer-cats" id="sizer-apps"></div>
@@ -102,12 +103,13 @@
</ul> </ul>
<details class="sizer-how"> <details class="sizer-how">
<summary>How does the page count?</summary> <summary>How does the page count?</summary>
<p id="sizer-typical"></p>
<p>The box's own services use about 470 MB of memory (Dashboard, tunnel, file manager, Docker). The page adds the measured figures of the apps you tick: once their typical use, once their peak, as if every app were busy at the same moment. That is a deliberately cautious count.</p> <p>The box's own services use about 470 MB of memory (Dashboard, tunnel, file manager, Docker). The page adds the measured figures of the apps you tick: once their typical use, once their peak, as if every app were busy at the same moment. That is a deliberately cautious count.</p>
<p>On a machine, the Felhom system gets the memory the installer gives it; the rest is for the base system. Fits well: one and a half times the peak still fits — the solid bar shows this, and it is the recommended size. Fits, but tight: the peak fits, one and a half times it does not — the thin mark shows this. The recommendation is the smallest of 8, 12, 16, 24, 32, 48 and 64 GB where it fits well.</p> <p>On a machine, the Felhom system gets the memory the installer gives it; the rest is for the base system. Fits well: one and a half times the peak still fits — the bar's length shows this, and the recommended size comes from it. Fits, but tight: the peak fits, one and a half times it does not — the thin mark shows this. The recommendation is the smallest of 8, 12, 16, 24, 32, 48 and 64 GB where it fits well.</p>
<p>The figures were measured on an N100 mini PC (13 October 2026): every app installed and at rest, the heavier ones also during one real task (a photo import, a film conversion, text recognition). A larger photo library or more users can need more.</p> <p>The figures were measured on an N100 mini PC (13 October 2026): every app installed and at rest, the heavier ones also during one real task (a photo import, a film conversion, text recognition). A larger photo library or more users can need more.</p>
</details> </details>
</section> </section>
<script type="application/json" id="sizer-strings">{"typical": "Typical use", "peak": "At peak, if everything works at once", "platform_note": "Both include the box's own services (about 0.5 GB).", "selected": "{n} selected", "recommend": "Recommended: a {gb} GB machine.", "tight_on": "It fits on {gb} GB, but tight.", "only_tight": "None of the sizes fits well; from {gb} GB it fits, but tight.", "none_fits": "It does not fit any size, not even {gb} GB.", "over_max": "more than {gb} GB", "meter_text": "Machine memory needed: {x}", "tight_why": "Tight: it works, but a large photo import or a backup at the same time can slow it down.", "cpu_head": "These make the processor work hard for a short time", "heavy_pair": "{names}: they slow each other down only while both are working.", "and": " and ", "unmeasured": "Not yet measured", "builtin": "built in, part of every box", "load_error": "The estimate's data did not load. Reload the page."}</script> <script type="application/json" id="sizer-strings">{"selected": "{n} selected", "recommend": "Recommended: {a} {gb} GB machine.", "tight_on": "It fits on {gb} GB, but tight.", "only_tight": "None of the sizes fits well; from {gb} GB it fits, but tight.", "none_fits": "It does not fit any size, not even {gb} GB.", "over_max": "more than {gb} GB", "meter_text": "Machine memory needed: {x}", "tight_why": "Tight: it works, but a large photo import or a backup at the same time can slow it down.", "cpu_head": "These make the processor work hard for a short time", "heavy_pair": "{names}: they slow each other down only while both are working.", "and": " and ", "unmeasured": "Not yet measured", "builtin": "built in, part of every box", "load_error": "The estimate's data did not load. Reload the page.", "seg_base": "The machine's base system", "seg_platform": "Felhom's own services", "seg_spare": "Spare room", "seg_more": "{n} more apps", "seg_tight": "Fits, but tight, from", "why": "At peak your apps and Felhom's own services use {b}{peak} GB{/b}. The machine needs more than that: about {b}{base} GB{/b} stays with its base system, and we leave {spare} % spare room on top. That is why we recommend {b}{rec} GB{/b}.", "why_over": "At peak your apps and Felhom's own services use {b}{peak} GB{/b}. The machine needs more than that: about {b}{base} GB{/b} stays with its base system, and we leave {spare} % spare room on top. That is why it needs more than {b}{rec} GB{/b}.", "why_empty": "The machine's base system and Felhom's own services use about {b}{base_platform} GB{/b}.", "typical_line": "With Felhom's own services, the selected apps use {typical} on average and {peak} at peak.", "a": "a", "an": "an", "an_before": ["8", "11", "18", "80"]}</script>
<footer> <footer>
<div class="container"> <div class="container">
+9 -7
View File
@@ -18,8 +18,8 @@
<link rel="stylesheet" href="/assets/shell.css?v=25"> <link rel="stylesheet" href="/assets/shell.css?v=25">
<link rel="icon" type="image/png" href="/assets/logo_favicon_2.svg"> <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> <script defer src="https://stats.felhom.eu/script.js" data-website-id="d419db57-5e1c-48df-bdb3-c29f25fb1661"></script>
<link rel="stylesheet" href="/assets/sizing.css?v=2"> <link rel="stylesheet" href="/assets/sizing.css?v=3">
<script defer src="/assets/sizing.js?v=2"></script> <script defer src="/assets/sizing.js?v=3"></script>
</head> </head>
<body class="shell page-gepigeny"> <body class="shell page-gepigeny">
<header class="mobile-topbar"> <header class="mobile-topbar">
@@ -73,7 +73,7 @@
<p class="page-lede">Jelöld be, mit futtatnál a dobozon, és az oldal megmutatja, mekkora memóriájú gép kell hozzá. Ez becslés a saját mérésemből egy N100-as mini PC-n (2026. október 13.), nem ígéret.</p> <p class="page-lede">Jelöld be, mit futtatnál a dobozon, és az oldal megmutatja, mekkora memóriájú gép kell hozzá. Ez becslés a saját mérésemből egy N100-as mini PC-n (2026. október 13.), nem ígéret.</p>
<noscript><p class="sizer-noscript">Ehhez az oldalhoz JavaScript kell. Kapcsold be, és töltsd újra az oldalt.</p></noscript> <noscript><p class="sizer-noscript">Ehhez az oldalhoz JavaScript kell. Kapcsold be, és töltsd újra az oldalt.</p></noscript>
<section class="shell-section sizer" id="sizer" data-src="/assets/sizing-data.json?v=2" hidden> <section class="shell-section sizer" id="sizer" data-src="/assets/sizing-data.json?v=3" hidden>
<div class="sizer-presets" role="group" aria-label="Gyors választás"> <div class="sizer-presets" role="group" aria-label="Gyors választás">
<button type="button" class="btn btn-outline btn-sm" data-preset="immich,jellyfin,nextcloud,home-assistant,vaultwarden,paperless-ngx">Családi alapcsomag</button> <button type="button" class="btn btn-outline btn-sm" data-preset="immich,jellyfin,nextcloud,home-assistant,vaultwarden,paperless-ngx">Családi alapcsomag</button>
<button type="button" class="btn btn-outline btn-sm" data-preset="nextcloud,vaultwarden">Csak fájlok és jelszavak</button> <button type="button" class="btn btn-outline btn-sm" data-preset="nextcloud,vaultwarden">Csak fájlok és jelszavak</button>
@@ -81,12 +81,13 @@
</div> </div>
<div class="sizer-sticky"> <div class="sizer-sticky">
<div class="sizer-meter" id="sizer-meter" role="meter" aria-label="Szükséges gépmemória" aria-valuemin="0" aria-valuemax="64" aria-valuenow="0"> <div class="sizer-meter" id="sizer-meter" role="meter" aria-label="Szükséges gépmemória" aria-valuemin="0" aria-valuemax="64" aria-valuenow="0">
<div class="sizer-track"><div class="sizer-fill" id="sizer-fill"></div><div class="sizer-tightmark" id="sizer-tight"></div></div> <div class="sizer-track"><div class="sizer-stack" id="sizer-stack"></div><div class="sizer-tightmark" id="sizer-tight"></div></div>
<div class="sizer-scale" id="sizer-scale" aria-hidden="true"></div> <div class="sizer-scale" id="sizer-scale" aria-hidden="true"></div>
</div> </div>
<p class="sizer-line" id="sizer-line" aria-live="polite"></p> <p class="sizer-line" id="sizer-line" aria-live="polite"></p>
<p class="sizer-legend">Teli sáv: bőven elfér · Vékony jel: elfér, de szűkösen</p>
</div> </div>
<p class="sizer-why-line" id="sizer-why-line"></p>
<ul class="sizer-legend" id="sizer-legend"></ul>
<div class="sizer-result" id="sizer-result"></div> <div class="sizer-result" id="sizer-result"></div>
<h2 class="sizer-sub">Alkalmazások</h2> <h2 class="sizer-sub">Alkalmazások</h2>
<div class="sizer-cats" id="sizer-apps"></div> <div class="sizer-cats" id="sizer-apps"></div>
@@ -102,12 +103,13 @@
</ul> </ul>
<details class="sizer-how"> <details class="sizer-how">
<summary>Hogyan számol az oldal?</summary> <summary>Hogyan számol az oldal?</summary>
<p id="sizer-typical"></p>
<p>A doboz saját szolgáltatásai kb. 470 MB memóriát használnak (vezérlőpult, alagút, fájlkezelő, Docker). Ehhez adja hozzá a bejelölt alkalmazások mért értékeit: egyszer az átlagos használatot, egyszer a csúcsot, mintha minden alkalmazás egyszerre dolgozna. Ez szándékosan óvatos számítás.</p> <p>A doboz saját szolgáltatásai kb. 470 MB memóriát használnak (vezérlőpult, alagút, fájlkezelő, Docker). Ehhez adja hozzá a bejelölt alkalmazások mért értékeit: egyszer az átlagos használatot, egyszer a csúcsot, mintha minden alkalmazás egyszerre dolgozna. Ez szándékosan óvatos számítás.</p>
<p>Egy gépen a Felhom-rendszer annyi memóriát kap, amennyit a telepítő ad neki; a többi az alaprendszeré. „Bőven elfér”: a csúcs másfélszerese is belefér — ezt mutatja a teli sáv, és ez az ajánlott gépméret. „Elfér, de szűkösen”: a csúcs belefér, a másfélszerese nem — ezt mutatja a vékony jel. Az ajánlás a 8, 12, 16, 24, 32, 48 és 64 GB-os méretek közül a legkisebb, amelybe bőven elfér.</p> <p>Egy gépen a Felhom-rendszer annyi memóriát kap, amennyit a telepítő ad neki; a többi az alaprendszeré. „Bőven elfér”: a csúcs másfélszerese is belefér — ezt mutatja a sáv hossza, és ebből jön az ajánlott gépméret. „Elfér, de szűkösen”: a csúcs belefér, a másfélszerese nem — ezt mutatja a vékony jel. Az ajánlás a 8, 12, 16, 24, 32, 48 és 64 GB-os méretek közül a legkisebb, amelybe bőven elfér.</p>
<p>A számok egy N100-as mini PC-n mért értékek (2026. október 13.): minden alkalmazás telepítve, nyugalomban, a nehezebbek egy valódi feladat közben is (fotóimport, filmátalakítás, szövegfelismerés). Egy nagyobb fotótár vagy több felhasználó többet kérhet.</p> <p>A számok egy N100-as mini PC-n mért értékek (2026. október 13.): minden alkalmazás telepítve, nyugalomban, a nehezebbek egy valódi feladat közben is (fotóimport, filmátalakítás, szövegfelismerés). Egy nagyobb fotótár vagy több felhasználó többet kérhet.</p>
</details> </details>
</section> </section>
<script type="application/json" id="sizer-strings">{"typical": "Átlagos használat", "peak": "Csúcson, ha minden egyszerre dolgozik", "platform_note": "Mindkettőben benne vannak a doboz saját szolgáltatásai (kb. 0,5 GB).", "selected": "{n} kiválasztva", "recommend": "Ajánlott: {gb} GB-os gép.", "tight_on": "{gb} GB-on szűkösen elfér.", "only_tight": "Bőven egyik méretbe sem fér; {gb} GB-tól szűkösen elfér.", "none_fits": "Egyik méretbe sem fér bele, {gb} GB-ba sem.", "over_max": "több mint {gb} GB", "meter_text": "Szükséges gépmemória: {x}", "tight_why": "Szűkösen: működik, de egy nagy fotóimport vagy egy közben futó mentés lelassíthatja.", "cpu_head": "Rövid ideig erősen dolgoztatják a processzort", "heavy_pair": "{names}: csak akkor lassítják egymást, amikor éppen egyszerre dolgoznak.", "and": " és ", "unmeasured": "Még nem mértük", "builtin": "beépítve, minden dobozban benne van", "load_error": "A becslés adatai nem töltődtek be. Töltsd újra az oldalt."}</script> <script type="application/json" id="sizer-strings">{"selected": "{n} kiválasztva", "recommend": "Ajánlott: {gb} GB-os gép.", "tight_on": "{gb} GB-on szűkösen elfér.", "only_tight": "Bőven egyik méretbe sem fér; {gb} GB-tól szűkösen elfér.", "none_fits": "Egyik méretbe sem fér bele, {gb} GB-ba sem.", "over_max": "több mint {gb} GB", "meter_text": "Szükséges gépmemória: {x}", "tight_why": "Szűkösen: működik, de egy nagy fotóimport vagy egy közben futó mentés lelassíthatja.", "cpu_head": "Rövid ideig erősen dolgoztatják a processzort", "heavy_pair": "{names}: csak akkor lassítják egymást, amikor éppen egyszerre dolgoznak.", "and": " és ", "unmeasured": "Még nem mértük", "builtin": "beépítve, minden dobozban benne van", "load_error": "A becslés adatai nem töltődtek be. Töltsd újra az oldalt.", "seg_base": "A gép alaprendszere", "seg_platform": "Felhom saját szolgáltatásai", "seg_spare": "Tartalék", "seg_more": "további {n} alkalmazás", "seg_tight": "Szűkösen elfér innen:", "why": "Az alkalmazásaid a Felhom saját szolgáltatásaival együtt csúcson {b}{peak} GB{/b}-ot használnak. A gép ennél többet igényel: kb. {b}{base} GB{/b} a gép alaprendszeréé, és {spare} % tartalékot hagyunk mellé. Ezért ajánlott a {b}{rec} GB{/b}.", "why_over": "Az alkalmazásaid a Felhom saját szolgáltatásaival együtt csúcson {b}{peak} GB{/b}-ot használnak. A gép ennél többet igényel: kb. {b}{base} GB{/b} a gép alaprendszeréé, és {spare} % tartalékot hagyunk mellé. Ezért {b}{rec} GB{/b}-nál is több kell.", "why_empty": "A gép alaprendszere és a Felhom saját szolgáltatásai kb. {b}{base_platform} GB{/b}-ot használnak.", "typical_line": "A kiválasztott alkalmazások a Felhom saját szolgáltatásaival együtt átlagosan {typical}-ot, csúcson {peak}-ot használnak.", "a": "", "an": "", "an_before": []}</script>
<footer> <footer>
<div class="container"> <div class="container">