The catalogue side is app-catalog-felhom.eu b7f0f7c. Here: the evidence, the website, the register and the operator's view. Website - Two cards in the Otthon & Eletmod / Home & Lifestyle section of BOTH apps pages, with assets: grocy-logo.svg is grocy's own icon with its single fill made white like the other logos, lubelogger-logo.png is the app's own icon with its dark background dropped and the mark made white (the rule SparkyFitness's PNG follows), and six screenshots of each app's own UI with a household's own data, taken headless on the bench from the published template. - The app count moved 56 -> 58 in 15 places per language set, both languages, and the open-source tile 49 -> 51. Checked by asking the same patterns for the new number afterwards. marketing/facebook/COPY.md still says 56 and is NOT changed: the post it carries is already scheduled. Evidence - documentation/audits/new-apps-2026-10-10/ — FIT.md (checklist group 0 for all three, with the Hungarian-UI column), the bench and box transcripts, the memory samples, the screenshots and the gate runs. Register: 137 -> 139 rows, 2 opened, 0 closed. - R-926 after a remove-keeping-backups and restore, nobody has checked what the app page shows for an after_install app's generated password. Measured here: LubeLogger is safe (its login is derived from the environment at every start, the new password signs in, the data is back); grocy's install password still signs in from the restored database but the deployed environment no longer carries ADMIN_PASSWORD at all. Seven apps are in the class. - R-927 Monica, stopped at checklist 0.2 with the measurements, waiting on the operator. Gate scripts: the same console trap in nineteen of them and in repo_gates.py itself, where it ABORTED THE WHOLE RUNNER at the first gate — printing a non-ASCII character on this workstation's cp1250 console raised UnicodeEncodeError before the gate had decided anything, and reuse_refs_check.py died while printing a NOTE. All now reconfigure their own streams. Red-proof that the remaining script-test failures are not mine: test_due_checks_gate.py fails the same 5 of 42 with the change reverted.
17 KiB
FIT — checklist group 0 for Grocy, LubeLogger and Monica (2026-10-10)
Read and measured 2026-10-10 09:40–11:10 UTC. Sources: the GitHub API, raw upstream source at the pinned tag, the Docker Hub / ghcr registry APIs, and the images themselves started on bench LXC 9401 (demo-hp, Tier 0). [U] upstream code/docs/registry · [M] measured on the bench, this session · [I] our inference. Group 0 plans the later tests; it closes only what it measured.
Raw reads: A/. Bench transcripts: bench/.
Where the test machines actually are — a correction to the brief's reading: both 9202
(demo-hp-scratch) and the bench 9401 (upgrade-harness) live on demo-hp, not on felhom-pve.
felhom-pve carries only 9201 (demo-felhom). Measured with pct list on both hosts.
The table
| app (project → image, and why that image) | licence (file read) | last release · 2026 | tags · arch | phone-home (switch) | internet at runtime | non-HTTP ports | clients / login route | DB + services | memory (rough) | Hungarian UI | first-admin class | verdict |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
Grocy (grocy/grocy → lscr.io/linuxserver/grocy). There is no image published by grocy itself — grocy/grocy on Docker Hub is 404 [M], and grocy's own README answers "How to run using Docker" with a single link to hub.docker.com/r/linuxserver/grocy [U]. So linuxserver is the upstream-recommended image, and the catalog already pins four others from the same publisher. |
MIT — LICENSE.md, API spdx_id: MIT [U] |
v4.7.1, 2026-09-04 · 3 in 2026 (v4.6.0 03-06, v4.7.0 08-28, v4.7.1 09-04); 83 releases since 2017 | 4.7.1, version-v4.7.1, v4.7.1-ls343, latest · amd64 + arm64 [M]. linuxserver rebuilds weekly, so 4.7.1 is re-pushed (ls342 2026-09-27 → ls343 2026-10-04) |
none. The only outbound hosts in the whole codebase are world.openfoodfacts.org (the barcode-lookup plugin, on the household's explicit action) and releases.grocy.info (demo-data images, only when MODE ≠ production) [M, repo-wide grep]. No update check, no analytics. Switch: GROCY_STOCK_BARCODE_LOOKUP_PLUGIN="" |
no | none (80 in the image; 443 unused) | PWA only — no native app. Third-party phone apps use the REST API with the header GROCY-API-KEY (app.php:102) [M]; the iCal feed uses ?secret= |
SQLite, one container (/config/data/grocy.db); nginx + php-fpm 8.5 |
~30 MiB idle [M] | yes — ~92 %. localization/hu/strings.po holds 734 strings, 62 untranslated (91.6 %); the login page rendered <title>Bejelentkezés | Grocy</title> with GROCY_DEFAULT_LOCALE=hu [M] |
class 3 — admin/admin exists on a fresh start and signs in [M] |
build |
LubeLogger (hargata/lubelog → ghcr.io/hargata/lubelogger; upstream's own docker-compose.yml names exactly this image. hargata/lubelogger on Docker Hub carries the same tags — ghcr chosen because it is the one upstream writes down) |
MIT — LICENSE, API spdx_id: MIT [U] |
v1.7.3, 2026-09-12 · 17 in 2026 (v1.6.1 02-26 … v1.7.3 09-12) | v1.7.3, v1.7.2 … latest, edge · amd64 + arm64 [M] |
none at start. Three outbound fetches, each on a page the household opens: the sponsors list (hargata.github.io), the release check (api.github.com, behind a checkForUpdate request flag), and the language-pack download (hargata.github.io) [M]. Startup makes no request — the first log lines are the banner and Now listening [M] |
no | none (8080). A SignalR websocket at /api/ws — websockets pass the tunnel (MeTube measured 101) |
PWA; no official native app. API takes HTTP Basic or x-api-key / ?apiKey= on /api, /kiosk, /images, /documents, /temp (GetPathAllowAPIKeyAuth) — Basic measured 200 right / 401 wrong [M] |
LiteDB, one container (/App/data/cartracker.db). PostgreSQL is optional (POSTGRES_CONNECTION); upstream's default compose runs LiteDB, so per 1.2 that is what we run |
~50–61 MiB idle [M] | yes, after one click — ~72 % coverage. Nothing ships in the image: /App/data/translations/ is empty on a fresh start [M]. hu_HU exists in the project's directory and the household downloads it in Settings; the file holds 403 keys of which 400 are translated (99.3 %), but German's file holds 559 — so hu_HU covers ~72 % of the largest key set and the rest falls back to English [M] |
class 1 once we set it — see below | build |
Monica (monicahq/monica → monica, a Docker Official Image) |
AGPL-3.0 — LICENSE.md, API spdx_id: AGPL-3.0 [U] |
none since 2025-04-21; the last stable release is v4.1.2, 2024-05-04 — 29 months [U] | 4.1.2, 5.0.0-beta.5, each × -apache/-fpm/-fpm-alpine · 7 arches; the image is re-pushed constantly (2026-10-08) because Docker Official Images rebuild on base-image changes |
not reached | not reached | not reached | not reached | MySQL/MariaDB or PostgreSQL + Redis [U] | not measured | not measured | not measured | stop — 0.2. The operator decides. |
Monica — the stop, in full, because it is the one decision this run hands back
Checklist 0.2 asks for a release in the last 6 months. Monica has had none in 17 months, and no stable release in 29 months. Four independent reads, all 2026-10-10:
- Releases. The newest release of any kind is
v5.0.0-beta.5, 2025-04-21, and it is a prerelease. The newest stable isv4.1.2, 2024-05-04. The tag list agrees: afterv4.1.2there is nothing but betas. - The stable branch is dead.
4.x— the branch the README sends you to for "the stable and current version" — has its newest commit on 2024-05-04. mainis the beta, and says so: "This branch is in development. It's our beta version."- The commit history has a 13-month hole: 2025-08-30, then nothing until 2026-09-24, when two commits
landed. One of them is the telling one — it adds a login-page notice reading "As we are about to deploy a
brand new version of Monica, this instance and all its data will be deleted at the end of December 2026"
(PR #7978, merged 2026-09-24). The notice is
APP_SHOW_DELETION_NOTICE, off by default, so it would never show on a household's box; what it tells us is about the project, not the switch: the author is preparing a rewrite and is winding the hosted service down.
So the brief's third anticipated risk is not only real, it is worse than "releases slowly": nothing has been released for a year and a half, the stable branch has not moved in two and a half years, and upstream has announced a replacement with no date. 790 issues are open.
What the operator decides. Keeping Monica out costs the catalog nothing it has today — no app covers a
family address book, and none did yesterday either. Letting it in means publishing an app whose next version is
a rewrite with no migration story, on a household's box, with our name on the restore promise. The honest
recommendation is keep it out and look again when the rewrite ships; lifecycle: exists precisely so an app
can be added later without disturbing anything. Radicale already carries contacts over CardDAV for households
that only want the address book itself.
The three risks the brief named, measured
1. Grocy starts with admin / admin — true, and the replacement route is proven
Upstream says it in its own README: "Default login is user admin with password admin, please change the
password immediately". Measured on the bench, fresh volume:
- the
userstable holds exactly one row,id=1 admin, password hashed$argon2id$v=19$m=65536,t=4,p=1; POST /loginwithusername=admin&password_base64=YWRtaW4=→ 302 to/, and the cookie returns a 200 stock overview. So the default really isadmin/admin;- a stranger with no cookie gets 302 →
/loginon every page and 401 on/api/system/info. There is no open first-run screen — grocy is first-admin class 3, the bookstack / mealie / calibre-web shape, not asetup_gateapp.
How the install replaces it (checklist 3.2). Grocy ships no CLI, no admin env var and no headless setup. It
does ship the hashing primitive it uses itself: UsersService calls password_hash($password, PASSWORD_ARGON2ID),
and the image's PHP 8.5.6 reports password_algos() = [2y, argon2i, argon2id] with pdo_sqlite present [M]. So
the after_install command is a php -r that takes the password as its own argument ($argv[1] — measured:
with php -r 'code' a b, $argv[1] is a), hashes it with grocy's own algorithm, writes it with a prepared
statement, and then verifies with password_verify before printing its success marker. Proven on the bench:
| after the command ran | result |
|---|---|
admin / admin |
302 → /login?invalid=true (refused) |
admin / generated |
302 → / (signed in) |
admin / a wrong password |
302 → /login?invalid=true (refused) |
Lock-out (3.6), measured: 25 wrong passwords in a row for admin, then the right one — in at once. Grocy
has no lock and no throttle. So the name need not be generated (the calibre-web reason, R-752, does not apply
here) and no family member can be locked out by a stranger.
Barcode scanning. Upstream: camera scanning is ZXing, "totally offline / client-side camera stream
processing", and "due to browser security restrictions, this only works when serving Grocy via a secure
connection (https://)". Every Felhom app is served https:// through the tunnel, so it works — and the
scanning itself sends nothing anywhere. The lookup of an unknown barcode is the separate, explicit "External
barcode lookup" workflow, and that one does reach world.openfoodfacts.org with the barcode number.
Overlap with Mealie / Tandoor. Grocy has recipes and a meal plan too, so the overlap is real but narrow:
Mealie and Tandoor are recipe libraries (import a recipe, scale it, cook from it), grocy is a stock book (what
is in the house, what runs out, what to buy, whose turn the chores are). Grocy's recipes exist to subtract
ingredients from stock. One line goes in use_cases.
2. LubeLogger may have no login at all — true, and worse than "may"
Middleware/Authen.cs reads bool.Parse(configuration["EnableAuth"] ?? "false"), and appsettings.json ships
"EnableAuth": false. When it is false the middleware mints a ticket for every request with
ClaimTypes.Name = "admin" and the role IsRootUser. Measured on a default start, as an anonymous stranger:
| request | result |
|---|---|
GET / |
200, <title>Garage - LubeLogger</title> |
GET /api/vehicles |
200 |
GET /Home/Settings |
200 |
POST /Vehicle/SaveVehicle |
200 {"success":true} — and the vehicle came back from /api/vehicles |
So on a default install anyone who finds the subdomain reads and writes the family's car records as root.
The route that turns it on, and it is better than an after_install. The root login is not a database row:
AuthenticateRootUser compares the submitted name and password, each SHA-256 hex (StaticHelper.GetHash),
against the config keys UserNameHash and UserPasswordHash. ASP.NET's configuration includes environment
variables, so all three keys can be set at deploy time — which means auth is on before the app's first byte,
with no install window to measure. Measured from the container's creation, polled every second:
t+1s /api/vehicles=000 (not listening yet) /=000
t+2s /api/vehicles=401 /=302 → /Login/Index
t+3s /api/vehicles=401 /=302 → /Login/Index
and then, with auth on:
| request | result |
|---|---|
stranger GET /, GET /Home/Settings, POST /Vehicle/SaveVehicle |
302 → /Login/Index |
stranger GET /api/vehicles |
401 |
| household's generated login, web form | {"success":true}, and /api/vehicles 200 with the cookie |
| household's login, wrong password | {"success":false,"message":"Invalid Login Credentials"} |
| API Basic auth, right / wrong | 200 / 401 |
So the verdict is build, and LubeLogger becomes first-admin class 1 — generated by us at install.
Two more things that had to be checked before trusting the env route, both measured:
- No config file undoes it.
Program.csaddsdata/config/userConfig.jsonto the configuration after the environment, so a file holdingEnableAuth: falsewould win. On a fresh start that file is not written (/App/data/config/was empty). And when it is later written,SaveUserConfigdeliberately re-reads those three keys from the merged configuration rather than from the posted form — so the household cannot turn auth off from the UI while the env sets it. - Sign-up is closed by the app itself.
RegisterNewUserrefuses without a token minted by the root user:POST /Login/Registeranswered{"success":false,"message":"Invalid Token"}. Nothing forsignup_blockto do.
Lock-out (3.6), measured: 25 wrong passwords, then the right one — in at once. No lock, no throttle. Same reading as grocy, and the same conclusion: a stranger cannot lock the family out.
Database (1.2). LiteDB, which is upstream's default compose. One container, no engine, so no MariaDB /
PostgreSQL major ever to cross — the whole engine-major rule is n/a for this app by construction.
File uploads (2.2). InitMessage creates data/images, data/documents, data/temp, data/themes,
data/translations beside data/cartracker.db. Receipts and documents are household content → the
${USERDATA_PATH} side; the database and the DataProtection keys are appdata.
One thing the image lacks, and it changes the healthcheck. ghcr.io/hargata/lubelogger:v1.7.3 has no
curl, no wget, no nc, no python, no node [M] — none of the four healthcheck families in REUSE.md §2 exists
in it. It does have bash, and /dev/tcp works there (measured: Connection refused against a dead port,
which is the mechanism answering). So LubeLogger needs a fifth family, a bash /dev/tcp probe, and that is
a catalog-wide convention — it goes into REUSE.md in the same commit.
3. Monica's release state — see above. The brief said "releases slowly"; the measurement says "stopped".
Claims in this brief that turned out wrong, or need sharpening
- "Grocy starts with the login
admin/admin(as far as we know)" — right, and now measured rather than assumed, on a fresh volume and through the login route. - "LubeLogger may have no login at all until someone turns it on in its settings" — right about the default, wrong about "settings": the switch is a configuration key readable from the environment, which is why the fix needs no post-install step and leaves no window. The brief's worry that "a stranger on the internet sees the family's car data" understates it: a stranger also writes it, as root.
- "Monica releases slowly" — too kind. No release in 17 months, no stable release in 29, the stable branch untouched since 2024-05-04, and an upstream notice that the hosted instance and its data go away at the end of December 2026 ahead of a rewrite.
- "the test machines" — 9202 and the bench 9401 are both on demo-hp. felhom-pve has only 9201.
- Grocy's image. The brief asks "upstream recommends which?" — the answer is unambiguous and worth writing down: upstream publishes no image of its own and points at linuxserver.io.
- A near miss worth recording. My first pass at grocy's Hungarian coverage reported 0 % translated — a
broken
.poparser that mishandled multi-linemsgstr. The German control (which must be ~100 %) read 0 % too, which is what caught it; a second, independent counting method gave German 0 untranslated, French 12, Hungarian 62 of 734 → 91.6 %, with real Hungarian strings in the samples. Without the control this table would have carried a false "no" in its most load-bearing new column.
What group 0 did NOT close
- Memory from birth with swap off (5.1), the 10-minute soak (5.2), backup/restore through the product (2.5), "remove with data" (2.6), the upload read back through the front door (2.8), the cold-start health timing (4.3), the negative health control (4.4), the ladder step (6.2) and the forced-fail undo (6.3) are all Part-B work on the bench and on 9202. Nothing above closes them.
- The forged-
X-Forwarded-Fortest (3.10) through the simulated tunnel is Part B. What is read so far: grocy reads no client address anywhere (repo-wide grep forREMOTE_ADDR/HTTP_X_FORWARDED_FOR/getClientIp— nothing), and LubeLogger reads it in one place only,LoginController~L521: it takes the real peer fromConnection.RemoteIpAddressand then appends the wholeX-Forwarded-Forstring to aLogWarningfor a failed login. Measured with a forged header, the log line readFailed Login for … from ::ffff:172.17.0.1, 1.2.3.4, 10.9.9.9— the real peer first, the forged part visible and deciding nothing. No trusted-proxy list, noForwardedHeadersmiddleware, no lock and no rights keyed on the address. So neither app needs the<router>-xffreset; both get a line in their record saying why.