Files
app-catalog-felhom.eu/templates/lubelogger/docker-compose.yml
T
admin b7f0f7cef5
gates / gates (push) Failing after 14m4s
Grocy and LubeLogger: two new apps, each with its complete record
60 template directories, 58 offered. Records: onboarding/grocy.md and
onboarding/lubelogger.md, all 61 checks answered, none open. Evidence:
felhom.eu/documentation/audits/new-apps-2026-10-10/.

Grocy 4.7.1 from lscr.io/linuxserver/grocy (grocy publishes no image of its
own). SQLite in one volume, no HDD, ~30 MiB idle, amd64 + arm64, Hungarian UI
91.6 %. First admin class 3: it starts with its documented admin/admin and
after_install replaces that password with a generated one.

LubeLogger v1.7.3 from ghcr.io/hargata/lubelogger. LiteDB in one volume, no
HDD, ~60 MiB idle, amd64 + arm64. First admin class 1 — and it has to be: the
image ships EnableAuth=false, and with that the middleware mints a ticket with
the IsRootUser role for every visitor. Measured on a default start: a stranger
got 200 on /, on /api/vehicles and on /Home/Settings and CREATED A VEHICLE.
The compose entrypoint exports EnableAuth=true and the SHA-256 of a generated
name and password, so the app's own login is on before its first byte (at t+1 s
nothing listening, at t+2 s /api/vehicles already 401).

Two defects found by the walk and fixed before publishing:

1. An after_install command may not contain `$`. The controller runs every
   element through os.Expand and refuses one naming anything outside env:, so
   PHP cannot be inlined. On 9202 the first attempt came back
   `[pw argv dsn db i t e s n q h] not declared in env or has no value — not
   run` and the app sat behind its install hold with admin/admin in place. The
   code now lives in a file the compose entrypoint writes. Written into
   REUSE.md's after_install row as a trap.

2. Grocy's persisted config.php does not follow the image. The image copies
   config-dist.php only when that file is absent, so a volume written by 4.6.0
   and started under 4.7.1 answered HTTP 500 on every page — AUTH_CLASS names a
   class 4.7 moved — while its log said migrations done. The entrypoint now
   deletes the file at every start. The 4.6.0 -> 4.7.1 edge failed before this
   and is proven after it, on both venues.

Ladders, written by upgrade-test.py --write-ladder from both verdicts:
  grocy       4.6.0 -> 4.7.1   bench proven, box proven (guarded Update, 41 s)
  lubelogger  v1.7.2 -> v1.7.3 bench proven, box proven (25.6 s)
Checklist 6.3 came from a real failure, not a forced one: before the config.php
fix the product undid the same step in 346 s with the data intact.

REUSE.md: a fifth healthcheck family (bash /dev/tcp) for an image with no HTTP
client at all, measured in both directions.

Tool fixes made on the way: check-onboarding.py crashed on a Windows console
while printing which ids were open; upgrade-test.py's read_text/write_text used
the platform encoding and wrote a cp1250 em dash into a template full of
Hungarian.
2026-10-10 12:31:34 +02:00

113 lines
6.1 KiB
YAML

# LubeLogger - A családi autó: szervizek, javítások, tankolás, költségek
# Domain: ${SUBDOMAIN}.${DOMAIN}
# Database: None (LiteDB inside the app's own data volume — /App/data/cartracker.db)
# RAM: ~60M (mem_limit: 512M) | Pi-compatible: Yes (amd64, arm64)
#
# Environment variables:
# DOMAIN - Your domain (e.g., demo-felhom.eu)
# ROOT_USER - A bejelentkezési név (az űrlapon megadható, alapból "csalad")
# ROOT_PASSWORD - A jelszó (telepítéskor generálva, az alkalmazás oldalán látható)
#
# Storage layout:
# Everything → lubelogger_data:/App/data (named volume, NVMe)
# cartracker.db the vehicles, services, fuel-ups, costs
# documents/ receipts and papers the household attaches to a record
# images/ vehicle photos
# config/, themes/, translations/, temp/
# Keys → lubelogger_keys:/root/.aspnet/DataProtection-Keys (named volume, NVMe)
# No HDD mount, and that is a deliberate choice: the uploads are receipts and a few photos, so the
# whole app fits in the tier-1 backup unit (database + volumes). An attached receipt is only ever
# opened from the record it belongs to, so there is nothing for the household to browse on a drive.
# The DataProtection key signs the LOGIN COOKIE and nothing else — losing it signs everyone out and
# destroys no data, so it is NOT a `data_key` (checklist 1.9).
#
# WHY THE CUSTOM ENTRYPOINT — the one thing this app cannot do without it.
# `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 the role `IsRootUser`. Measured on the bench 2026-10-10 on a default start: an
# anonymous stranger got 200 on /, 200 on /api/vehicles, 200 on /Home/Settings, and
# `POST /Vehicle/SaveVehicle` answered `{"success":true}` — a stranger READS AND WRITES the family's
# car records as root. So the app may never be published with its own default.
# 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`. The box can only hand the app a PLAINTEXT generated password, so something has
# to hash it — and ASP.NET reads configuration from the environment, so the entrypoint exports the two
# hashes and `EnableAuth=true` before exec'ing the app. Auth is therefore on BEFORE THE FIRST BYTE:
# measured from the container's creation, /api/vehicles was 401 and / was 302 → /Login/Index on the
# first second the app answered at all. No install window to close (checklist 3.5).
# The same shape as templates/radicale (an image whose generated login cannot be passed as plaintext);
# unlike templates/gokapi's entrypoint (REUSE.md §3) it seeds no config file, so an image update cannot
# strand it. It deliberately writes NOTHING: Program.cs adds `data/config/userConfig.json` AFTER the
# environment, and that file is where the household's own settings live — writing our keys into it
# would wipe their dark-mode and tab choices on every start. `ConfigHelper.SaveUserConfig` re-reads
# these three keys from the merged configuration when it saves, so the household cannot turn auth off
# from the UI either (measured: /App/data/config stayed empty).
#
# Sign-up is closed by the app itself: `RegisterNewUser` refuses without a token minted by the root
# user, measured `POST /Login/Register` → {"success":false,"message":"Invalid Token"}. So there is no
# `signup_block` to add, and LUBELOGGER_OPEN_REGISTRATION stays unset.
#
# Outbound traffic: none at start. Three fetches, each on a page the household opens on purpose — the
# sponsors list and the language pack from hargata.github.io, and the release check from
# api.github.com behind a `checkForUpdate` request flag.
#
# Run-identity: the image runs as root (its APP_UID=1654 is not used as USER) — measured; everything it
# writes is inside its own two volumes.
services:
lubelogger:
image: ghcr.io/hargata/lubelogger:v1.7.3
container_name: lubelogger
restart: unless-stopped
environment:
- TZ=Europe/Budapest
- ROOT_USER=${ROOT_USER}
- ROOT_PASSWORD=${ROOT_PASSWORD}
# Without this the app logs "WARNING: No Locale or Culture Configured" and formats dates and
# numbers the invariant way. The UI LANGUAGE is a separate thing the household picks in
# Settings (it downloads hu_HU from the project's own GitHub Pages).
- LUBELOGGER_LOCALE_OVERRIDE=hu-HU
entrypoint:
- /bin/bash
- -c
- |
set -e
export EnableAuth=true
export UserNameHash=$$(printf %s "$$ROOT_USER" | sha256sum | cut -d' ' -f1)
export UserPasswordHash=$$(printf %s "$$ROOT_PASSWORD" | sha256sum | cut -d' ' -f1)
unset ROOT_PASSWORD
exec ./CarCareTracker
volumes:
- lubelogger_data:/App/data
- lubelogger_keys:/root/.aspnet/DataProtection-Keys
networks:
- traefik-public
deploy:
resources:
limits:
memory: 512M
# This image has NO curl, NO wget, NO nc, NO python and NO node — none of REUSE.md §2's four
# healthcheck families exists in it. It does have bash, and bash's /dev/tcp works: measured both
# ways 2026-10-10, rc=0 against the app (HTTP/1.1 200 OK) and rc=1 against a dead port.
healthcheck:
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /Login/Index HTTP/1.0\\r\\n\\r\\n' >&3 && head -1 <&3 | grep -q 200"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
labels:
- "traefik.enable=true"
- "traefik.http.routers.lubelogger.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)"
- "traefik.http.routers.lubelogger.entrypoints=websecure"
- "traefik.http.routers.lubelogger.tls=true"
- "traefik.http.routers.lubelogger.tls.certresolver=letsencrypt"
- "traefik.http.services.lubelogger.loadbalancer.server.port=8080"
volumes:
lubelogger_data:
lubelogger_keys:
networks:
traefik-public:
external: true