Commit Graph

10 Commits

Author SHA1 Message Date
schmidt.florian 813d96709e Deploy: make the LXC updater a real file so existing containers can bootstrap it
ci / build-test (push) Successful in 1m28s
The updater was emitted from a heredoc inside the installer, so a container
provisioned before it existed had no way to obtain /usr/bin/update — 'update'
just reported command not found, with no path forward short of reinstalling.

Ship it as deploy/install/metervault-update.sh and have the installer
install(1) it from the checkout it just built. An already-provisioned
container can now bootstrap from its own source tree after a git pull, and the
updater refreshes itself (atomic rename, since bash reads a running script
lazily) so the same stranding does not recur.

Uses echo rather than msg_warn for the missing-file case: msg_warn is not
otherwise relied on in this script, and an undefined function under the
framework ERR trap would abort an otherwise-successful install.

Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd
2026-07-18 09:48:02 +02:00
schmidt.florian 400d349d6a Deploy: guard installer against host execution + self-contained update command
ci / build-test (push) Successful in 1m27s
Safety fix after a host-vs-container mishap: add a systemd-detect-virt guard at the top of the install script (before install.func is sourced) that refuses to run outside a container, so a mistaken invocation on the Proxmox host exits harmlessly instead of running 'apt upgrade' + installing PostgreSQL/.NET on the hypervisor (override METERVAULT_ALLOW_HOST=1). Also rewrite /usr/bin/update to a self-contained in-container rebuild (git pull + dotnet publish + restart) instead of re-running the ct script through the framework.

Claude-Session: https://claude.ai/code/session_01Kib2MniVFbD95fkgLgBBnB
2026-07-17 12:36:29 +02:00
schmidt.florian 8e2e632c74 Deploy: make LXC installer's cosmetic post-install steps non-fatal
ci / build-test (push) Successful in 1m8s
The framework's motd_ssh runs 'chmod -x /etc/update-motd.d/*', which returns 'Operation not permitted' in some unprivileged LXCs. Under the community-scripts ERR trap that aborted an already-successful install right before /usr/bin/update was written. Write the update command first, then run motd_ssh/customize/cleanup best-effort (motd_ssh now guarded like customize already was).

Claude-Session: https://claude.ai/code/session_01Kib2MniVFbD95fkgLgBBnB
2026-07-17 12:21:43 +02:00
schmidt.florian 18dd4f720d Deploy: Proxmox VE community-scripts LXC installer (Gitea, build-from-source)
ci / build-test (push) Successful in 1m18s
Adds deploy/ct/metervault.sh (host) + deploy/install/metervault-install.sh (in-LXC), modeled on MQTTower's community-scripts installer but adapted for MeterVault: it lives on public Gitea (not GitHub) and is a single Blazor app + TimescaleDB (not multi-mode). The host script sources the community-scripts framework, creates a Debian 12 LXC and runs the install script via pct exec. The install script sets up PostgreSQL 16 + TimescaleDB, installs the .NET SDK, builds the app from the public Gitea repo (no prebuilt tarball exists — releases ship as a container image), writes /etc/metervault/environment + a systemd unit on port 8760, and installs an 'update' command (git pull + rebuild). scripts/run-metervault-ct-install.sh is the maintainer helper. All three pass shellcheck (warning-level clean) and bash -n. README documents the one-liner.

Claude-Session: https://claude.ai/code/session_01Kib2MniVFbD95fkgLgBBnB
2026-07-17 11:18:27 +02:00
schmidt.florian fe3d81b192 Deploy: change default HTTP port 8080 -> 8760
8080 is a very common, collision-prone port. 8760 (hours in a year) is an uncommon default that also avoids Home Assistant's 8123. Applied across Dockerfile (ASPNETCORE_URLS/EXPOSE), compose (mapping + healthcheck), the Unraid template, README and a code comment. The METERVAULT_PORT host override still works.

Claude-Session: https://claude.ai/code/session_01Kib2MniVFbD95fkgLgBBnB
2026-07-17 11:05:01 +02:00
schmidt.florian 1282acf82c SDD §8 panels (PV/oil/meter-detail) + fix reference-data-in-Docker
Complete the SDD §8 dashboard views that were deferred at the M5 boundary,
and fix a shipping bug that left the Docker demo empty.

Bug: "Load reference data" created meters/tank/tariffs but imported zero
readings in Docker. Root cause: sampledata/ was excluded by .dockerignore and
never copied into the build stage, so the App csproj's linked Content glob
resolved to nothing at publish time; ReferenceDataImporter then silently
skipped the missing CSVs after already writing its marker meter, leaving the
DB permanently "loaded" but empty.
  - .dockerignore: stop excluding sampledata/
  - Dockerfile: COPY sampledata/ into the build stage
  - ReferenceDataImporter: fail-fast (validate CSVs exist before the marker
    meter) and throw instead of silently skipping a missing file
  - Program.cs + MeterVaultOptions: opt-in MeterVault__SeedReferenceData
    (compose METERVAULT_SEED=true) for a one-command populated demo

New SDD §8 panels (read models in Infrastructure/Dashboard, Blazor pages):
  - §8.4 Solar/PV (/solar): generation from GenerationCounter meters;
    self-consumption / autarky % / self-consumption % / savings derived from
    meters tagged total_load & grid_import via Meter.Meta role config
    (MeterRoles/MeterMeta) — nothing hardcoded by name.
  - §8.5 Oil/consumable (/consumables): tank level (cm→L calibrated), fill
    gauge, deliveries log, burner runtime, effective L/h (fixed/empirical),
    forecast-to-empty, tariff cost, monthly series.
  - §8.6 Meter detail (/meters/{id}): raw readings, normalized consumption,
    source status, tariff timeline, events, measured-vs-estimated markers.
  - Reusable SeriesChart component; nav links; Meters list rows link to detail.

Tests: MeterMetaTests (Core, +10); DashboardRenderTests extended to assert the
three panel services compute real figures and the new routes render (108 total,
all green). Live-verified in Docker: seed imports 302 readings / 347 consumption
rows; panels render (generation 16,481 kWh, oil 3,967 L) cross-checking the DB.

Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK
2026-07-14 09:52:11 +02:00
schmidt.florian 39da00d486 Switch to master branch + Gitea Actions
ci / build-test (push) Successful in 1m15s
version-tag / tag-if-newer (push) Successful in 6s
- Move CI/release workflows from .github/workflows to .gitea/workflows (Gitea Actions),
  targeting the master branch.
- docker-publish: push to the Gitea container registry (git.finalfactory.de) with a
  lowercased image name; login via github.token or a PACKAGES_TOKEN secret.
- version-tag: gitea-actions bot identity; optional RELEASE_TOKEN to re-trigger the image build.
- Update README/CLAUDE.md/Unraid template/build-and-push.ps1 to the Gitea registry + master.

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
2026-07-13 16:26:04 +02:00
schmidt.florian a6edec2b12 Polish/audit: fix bugs found by 3 subsystem audits
ci / build-test (push) Successful in 2m45s
Correctness/data:
- Fix demo cost double-count: reference importer no longer imports the Kosten Strom/Wasser
  columns for categories that are metered (only Heizung), so Wasser rollup is 70€ not 140€.
- Spurious-decrease guard: only a reset/swap in the window (prevReading, thisReading] explains
  a decrease — an old historical reset no longer permanently disables the guard.
- Gate swap auto-detection on MappingProfile.DetectCumulativeSwaps (flag was ignored).
- Prorate basePrice by bucket length (day/month/year); guard virtual expressions against NaN/Inf.

Concurrency/infra:
- Blazor: register a DbContextFactory; CostService/DashboardService and the read pages now use
  short-lived per-operation contexts (no shared circuit DbContext); guard Trends re-entrancy.
- /events: wrap event insert + consumption recompute in one transaction (atomic); 404 (not 500)
  on unknown meter.
- MQTT worker: subscribe to newly-added topics on each tick; move client cleanup into finally.
- Migrations: CREATE MATERIALIZED VIEW IF NOT EXISTS + if_not_exists on CAgg/compression/
  hypertable calls (re-run-safe after a mid-migration crash).
- HA worker: prune stale poll-schedule entries; export: null dangling ImportBatchIds on restore.

API/security:
- API fail-closed by default: with no keys and AllowAnonymousApi off, /api/v1 returns 401
  (protects /export and /import). New MeterVault:AllowAnonymousApi opt-in.
- Cap /readings batch at 5000; report ignored (unknown-meter) count; enums as strings in JSON.

+4 regression tests (guard window, API closed, /events 404, no demo double-count). 98 tests
green; Docker deploy re-verified healthy with the API fail-closed.

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
2026-07-13 12:56:51 +02:00
schmidt.florian e223278771 M7: release polish — export/import, docs, easy docker push
- Easy docker push (MQTTower ergonomics + the image push it lacks): VERSION file →
  version-tag.yml (semver-guard auto-tag) → docker-publish.yml (buildx multi-arch → GHCR,
  registry centralized for one-line retarget to git.finalfactory.de) + ci.yml + build-and-push.ps1.
- Verified end to end: deploy/Dockerfile builds; docker compose stack (app + timescaledb) comes
  up healthy; /healthz and the dashboard respond in-container.
- JSON config export/import (ExportService) with id remapping on restore + GET /export, POST
  /import endpoints; round-trip test preserves meter→type, meter-scoped tariff, category links.
- README, HA/Tasmota/MQTT wiring guide (docs/wiring.md), Unraid template.
- CLAUDE.md updated to reflect the built codebase.
- i18n: locale-aware number/currency formatting (de-DE); full de UI string localization deferred.

95 tests green (56 Core + 39 integration).

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
2026-07-13 12:25:03 +02:00
schmidt.florian 48a7f5a825 M0: scaffold solution, EF+Timescale schema, /healthz
Five-project Clean Architecture solution (Core/Infrastructure/App + Core.Tests/
Integration.Tests) on .NET 10 with central package management, snake_case EF mapping,
and shared build/style config.

- Full domain entity set + EF DbContext for the SDD §5.3 schema (singular table names).
- InitialSchema migration (relational) + TimescaleHypertables migration (raw SQL:
  create_hypertable + compression on reading, hypertable on consumption).
- App wiring: Serilog (actually wired, unlike MQTTower), DbContext, migrate-on-startup,
  /healthz. Serves plain HTTP behind a reverse proxy (no HTTPS redirect).
- deploy/Dockerfile (2-stage, ICU-capable) + docker-compose (app + timescaledb).
- Integration.Tests: shared TimescaleFixture (Testcontainers) — migrations, hypertables,
  compression policy, and /healthz all verified green (4/4).

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
2026-07-13 11:02:21 +02:00