Timar

MIT · one container · no agents

Off is the right default. Unpatched isn't.

Timar wakes the machines in your homelab, updates them, reads their logs, and puts them back the way it found them. Nothing is installed on the machines it manages — it needs SSH, and Wake-on-LAN for the ones that sleep. Between runs they stay dark, which is the point, and the reason most of them are off in the first place.

Run it Source on GitHub

v0.1.1amd64 + arm64Linux · OpenWrt · ProxmoxMIT licensed

Timar's dashboard listing six machines. Two are up, three are asleep and marked on-demand, one is down. Each on-demand row offers a wake or shutdown action; always-on rows show n/a. Below, a scheduled work panel shows a daily log sweep and a weekly update run with their last and next runs. The same dashboard in a light theme.
Three states, not two: asleep is a machine that is meant to be off, and it is not painted like a fault. A status page that shows both in red teaches you to ignore red.

The problem

A sleeping machine cannot phone home.

Homelab machines are mostly off. The GPU box runs when you need it, the hypervisor wakes for a job, the build server sits dark for a week. Every tool built for always-on fleets assumes an agent that reports in — which is the one thing a machine that is powered down cannot do. So the machines that go longest without a security update are exactly the ones nothing is watching.

Timar inverts it. Waking the host is step one of the job, and shutting it back down is the last. A machine that was off before the run is off after it.

What a run does

Wake, work, and put it back.

  1. Wake what is asleep A magic packet, or `qm start` on the hypervisor for a guest — a VM has no wake address of its own. From a different subnet, a relay sends the packet from a machine already on that segment.
  2. Update it with the command its platform actually has Debian's apt, Proxmox's dist-upgrade, your own command if you have one. OpenWrt is skipped by default on purpose: an unattended apk upgrade can fill the overlay or land a kernel-module mismatch, and the machine that breaks is the one carrying the session you would repair it from.
  3. Sweep the logs while it is up System log errors, disks past a threshold, stopped containers — and scheduled jobs on that machine that did not run today. Watching for absence is the point.
  4. Shut down whatever started off Guests first, then their hypervisor, in order. Anything that was already running is left running.
An archived update run, summarised as five updated, none failed, one skipped. Three machines are annotated 'woken, updated, shut down again'; two that were already on carry no annotation; the OpenWrt router is marked skipped because no update command is configured for it. The same update run in a light theme.
A run says what it did to each machine, not just that it succeeded. The three annotated lines were asleep when the run started and are asleep again now; the router was skipped rather than quietly left out.

The reports outlive the notification. Every finished run is archived and readable in the UI, so a disk creeping upward or an update failing every Friday shows up as a series rather than a single snapshot. Telegram delivery is a copy, not the only place the findings exist.

An archived log sweep: one machine with findings, none unreachable, three asleep. A written assessment at the top singles out a disk at 91 percent and a drive reporting a pending sector, and says which of the two is the more urgent. Below it, the raw per-host findings — the two smartd lines and the disk, then 'clean' for two hosts and 'offline, not checked' for the three that were asleep. The same log sweep in a light theme.
The written assessment sits above the findings it was written from, never instead of them. A machine that was asleep is recorded as not checked rather than clean — a sweep does not wake the fleet, and reporting an unchecked host as healthy is the one thing a status page must not do.

Measured, not assumed

The failures here are the quiet kind.

A magic packet that never leaves the host. A disk check that exits non-zero and reports all-clear. A schedule that stops firing and writes no error anywhere. None of these announce themselves, so the decisions behind them were measured rather than reasoned about — and the measurements are written down in ARCHITECTURE.md.

0 → 1

Packets on the wire

Wake-on-LAN from a bridge network: the send succeeds, no error, and tcpdump on the LAN sees nothing. Host networking, same image, same button: one packet. That is why the compose file ships network_mode: host.

3.02s → 0.01s

Cold and cached probes

Most of the fleet is supposed to be unreachable, so a probe timeout is the normal case. Probes run in parallel behind a short TTL: the page costs one timeout, not one per sleeping machine.

126 MB

The whole thing

One Alpine image, running as a non-root fixed UID, for amd64 and arm64. A Raspberry Pi is a first-class host — it is what Timar is written on.

Platforms

Commands that exist on the machine you pointed at.

A check that cannot run on a platform says so, instead of quietly reporting all-clear. Every command was run against a real host of that platform before it was written down: busybox and coreutils disagree in ways that fail silently — one df flag GNU accepts and busybox rejects turned into a disk check that passed on every router it was pointed at.

PlatformSystem logDiskContainersUnattended updates
Linux (systemd)journalctlyesDockeryes
Proxmox VEjournalctlyesguests via qmyes
OpenWrtlogreadyesoff by default

Run it

One container, one volume.

$ curl -O https://raw.githubusercontent.com/orkun-soylu/timar/main/docker-compose.yml
$ docker compose up -d

Then open http://<host>:8080 and create the operator account. Nothing else answers until you do — the first screen is the only one served before an account exists. Everything an installation is lives in one directory (/data): config, credentials, state, reports and the SSH key. Backup and migration are cp -a.

Timar's settings page, servers tab: six machines with their SSH login, platform and wake column. Two are always on, two are on-demand, and a Proxmox guest reads 'on-demand via hv-01'. Each row offers enrol, edit and remove. A second tab holds the settings that belong to the whole fleet. The same settings page in a light theme.
The fleet is edited here, not in a YAML file you hand-write — though it is still just config.yaml underneath. A guest reads on-demand via hv-01 because it inherits that from its hypervisor: a VM has no wake address of its own, and calling it always-on would turn every night it spends off into an outage report.

Do not expose this to the internet. Timar holds an SSH key that reaches every machine it manages and can grant itself sudo on them. The login page is the only thing in front of your fleet. Keep it on a private network, or behind a reverse proxy you control.

Nothing to install on the fleet

SSH, and Wake-on-LAN for the machines that sleep. Timar generates its own keypair on first use, installs it with your password once, and pins each host key on first sight.

Optional, not required

A model to summarise sweeps (Anthropic, any OpenAI-compatible endpoint, or Ollama) and Telegram for delivery. Without either, waking, updating and the sweep all still work — you get the raw findings instead of a written assessment.