b7f0f7cef5
gates / gates (push) Failing after 14m4s
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.
113 lines
6.1 KiB
YAML
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
|