schmidt.florian 8e2e632c74
ci / build-test (push) Successful in 1m8s
Deploy: make LXC installer's cosmetic post-install steps non-fatal
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

MeterVault

A self-hosted, local-first energy & utility metering platform. MeterVault pulls meter data from Home Assistant, Tasmota and raw MQTT on a schedule, stores every reading timestamped and immutable, normalizes it into consumption, and turns it into cost dashboards. Energy types (electricity, water, heating oil, gas, district heat, …) and meters are user-defined — nothing is hardcoded.

Successor to a hand-maintained Energiebilanz spreadsheet. See docs/SDD.md for the full design.

Features

  • Automatic ingestion from MQTT/Tasmota (persistent subscriptions) and Home Assistant (REST poll or push), plus manual entry, a REST push API, and CSV import.
  • Immutable raw readings on a TimescaleDB hypertable; a normalized, append-only consumption layer on top — reproducible, auditable.
  • Seven measurement modes (cumulative/generation registers, burner runtime, tank/consumable, direct delta, instant rate, virtual). Handles meter swaps, counter resets, tank dip-sticks with calibration, and virtual meters defined by an expression (PV self-consumption, savings, net).
  • Tariff engine with time-ranged price history (unit/base/feed-in), scoped global / per type / per meter; cost categories decoupled from energy types; meterless manual costs.
  • Continuous aggregates (daily/monthly/yearly, local timezone) so dashboards never scan raw.
  • Dashboard: cost KPIs with period-over-period deltas, "what costs most", a "what cost more/ less" difference view, trends, a PV/Solar panel (generation, self-consumption, autarky %, savings), an oil/consumable panel (tank gauge, deliveries, burner runtime, effective L/h, forecast-to-empty) and a per-meter detail view (raw readings, consumption, sources, tariff timeline, events), one-click reference-data load, CSV dry-run.
  • Per-energy-type flow pages (Electricity, Water, …): a Sankey diagram of the meter chain — a downstream meter is a subsection of an upstream one (main → car, pool, garden, …), arrow thickness ∝ amount, with an auto-computed "Other/unmetered" remainder. Meters can have several upstreams (a merge, e.g. grid + solar → house).
  • Admin UI: full create/edit/delete for energy types, meters (with consumption recompute on mode/baseline change, and cycle-safe upstream-meter wiring), ingest sources, tariffs, cost categories, and MQTT/Home-Assistant connectors; a "Test connection" for Home Assistant; effective-settings view.
  • REST API + OpenAPI/Swagger, API-key auth, reverse-proxy trust (Authelia/Traefik).
  • JSON config export/import for portability; Docker Compose + multi-arch image.

Quick start (Docker)

docker compose -f deploy/docker-compose.yml up -d
# open http://localhost:8760  → Import → "Load reference data" for a populated demo
# ...or start pre-populated:  METERVAULT_SEED=true docker compose -f deploy/docker-compose.yml up -d
# API docs at http://localhost:8760/swagger

Quick start (Proxmox VE LXC)

A community-scriptsstyle installer builds a self-contained LXC (Debian + PostgreSQL/TimescaleDB + the app as a systemd service). Run on the Proxmox host:

bash -c "$(curl -fsSL https://git.finalfactory.de/FinalFactory/MeterVault/raw/branch/master/deploy/ct/metervault.sh)"

It asks the standard container questions, optionally loads the demo dataset, and prints the URL (http://<ct-ip>:8760) plus the generated DB password. Re-run update inside the container to pull the latest source and rebuild. It builds from this public Gitea repo (there is no prebuilt tarball — releases ship as a container image). See deploy/ct/metervault.sh and deploy/install/metervault-install.sh.

Configuration is via environment variables (Section__Key double-underscore mapping), e.g.:

Variable Purpose
ConnectionStrings__Default PostgreSQL/Timescale connection string
MeterVault__TimeZone Local timezone for buckets/display (default Europe/Berlin)
MeterVault__ApiKeys__0 An API key accepted on the X-Api-Key header
MeterVault__AllowAnonymousApi true to open the REST API without a key (trusted LAN only)
MeterVault__ReverseProxyTrust true to honour X-Forwarded-User behind an auth proxy
MeterVault__EnableLiveIngestion false to disable the MQTT/HA workers
MeterVault__SeedReferenceData true to load the bundled demo dataset on first start (idempotent)

The REST API is closed by default: with no ApiKeys configured and AllowAnonymousApi off, it returns 401. Set at least one API key (or open it explicitly for a trusted network).

Secrets (broker/HA tokens) are never stored in the database — endpoint configs hold the name of an environment variable, resolved at runtime.

Pushing readings (Home Assistant)

curl -X POST http://localhost:8760/api/v1/readings \
  -H "X-Api-Key: $METERVAULT_API_KEY" -H "Content-Type: application/json" \
  -d '[{"meterId": 1, "time": "2026-01-01T12:00:00Z", "value": 47200}]'

See docs/wiring.md for wiring up Tasmota, MQTT and Home Assistant.

Development

dotnet build
dotnet test                       # integration tests spin a TimescaleDB via Testcontainers (needs Docker)
dotnet test tests/Core.Tests      # fast unit tests, no Docker
dotnet run --project src/App

Architecture, project layout and conventions live in CLAUDE.md.

Releasing

Edit the VERSION file on master; Gitea Actions tags vX.Y.Z and builds/pushes a multi-arch image to the Gitea container registry (.gitea/workflows/). Locally: pwsh deploy/build-and-push.ps1 -Registry git.finalfactory.de -Image finalfactory/metervault -Push. Requires a Docker-capable act_runner; the image build itself is self-contained.

License

Not yet chosen (see SDD §14). Add a LICENSE before the first public tag.

S
Description
No description provided
Readme 883 KiB
Languages
C# 69.5%
HTML 25.3%
Shell 3.6%
CSS 0.9%
JavaScript 0.3%
Other 0.4%