GitOps reconciliation and recovery for Tasmota device configuration
  • Python 70.8%
  • Shell 24.6%
  • JavaScript 2.2%
  • Dockerfile 1.5%
  • Just 0.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Renovate Bot 9e5f21be74
All checks were successful
Container / publish (pull_request) Has been skipped
Release Tasmota reconciler chart / chart (pull_request) Successful in 13s
Container / validate (pull_request) Successful in 30s
Container / validate (push) Has been skipped
Container / publish (push) Successful in 56s
chore(deps): update dependency anchore/syft to v1.57.1
2026-11-09 16:21:56 +00:00
.forgejo chore(deps): update dependency anchore/syft to v1.57.1 2026-11-09 16:21:56 +00:00
charts/tasmota-reconciler chore(chart): release Tasmota chart 0.1.1 2026-09-25 17:00:53 +02:00
deploy feat(tasmota): stage app-owned GitOps deployment 2026-09-27 11:35:02 +02:00
examples fix(tasmota): retry observations and isolate credentials (#7) 2026-08-05 20:47:43 +00:00
resources/cosign feat: scaffold Tasmota reconciler 2026-07-26 14:56:58 +02:00
scripts ci(chart): migrate to pinned shared publisher preparation 2026-09-28 08:00:52 +02:00
src/tasmota_reconciler fix(tasmota): retry observations and isolate credentials (#7) 2026-08-05 20:47:43 +00:00
tests fix(tasmota): retry observations and isolate credentials (#7) 2026-08-05 20:47:43 +00:00
.gitattributes feat(tasmota): stage app-owned GitOps deployment 2026-09-27 11:35:02 +02:00
.gitignore feat: scaffold Tasmota reconciler 2026-07-26 14:56:58 +02:00
.python-version chore(deps): update python docker tag to v3.15 2026-10-13 02:57:22 +00:00
.sops.yaml feat(tasmota): stage app-owned GitOps deployment 2026-09-27 11:35:02 +02:00
Containerfile chore(deps): update docker.io/library/python:3.15.2-alpine3.24 docker digest to a943ed0 2026-11-06 00:26:39 +00:00
justfile feat: scaffold Tasmota reconciler 2026-07-26 14:56:58 +02:00
pyproject.toml fix(deps): update dependency decode-config to v15.6.0.0 2026-09-26 18:32:40 +00:00
README.md feat(tasmota): stage app-owned GitOps deployment 2026-09-27 11:35:02 +02:00
renovate.json fix(renovate): update signing tool checksums with release versions 2026-10-11 10:18:26 +02:00
requirements-dev.lock fix: fall back to native MQTT commands (#4) 2026-07-27 05:04:38 +00:00
requirements.lock fix: fall back to native MQTT commands (#4) 2026-07-27 05:04:38 +00:00
uv.lock chore(deps): lock file maintenance 2026-11-09 00:24:19 +00:00

Tasmota reconciler

tasmota-reconciler turns selected, stable Tasmota settings into Git-owned desired state. It uses the upstream decode-config utility for version-aware configuration transport and restore, while this project adds:

  • a multi-device manifest;
  • MQTT credentials from mounted secret files;
  • subset-aware drift detection;
  • explicit plan, apply, and encrypted .dmp backup operations;
  • bounded retries for transient or truncated device observations;
  • reboot-aware post-apply verification;
  • native Tasmota MQTT command fallback when a restore fails to converge; and
  • JSON-line output suitable for Kubernetes Jobs and log monitoring.

Runtime values such as relay state, uptime, signal strength, and accumulated energy are intentionally outside the reconciler unless explicitly present in a device's desired JSON. Secret-like fields are rejected from desired-state files; credentials belong in mounted SOPS-managed secret files.

Configuration

See examples/manifest.json and examples/devices/winterstern.json. Relative desiredFile paths are resolved from the manifest directory.

The broker URL must not contain credentials. If authentication is required, use usernameFile and passwordFile. devicePasswordFile is the password stored in the Tasmota device and is required by decode-config when its MQTT broker identity uses a different password.

Individual device entries may override the global deviceMqtt identity with mqttUsername and mqttPasswordFile. Both fields must be supplied together; this allows each device to have a separately revocable broker credential.

Optional top-level wifi and deviceMqtt blocks inject their passwords only into a short-lived restore file. This lets Git own the SSID and device MQTT identity without putting either password in the manifest, desired JSON, drift output, command arguments, or logs. Password values are intentionally not verified because decode-config hides them when observing a device.

Commands

tasmota-reconciler --manifest /config/manifest.json plan
tasmota-reconciler --manifest /config/manifest.json apply
tasmota-reconciler --manifest /config/manifest.json backup \
  --output-directory /backups

plan returns:

  • 0 when all selected devices are converged;
  • 2 when drift exists; and
  • 1 for configuration or communication errors.

apply reads current state, restores only devices with drift, and retries verification while the device reboots. If the restore errors or persistent drift remains, the reconciler translates the remaining supported fields into native Tasmota commands, submits them with Backlog, and verifies the device a second time. It still fails unless every Git-owned field converges.

The fallback covers the Wi-Fi and MQTT transition fields, energy calibration, resolution, template/module, naming, topic, and telemetry fields used by the managed fleet. It rejects values that cannot be represented safely in a Backlog command instead of sending an ambiguous command.

Backups use Tasmota's encrypted .dmp format. They should be written to a persistent volume and included in the cluster backup policy.

Development

The repository uses the managed Nix Python environment:

direnv exec . uv sync --frozen
direnv exec . uv run ruff check .
direnv exec . uv run pytest

Image publication

Pull requests build the complete image, execute tests and linting inside the build, and inspect the CLI without publishing. Merges and weekly scheduled runs publish signed images with SPDX SBOM and SLSA provenance to:

registry.ancofazi.de/homelab/tasmota-reconciler:<decode-config-version>-<run>

This repository owns the complete Tasmota Reconciler deployment in deploy/, including device desired state and its SOPS-encrypted credentials. The chart's image pin is in charts/tasmota-reconciler/values.yaml. Chart tags publish the chart only; Flux reads this repository's deployment tree after the separately reviewed source cutover in Andreas/homelab.

CronJob chart

charts/tasmota-reconciler packages only the reconciler CronJob. Its default render matches the live Helm-owned CronJob, including the image tag and digest. The chart references the ServiceAccount, ConfigMap and Secret defined in deploy/; the chart values can override those references and the CronJob name. Until the separate homelab source cutover, Flux still applies the identical manifests from homelab.

The chart workflow validates pull requests and publishes an immutable OCI chart only for a matching chart-v<Chart.yaml version> tag on main. It uses the digest-pinned shared app-release action in publish-only mode. A tag does not deploy the chart by itself; Renovate proposes chart-version updates to deploy/helmrelease.yaml for review.

The live tasmota-reconciler CronJob already belongs to the app-owned Helm chart. Its initial handoff used a distinct suspended canary, then suspended and pruned the raw CronJob before installing and resuming the original name under Helm. The first scheduled Helm-owned Job completed successfully.