Skip to content

About

Self-hosted, featherweight VPS/laptop monitoring with a private Telegram bot per server

Topics

Resources

Contributing

Security policy

Stars

5 stars

Watchers

1 watching

Forks

Repository files navigation

StatixAgent

CI Go Platform Status

Monitor a Linux server or laptop from your phone, with nothing running in the middle.

StatixAgent is a single static Go binary that watches one machine and talks to your own private Telegram bot. It samples the system, watches sshd, and pushes alerts straight to your chat. There is no central server, no dashboard to host, and no third party in your data path — the agent talks only to Telegram's Bot API and to GitHub Releases when you ask it to update.

Everything after installation happens in Telegram: type /status for a live dashboard, get a push message the moment someone logs in over SSH or the power cord is pulled, and tap buttons to drill in.

curl -fsSL https://raw.githubusercontent.com/eliau2005/statixagent/main/install.sh | sudo bash

Project status: early open-source release (v0.x). The core is built, tested, and running; the project is now opened to the community. See Project status and ROADMAP.md.


Why StatixAgent?

Most monitoring stacks assume you are running a fleet and want a time-series database, a query language, and a dashboard. For a single VPS or a home laptop, that is a lot of moving parts to install, secure, and keep alive — often heavier than the thing you are trying to watch.

StatixAgent takes the opposite trade-off:

Prometheus / Grafana-style StatixAgent
Components to run exporter + TSDB + dashboard + alertmanager one binary
Where your data goes scraped into a central store stays on the machine; only alerts you request leave
How you interact a web UI you host and secure a private Telegram bot you already carry
Footprint designed for fleets designed for one box (MemoryMax=128M)
Setup compose files, config, reverse proxy one command, a one-time TUI wizard

It is deliberately not a fleet tool. Each install is fully isolated: one agent, one bot, one chat. That buys simplicity and a small attack surface, at the cost of cross-server correlation (see Trade-offs).

Reach for StatixAgent when you want to know your one box is okay — and hear about it the moment it is not — without standing up an observability stack.

What it can do

Watches

  • Core system — CPU (total, per-core, load average), RAM and swap, disk space per mount, disk I/O rates, network rates and lifetime totals per interface, uptime, process count, open file descriptors. CPU and memory alerts only fire once usage stays over the threshold for about a minute, so a cron job or backup burst stays quiet.
  • Thermal — temperatures (thermal zones + hwmon), fan speeds, and throttling detection. Temperature alerts only fire once the threshold is held for several consecutive samples, so a CPU touching 90 °C for a moment mid-boost stays quiet.
  • Battery & power (laptops / physical machines) — charge, health vs. design capacity, wattage draw, estimated time-to-empty, and an immediate alert when the power cord is pulled.
  • Workloads — systemd units, named processes, TCP ports, HTTP healthchecks, and Docker containers (status, per-container CPU/RAM, and restart-loop detection).
  • Reachability — SSL/TLS certificate expiry for hosts you name, and TCP connect latency from the server's own vantage point (/ping).

Security monitoring

SSH visibility is a first-class feature, not an afterthought:

  • Real-time alert on every SSH login, with geo-IP of the source; successful root logins are escalated.
  • Live session listing (user, source IP, duration, TTY).
  • Login and failure history, kept in memory.
  • Brute-force detection — a sliding-window counter per source IP with a threshold alert and quiet-period dedupe during an ongoing attack.
  • authorized_keys change alerts — the agent hashes root's and every home user's key files and alerts when one changes.

Trusted IPs

If you log in often from a known address, trust it to stop the per-login alert for it. Entries can be single IPv4/IPv6 addresses or CIDR ranges, and come from two places that are merged:

  • The bot — /trust <ip-or-cidr>, /untrust <ip-or-cidr> and /trusted (list), or the 🤝 Trust this IP button on a plain SSH login alert. The button opens a confirm screen restating the address; only ✅ Yes, trust acts, and ❌ Cancel puts the alert's buttons back. Changes apply to the next login, with no restart, and are kept in /etc/statix-agent/trusted_ips (0600, one entry per line). Up to 64 entries; /untrust removes only entries added this way.

  • The config — a static baseline, changed by editing the file and restarting (systemctl restart statix-agent):

    [ssh]
    trusted_ips = ["203.0.113.7", "198.51.100.0/24", "2001:db8:42::/48"]

    /trusted lists these separately, and /untrust says to edit the file instead. Re-running the installer keeps the existing [ssh] settings.

Both lists are empty by default, which keeps the alert-on-every-login behavior. Both use the same rules: invalid entries are rejected (at startup for the config, in the reply for /trust), and so are ranges that would trust everything (0.0.0.0/0, ::/0) and IPv4-mapped ranges shorter than /96. /trust also refuses an address already covered by an existing entry and names it. The trust button only appears on plain logins from an IP address that is not trusted yet — never on root, new-key or brute-force alerts.

A suppressed login still appears in /ssh history and the daily digest; only the push is skipped. Some logins from a trusted address still alert:

  • root logins always raise the ROOT SSH login alert;
  • a key login with a key that user has never logged in with before raises a SSH login with new key alert (once per user and key; see below);
  • a login from an address that failed to authenticate within the last two minutes alerts as usual, since a success right after failures is worth a look;
  • failed attempts are never exempt, so a burst of failures from a trusted address still raises the brute-force alert.

To recognise new keys, the agent remembers the user and SHA256 fingerprint of key logins in /etc/statix-agent/known_keys (0600, one user fingerprint per line). The file is only used once some source is trusted (config or bot). Keys from logins that alert anyway (untrusted sources, root) are learned straight away; a new key from a trusted source is learned only once its alert has been delivered, so a snoozed or undelivered alert fires again on the next login. SSH history is not kept across restarts, so there is nothing to learn from on the first start: after upgrading, or after trusting the first address from the bot, the first key login per user and key from a trusted address alerts once, and later ones stay quiet. Deleting the file resets this. Unreadable lines are skipped with a log message and kept in the file. Password and keyboard-interactive logins carry no fingerprint and stay suppressed from trusted addresses.

The source must be logged as an IP address: if sshd logs hostnames (UseDNS yes), the trusted list cannot match (and no trust button is offered) and the agent logs why.

Alerts and remediation

Alerts arrive as push messages — you never have to ask for them:

  • Threshold crossings (CPU, RAM, disk, temperature, low battery).
  • Power loss, host reboot, new SSH login, brute-force burst, key-file change, certificate expiry, and container down / restart-loop events.
  • Watched units, ports, HTTP checks and processes, checked every minute: an alert when one starts failing and a ✅ when it recovers, never while it stays as it was. A failure already there when the agent starts is not repeated. The alert's 🔍 Open shows the unit or list; with service actions on, a unit alert also offers 🔁 Restart, which asks for confirmation first.

Each alert carries one-tap action buttons for the obvious next step (jump to the relevant metric view, list SSH sessions, open the firewall control) and a 🔕 button to snooze that alert for an hour while you handle it.

Remediation is intentionally minimal and always gated behind an explicit confirmation:

  • SSH session disconnect buttons in /ssh.

  • /firewall — open SSH port 22 via ufw, behind a mandatory confirmation screen. Hosts without ufw get a plain "cannot manage the firewall here" and no buttons, never a guess about what is open. Every change runs as a trial: unless you tap Keep within 2 minutes, the previous rules are restored byte for byte, even if the agent crashes or the host reboots (a systemd-run timer, an in-process timer, and a check at startup). Closing port 22 is off by default; to allow it, set it in the config file (never from Telegram). Keep then also needs a new SSH connection seen after the change:

    [firewall]
    allow_close_ssh = true
    trial_seconds = 120   # 60-600
  • 🤝 Trust this IP on plain SSH login alerts, behind a confirm screen (see Trusted IPs).

  • Service actions, off by default: start, stop and restart a systemd unit or a Docker container, and read its recent logs, from its screen under /services. Turn them on in the config:

    [actions]
    enabled = true
    allow = ["nginx", "postgresql", "web"]  # units and container names

    With no allow list, only the watched units can be acted on, and no container. Units are never enabled or disabled. statix-agent.service, dbus*, systemd-*, *.target and the agent's own container are always refused, and stopping or restarting ssh, docker, containerd or a network manager asks twice. Logs are redacted of the bot token, but they are copied into the chat's history. Re-running the installer drops [actions], which turns them off again.

What it looks like

/status on a laptop that has been up six days, and a power-loss alert from the moment its mains supply dropped.

/status Power lost
/status output in Telegram: CPU, RAM, disks, interfaces, temperature, battery, process count and load, followed by the inline keyboard Telegram alert reading Power lost, running on battery, 79 percent, about four hours left, with Power, Status and snooze buttons

Telegram commands

Commands are registered for autocomplete, and every metric view carries an inline keyboard so you can navigate by tapping instead of typing.

Command Result
/status full snapshot: CPU, RAM, disks, net, temps, battery, uptime
/cpu /mem /disk /net /temp /battery one metric in detail, with sparkline trends on /cpu and /mem
/top heaviest processes by CPU and memory
/digest the day's recap — also auto-sent every morning
/services systemd units, processes, ports, healthchecks
/http endpoint checks (redirects are reported, not followed) · /http add <url> [status] · /http remove <url>
/ping <host[:port]> TCP connect latency from the server
/docker containers with CPU/RAM and restart counts
/ssh live sessions with disconnect buttons · /ssh history · /ssh fails
/ssl certificate expiry · /ssl add <host> · /ssl remove <host>
/firewall open/close SSH port 22 via ufw (says it cannot manage the firewall where ufw is absent)
/trusted trusted SSH sources, by origin (config / Telegram) · /trust <ip-or-cidr> · /untrust <ip-or-cidr>
/watching everything watched, managed with buttons
/settings alert thresholds and digest schedule, tuned with buttons
/services_scan /ports_scan discover candidates to watch
/update check for a new release · /update_confirm asks, then installs it
/clear_chat delete recent messages (asks first)
/help grouped command reference

The Live button animates the latest regular sampler snapshot. It does not take extra samples, evaluate alert thresholds, or add points to metric trends, so opening the view cannot change monitoring behavior.

Commands are accepted only from the single chat ID in the config; updates from any other chat are dropped before parsing.

Self-update

The agent can update itself from GitHub Releases, and the verification is not optional:

  1. Fetch the release's checksums.txt and its detached checksums.txt.sig.
  2. Verify the signature against an ed25519 public key compiled into the binary at release time. If the build carries no key, or the signature does not verify, the update aborts — there is no "skip verification" path.
  3. Verify the downloaded binary's SHA256 against the signed checksum.
  4. Swap atomically (rename), keeping the previous binary as .prev.
  5. A crash-loop guard at startup restores .prev automatically if the new binary keeps dying, and consumes it so versions cannot ping-pong.

Automatic update checks are off by default and opt-in at install time. Full details are in SECURITY.md.

Security model

  • The bot token lives in /etc/statix-agent/config.toml, written atomically with 0600 permissions.

  • Commands are accepted only from the configured chat ID; state-changing actions (/update_confirm, /clear_chat, /firewall, service actions) require an explicit confirmation. The command menu is shown in that chat only. Buttons can only run what the bot's own keyboards emit; any other callback data is refused. In a private chat the sender must be the chat's own user. Forwarded messages, messages sent via an inline bot and messages sent on behalf of a chat (anonymous group admins, channels) are ignored.

  • If chat_id is a group, list who may change state:

    [telegram]
    admin_user_ids = [123456789]

    Every member can then use views (status, metrics, navigation buttons), but only these users can run other commands or press other buttons. Without it every member can still do everything, and the bot posts a warning once per start; a future release will require it for groups. Private chats ignore it. Re-running the installer rewrites [telegram], so add it again after a re-install.

  • The systemd unit ships hardened: NoNewPrivileges, ProtectSystem=full, ProtectHome=read-only, PrivateTmp, a narrow ReadWritePaths, and MemoryMax=128M.

  • Updates are verified with ed25519 and SHA256 before an atomic swap, with automatic rollback.

See SECURITY.md for the full threat model, the privilege model, and the distinction between what is implemented today and future hardening.

Installation

curl -fsSL https://raw.githubusercontent.com/eliau2005/statixagent/main/install.sh | sudo bash

The one-time TUI wizard asks for your bot token (from @BotFather) and chat ID, lets you toggle monitor categories and thresholds, then:

  • writes /etc/statix-agent/config.toml (0600),
  • installs /usr/local/bin/statix-agent,
  • and enables the hardened systemd service (Restart=always).

The TUI runs only at install time. Everything afterward is done through the bot. Piping a script to sudo bash runs it as root — you are encouraged to read install.sh first; it is short.

Supported platforms

The agent runs on Linux and is released for:

  • linux/amd64
  • linux/arm64

It reads /proc, /sys, the auth log (journalctl / /var/log/auth.log), utmp (with a loginctl fallback for utmp-less distros such as Ubuntu 24.10+), and the Docker Engine socket when present.

Optional dependencies

None of these are installed by the agent, and each one degrades to a stated behaviour rather than an error when it is absent:

Tool Used by Without it
ufw /firewall The view says it cannot manage this host's firewall, names the likely backends (firewalld, nftables, iptables) instead of claiming the host is open, and offers no rule-changing buttons. ufw is absent from Alpine, Arch, Debian netinst/cloud images and containers, and is EPEL-only on the RHEL family.
Docker Engine socket /docker The container view and container alerts are disabled.
journalctl SSH auth-log watching Falls back to tail -F /var/log/auth.log.

Development and the full test suite run on any OS (Linux, macOS, Windows) — every parser is a pure function tested against fixtures.

Development

Requires Go 1.25+.

go test ./...   # full suite (14 packages), green on any OS
go vet ./...
make build      # cross-compile linux amd64 binaries into bin/
make dist        # all release artifacts + checksums

See CONTRIBUTING.md for setup, code-quality expectations, and how to structure a change, and docs/ARCHITECTURE.md for the design.

Releasing

Generate a signing key once with go run ./tools/sign keygen, store the private half as the STATIX_SIGNING_KEY repository secret, then push a v* tag — CI builds, signs, and publishes the release.

Project status

Early open-source release (v0.x). The MVP described in mvp.md is built and covered by tests, and the project has iterated through several UX rounds (inline keyboards, live mode, button-driven watch management, alert action buttons, trends, and a first remediation action). It is a working project with a strong foundation, now opened to the community.

It is not yet declared production-ready: it has not been through broad real-world deployment across many distros, and there is no published security audit. Treat v0.x accordingly, and please report what breaks.

Trade-offs

The decentralized design has deliberate costs:

  • No cross-server correlation — the same IP attacking several of your servers is not seen as one pattern.
  • No aggregated view — an "all servers" query means asking each bot.
  • History is in memory — a restart loses SSH/alert history; there is no database, by design. Only the small list of known SSH key fingerprints is kept on disk, along with the bot-managed trusted IPs, and only once some source is trusted.

Roadmap

See ROADMAP.md for what is done, what is stabilizing, and what is planned. Everything beyond the current release is clearly marked as future work and is not a commitment.

Contributing

Contributions are welcome — this is exactly the stage where they have the most impact. Good first areas include Linux distro compatibility, hardware/sensor coverage, SSH/security detection, tests, and documentation. Start with CONTRIBUTING.md.

Reporting a vulnerability

Please do not open a public issue for security problems. Report privately via a GitHub Security Advisory. See SECURITY.md for the disclosure process.

License

StatixAgent is released under the MIT License. By contributing, you agree that your contributions are licensed under the same terms.

Contributors

Thanks to everyone who has contributed to StatixAgent:

Contributors

About

Self-hosted, featherweight VPS/laptop monitoring with a private Telegram bot per server

Topics

Resources

Contributing

Security policy

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages