AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
thermoctl-fleet ist der Überblick des Vermieters über alle Wohnungen: Heartbeat, Störungsmeldungen, Alarm bei Ausbleiben, ein kurzer geschlossener Befehlskatalog. Keine zweite Steuerung, kein Datensam
Translation pending
Developer toolsPython · GoGitHub @MagicalWig34653OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Fleet-Übersicht für thermoctl: Herzschlag, Störungen und Wartungsbefehle über alle Wohnungen – ohne zweite Steuerung, ohne Mieterdaten in der Cloud
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A small cloud service for landlords with several thermoctl
installations: it receives a heartbeat with health data from every
apartment, collects faults, alarms on absence of a heartbeat, and can
send a short, closed list of maintenance commands to an apartment. It is
not a second controller -- setpoints, schedules, and arming stay in the
apartment -- and not a data collector: room temperatures, setpoints, and
tenant data are not transmitted. The full reasoning for this scope is in
docs/specification.md.Read the full README ↗The landlord's overview across all apartments: health, faults, and an alarm when a heartbeat goes missing.
Website · Dokumentation · Interaktive Demo
The website and the documentation are in German; this README is in English. The screenshots show the real UI against a locally seeded demo fleet, with fictitious data.
thermoctl-fleet is a small cloud service for landlords who run several
thermoctl installations. Every apartment sends a heartbeat
with health data; the service collects faults, raises an alarm when a
heartbeat is missing, and can send a short, closed list of maintenance
commands to the apartment.
thermoctl stays a standalone, self-hostable single-apartment product and
works fully without this service. The two never talk directly: a very small
agent on each apartment's base station queries thermoctl only through its
existing read-only REST interface and is the only component that talks to the
cloud.
The full reasoning is in docs/specification.md,
which is the authoritative source for this project.
report_now,
fetch_logs, backup_now, agent_restart, diagnostic_bundle), each with
an id, an expiry, and a local log entry in the apartment.flowchart LR
subgraph apt["Apartment (base station)"]
T["thermoctl<br/>(controller)"]
A["agent"]
W["watchdog (Go)"]
T -- "read-only REST" --> A
W -. "swaps agent container" .-> A
end
F["fleet service<br/>(cloud)"]
A -- "HTTPS POST: heartbeat, events, results" --> F
F -- "SSE: GET /v1/commands" --> A
L["Landlord (browser)"] --> F
The agent is the security boundary, not the cloud: it decides locally whether a command is executed at all (id already seen, expiry exceeded, precondition met). The watchdog only swaps between two already present, already verified container images and has no network access.
The repository holds both Docker images, the watchdog, and the system image
recipe because all of them are tightly coupled through one contract: the
Pydantic models in protocol/ are imported by both the cloud and
the agent, which makes contract tests possible.
| Apartment detail | Sign-in with 2FA | Overview on a phone |
|---|---|---|
Requires Python 3.13 or newer.
python3.13 -m venv .venv && .venv/bin/pip install -e ".[dev,fleet,agent,flash,docs]"
.venv/bin/python -m tools.dev_fleet
The launcher creates a private, gitignored .dev/ database with a demo login
and TOTP key, prints the login, and serves the UI on localhost:8000. Use
--reset (with --yes to skip the prompt) to recreate the state, --port to
change the port, and --no-reload to disable source watching.
Alternatively, run the bare service (no database, no authentication; see
docs/STATUS.md):
.venv/bin/uvicorn fleet.app:app --reload
Two independent images come from one repository:
docker build -f docker/Dockerfile.fleet -t thermoctl-fleet .
docker build -f docker/Dockerfile.agent -t thermoctl-agent .
docker run -d --name fleet --env-file .env -p 127.0.0.1:8100:8000 thermoctl-fleet
docker/compose.example.yml illustrates how the
two fit together. It is not a rollout recipe: the agent runs on each
apartment's base station, not next to the fleet service. Keep your own values
in a local .env that is never committed.
Accounts are created only on the server, never through the web UI. The
password is prompted interactively; FLEET_DATABASE_URL and FLEET_TOTP_KEY
must be set.
python -m fleet.admin create-user alice
Required for operation:
| Variable | Purpose |
|---|---|
FLEET_DATABASE_URL | SQLAlchemy connection string of the database |
FLEET_TOTP_KEY | Key under which accounts' TOTP secrets are stored encrypted |
FLEET_WEBAUTHN_RP_ID, FLEET_WEBAUTHN_ORIGIN, FLEET_WEBAUTHN_RP_NAME | Passkey (WebAuthn) relying party |
FLEET_PUBLIC_URL | Public address, written into the generated agent-registration.json |
FLEET_CERT_FINGERPRINT | SHA-256 fingerprint of the server certificate (sha256:<64 hex>) that agents pin |
Many further optional variables (alarm interval, retention periods, session
and lockout behaviour, registration throttling, SMTP/webhook alerts,
FLEET_UI_PASSWORDLESS_NETWORKS, ...) are described, derived from the source,
in the
documentation chapter on running the service.
TLS cannot be switched off; usually a reverse proxy terminates it.
Terminal UI (German interface; guides through image, disk, settings, confirmation, writing, read-back, and boot files):
python -m tools.flash_tui
Or the CLI. Show the eligible disks, do a dry run, then verify what was written:
python -m tools.flash_image list-disks
python -m tools.flash_image flash \
--image thermoctl-base-station.img.xz --disk /dev/disk4 \
--fleet-address https://fleet.example.invalid \
--certificate-fingerprint sha256:<64 hex characters> \
--registration-code <registration code from the fleet UI> \
--dry-run
python -m tools.flash_image verify --disk /dev/disk4 --image thermoctl-base-station.img.xz
Drop --dry-run to write for real. Optional: --backup-recipient (a public
age recipient, repeatable), --wifi-ssid and --wifi-password,
--boot-mount-point. The tool offers only external, physical disks and
erases the target; disks over 256 GB additionally need --allow-large-disk.
--yes skips the typed confirmation only together with
--i-know-this-erases-the-disk.
An Apple Silicon Mac, Lima, and QEMU are enough to run the whole registration flow against a locally started fleet service. The VM uses a locally built agent image, so the real watchdog digest swap is not exercised.
brew install lima qemu
tools/mac-test-vm create
tools/mac-test-vm start
tools/mac-test-vm enroll # confirm the device in the UI, then:
tools/mac-test-vm finish
tools/mac-test-vm logs # also: status, ssh, stop, delete --yes
These six points are not up for renegotiation; see CLAUDE.md
and the specification.
CommandType extends what a
compromised fleet server could do to an apartment; stage 2 needs the
owner's explicit approval.agent/ and
are enforced there, whatever the cloud says.go.mod stays
free of dependencies.| Path | Contents |
|---|---|
fleet/ | Cloud service (FastAPI): heartbeats and events, SSE command output, web UI |
agent/ | Agent on the base station: heartbeats, command execution, desired-state reconciliation, backup |
protocol/ | Shared Pydantic models, the contract between both sides |
watchdog/ | Go module without dependencies; swaps the agent container; no Docker image |
image/ | Recipe for the prepared base-station images (Raspberry Pi OS, Debian 13) |
tools/ | Dev server, flash tool, screenshots, image check, Mac test VM |
docker/ | Dockerfile.fleet, Dockerfile.agent, example compose file |
site/ | GitHub Pages website, documentation, and click demo (plain HTML/CSS/JS) |
branding/ | App icon (Icon Composer bundle) and renders |
.venv/bin/python -m pytest
.venv/bin/ruff check .
.venv/bin/mypy protocol fleet agent tools
(cd watchdog && go vet ./... && go test ./...)
Use python -m pytest rather than the .venv/bin/pytest console script: with
an editable install on macOS the script does not reliably find the package.
CI (ci.yml) runs ruff, mypy, and pytest on
Python 3.13 and 3.14 and builds both Docker images; the watchdog, image, and
Pages workflows are separate.
Open the repository with the project interpreter and use the shared .run/
configurations (grouped in PyCharm; names are German):
This is a scaffold under active development, not a finished product. The
current state and every open point are tracked in
docs/STATUS.md. Known open points:
service_restart, apply_update,
factory_reset, open_access) are not available on the command channel and
need the owner's approval after a heating season of operational experience.Please read CLAUDE.md first, in particular the scope limits and
the six security principles. Changes touching them need explicit approval from
the project owner. Every endpoint and function gets a real test; ruff, mypy,
and pytest must pass. Never commit secrets, apartment ids, or addresses.
Licensed under the GNU Affero General Public License, version 3
(AGPL-3.0-only), like thermoctl.
docs/Specification and STATUS.md |
tests/ | Test suite, including contract tests across both sides |