<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tyxak</title>
    <description>The latest articles on DEV Community by tyxak (@tyxak).</description>
    <link>https://dev.to/tyxak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3941080%2F6762ff41-dc6c-43b1-ab36-aca5ac1d16a5.png</url>
      <title>DEV Community: tyxak</title>
      <link>https://dev.to/tyxak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tyxak"/>
    <language>en</language>
    <item>
      <title>My NAS kept its encryption keys on itself — so I built a KMIP server into my open-source monitoring tool</title>
      <dc:creator>tyxak</dc:creator>
      <pubDate>Thu, 30 Jul 2026 14:14:16 +0000</pubDate>
      <link>https://dev.to/tyxak/my-nas-kept-its-encryption-keys-on-itself-so-i-built-a-kmip-server-into-my-open-source-monitoring-8o7</link>
      <guid>https://dev.to/tyxak/my-nas-kept-its-encryption-keys-on-itself-so-i-built-a-kmip-server-into-my-open-source-monitoring-8o7</guid>
      <description>&lt;p&gt;I maintain &lt;a href="https://github.com/tyxak/remotepower" rel="noopener noreferrer"&gt;RemotePower&lt;/a&gt;, an open-source (MIT)&lt;br&gt;
self-hosted control plane for a small fleet or a homelab — monitoring, alerting,&lt;br&gt;
CVE scanning, patching, a CMDB. It started as a tool for my own machines and&lt;br&gt;
grew from there. Version 6.4.1 just shipped, and the headline feature came out&lt;br&gt;
of a realisation that had been quietly bothering me for a while.&lt;/p&gt;
&lt;h2&gt;
  
  
  The itch
&lt;/h2&gt;

&lt;p&gt;My Synology encrypts its volumes. Good. It stores the encryption keys for those&lt;br&gt;
volumes... on the Synology. Same box, same disks, same burglar. Whoever walks&lt;br&gt;
off with the appliance gets the lock and the key together, which defeats a lot&lt;br&gt;
of the point of encrypting at all.&lt;/p&gt;

&lt;p&gt;The industry already has an answer: &lt;strong&gt;KMIP&lt;/strong&gt; (Key Management Interoperability&lt;br&gt;
Protocol), the OASIS standard that lets an appliance fetch its keys from an&lt;br&gt;
external key manager at mount time. Synology DSM, TrueNAS and VMware vSphere&lt;br&gt;
all speak it natively. The problem is the other end of the conversation —&lt;br&gt;
everything I found that implements the &lt;em&gt;server&lt;/em&gt; side is an enterprise appliance&lt;br&gt;
or a heavyweight commercial suite. There was no small, self-hostable KMIP&lt;br&gt;
server a homelab could point a NAS at.&lt;/p&gt;

&lt;p&gt;So I built one into the tool that was already monitoring those boxes.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;It's deliberately conservative in design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Off by default.&lt;/strong&gt; Nothing listens until you enable it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A separate, sandboxed sidecar&lt;/strong&gt; (&lt;code&gt;remotepower-kmipd&lt;/code&gt; on tcp/5696) that
terminates mutual TLS and parses the KMIP wire format — and holds &lt;em&gt;no&lt;/em&gt; keys,
no database access, no master key. It runs under systemd &lt;code&gt;DynamicUser=yes&lt;/code&gt;
with no access to the data directory. All the material that matters (the CA,
the key objects, encryption at rest, the activity log) lives behind the main
API, reached over loopback with a shared secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutual TLS only.&lt;/strong&gt; Enabling it generates a private CA; each appliance gets
its own client certificate through a three-step wizard, and the private key
is shown once and never stored server-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An encrypted recovery bundle&lt;/strong&gt;, because key custody without a way back
isn't a feature — it's a liability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setup is one installer flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./install-server.sh &lt;span class="nt"&gt;--with-kmip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The supported operation set is deliberately matched to what DSM actually calls&lt;br&gt;
in practice (an appliance that hits an unimplemented KMIP operation tends to&lt;br&gt;
abort its whole setup with a generic error, so partial support is worse than&lt;br&gt;
none).&lt;/p&gt;

&lt;p&gt;The footgun, stated plainly&lt;/p&gt;

&lt;p&gt;External key custody has a real trade-off, and I'd rather write it here than&lt;br&gt;
have someone discover it at 2am: once an appliance stores its keys here, it&lt;br&gt;
needs this server reachable at its own boot time to mount encrypted storage.&lt;br&gt;
A NAS that keeps its keys on a VM hosted by that same NAS will not come back&lt;br&gt;
from a reboot. That circular dependency is the classic self-hosted key-server&lt;br&gt;
mistake, and the docs lead with it.&lt;/p&gt;

&lt;p&gt;Two things in 6.4.1 exist specifically to soften the sharp edges: the key&lt;br&gt;
server now has its own row on the server-status page (it was the one sidecar&lt;br&gt;
without one, despite being the one whose outage stops volumes mounting), and it&lt;br&gt;
warns ahead of its own certificate expiry — because an expired client cert&lt;br&gt;
silently ends an appliance's access to its keys, and the failure surfaces at&lt;br&gt;
the next reboot, which is the worst possible moment.&lt;/p&gt;

&lt;p&gt;The rest of the release&lt;/p&gt;

&lt;p&gt;Less glamorous, but honest work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A per-destination minimum severity filter that had never filtered — the
saved values sat in a range below every event's actual priority, so all four
choices behaved identically. It works now, and existing settings keep exactly
the behaviour they had, so nobody's alerting changes without asking.&lt;/li&gt;
&lt;li&gt;Laptops on Windows and macOS report like laptops. Three signals were
Linux-only, which left the laptop features dead on precisely the hosts they
were designed for: a closed lid was treated like a dead server instead of
getting the grace period, and battery health never reached the alerts.&lt;/li&gt;
&lt;li&gt;A UI pass for the small stuff — one body size, one icon size, buttons that
sit in a row, panels that scroll instead of growing without limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it&lt;br&gt;
                                                                                                                                                                                                                    There's a read-only demo seeded with synthetic devices, alerts and CVEs —&lt;br&gt;
no install needed: &lt;a href="https://demoremote.tvipper.com" rel="noopener noreferrer"&gt;https://demoremote.tvipper.com&lt;/a&gt;                                                                                                                                                                   (login demo / demo, resets every few hours).&lt;br&gt;
                                                                                                                                                                                                                    Code and docs: &lt;a href="https://github.com/tyxak/remotepower" rel="noopener noreferrer"&gt;https://github.com/tyxak/remotepower&lt;/a&gt;&lt;br&gt;
                                                                                                                                                                                                                    It's a one-person project and I have no illusions about that. If you've run key&lt;br&gt;
servers in production — or you can poke a hole in the KMIP design — I would                                                                                                                                         genuinely rather hear it from you than from an incident. Issues and discussions&lt;br&gt;
are open.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6veh4r8ebpzqxcrzveby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6veh4r8ebpzqxcrzveby.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>homelab</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Making Windows a first-class citizen in my self-hosted fleet manager (RemotePower 6.2)</title>
      <dc:creator>tyxak</dc:creator>
      <pubDate>Tue, 14 Jul 2026 19:42:51 +0000</pubDate>
      <link>https://dev.to/tyxak/making-windows-a-first-class-citizen-in-my-self-hosted-fleet-manager-remotepower-62-l5i</link>
      <guid>https://dev.to/tyxak/making-windows-a-first-class-citizen-in-my-self-hosted-fleet-manager-remotepower-62-l5i</guid>
      <description>&lt;p&gt;For the last few releases, RemotePower's Windows support was… let's be honest, "kind of there." The agent ran, it reported in, but it did so as a scheduled task that quietly died if the box hiccuped, and half the things that were effortless on Linux needed hand-holding on Windows.&lt;/p&gt;

&lt;p&gt;6.2 — codename "Daem0nMatters" — is the release where I stopped apologizing for that.&lt;/p&gt;

&lt;p&gt;The agent is now a real service, everywhere&lt;/p&gt;

&lt;p&gt;The headline change is boring in the best way: the agent runs as a proper supervised service on every OS it supports.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows — an actual Windows service (via pywin32), registered with the Service Control Manager. It shows up in services.msc, and the SCM restarts it if it crashes. No more scheduled-task-that-silently-stops. (If you've ever fought "Error 1053: the service did not respond in time" — that self-heals now.)&lt;/li&gt;
&lt;li&gt;macOS — a launchd job with KeepAlive.&lt;/li&gt;
&lt;li&gt;Linux — systemd, as before.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical upshot: the agent survives reboots and crashes on its own, on the OS where that was least true before.&lt;/p&gt;

&lt;p&gt;Onboarding a Windows box is now a one-liner&lt;/p&gt;

&lt;p&gt;Just like Linux. You paste one elevated-PowerShell line:&lt;/p&gt;

&lt;p&gt;iwr /install.ps1?t= | iex&lt;/p&gt;

&lt;p&gt;…and it downloads the agent, verifies its SHA-256, installs Python if it's missing (winget on modern Windows, the official python.org silent installer otherwise), enrolls with a one-time token, and registers the service. Nothing else to configure on a bare box.&lt;/p&gt;

&lt;p&gt;It actually understands Windows now&lt;/p&gt;

&lt;p&gt;A Windows host isn't just "a Linux host with worse paths," so the agent stopped pretending. New this release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security posture as first-class checks — BitLocker volume status, Windows Defender real-time protection and signature age, the Windows Firewall per profile, and the Windows Update service all render as Checks rows, drive alerts, and now show at-a-glance in the device drawer. "Real-time protection is off on WEB03" is a thing you find out before it matters.&lt;/li&gt;
&lt;li&gt;A windows_service check type — the analogue of systemd_unit: "is service X running?" via Get-Service.&lt;/li&gt;
&lt;li&gt;Real Event Log reading — a persistent RecordId cursor (so you don't re-ingest or miss events across restarts), the Security channel, and Event IDs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And two genuinely embarrassing bugs got fixed along the way: the self-updater used to report success even when it failed (rc=0 unconditionally), and it resolved helper binaries by bare name — a SYSTEM PATH-hijack waiting to happen. Both closed, and the self-update is signature-checked when you enable signed updates.&lt;/p&gt;

&lt;p&gt;The rest of 6.2, briefly&lt;/p&gt;

&lt;p&gt;Windows was the theme, but it rode in with a batch of gap-closing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A governed AI executor — you can ask the assistant to remediate a host, but it can only pick one action from a catalog you wrote, by exact id. It can't author a command, so there's no prompt-injection path to a shell.&lt;/li&gt;
&lt;li&gt;A PII scan (cards by Luhn, national IDs, IBANs, emails), EDR coverage reporting (cross-references Wazuh/CrowdStrike/SentinelOne against the real fleet to find hosts with no endpoint protection), and DNS-blocker control (pause Pi-hole/AdGuard for a bounded, self-re-enabling window).&lt;/li&gt;
&lt;li&gt;Three new tripwires: privileged-group changes (sudo/wheel/Administrators), Defender-off, and USB physical-access.&lt;/li&gt;
&lt;li&gt;A host-wide disk-usage explorer for when a mount is 94% and you need to know of what, plus a composite device-reliability prediction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the boring-but-important stuff&lt;/p&gt;

&lt;p&gt;It's still self-hosted, MIT-licensed, Python + nginx (gunicorn/Flask), with JSON / SQLite / PostgreSQL backends. Every release goes through CodeQL, Bandit and gitleaks, plus a code-level security review; nothing Critical, High or Medium ships. The 6.2 review is in the repo (&lt;a href="https://github.com/tyxak/remotepower/blob/main/docs/security-review-6.2.0.md" rel="noopener noreferrer"&gt;https://github.com/tyxak/remotepower/blob/main/docs/security-review-6.2.0.md&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If you've been running Linux boxes on it and eyeing the Windows column with suspicion — this is the release to give it another look.&lt;/p&gt;

&lt;p&gt;Repo + docs: &lt;a href="https://github.com/tyxak/remotepower" rel="noopener noreferrer"&gt;https://github.com/tyxak/remotepower&lt;/a&gt;&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>sysadmin</category>
      <category>microsoft</category>
      <category>python</category>
    </item>
    <item>
      <title>Five fixes from a stranger, and the release that finally got a community</title>
      <dc:creator>tyxak</dc:creator>
      <pubDate>Thu, 25 Jun 2026 20:14:16 +0000</pubDate>
      <link>https://dev.to/tyxak/five-fixes-from-a-stranger-and-the-release-that-finally-got-a-community-1nhi</link>
      <guid>https://dev.to/tyxak/five-fixes-from-a-stranger-and-the-release-that-finally-got-a-community-1nhi</guid>
      <description>&lt;p&gt;RemotePower started as a web page with a single button that turned a machine off. I wrote it in Python one afternoon, and then scope creep did the rest, with a lot of help from AI to speed things up (sorry, AI snobs :-)). That one button is now a self-hosted control plane for a whole Linux fleet: monitoring and alerting, a CMDB, CVE scanning, patching, browser-based SSH, Proxmox, drift detection. Underneath it's still deliberately boring — nginx, Python CGI, flat JSON files — and the agents poll out over HTTPS, so there are &lt;strong&gt;no inbound ports open on the clients, ever&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For most of its life it's been a one-person project. The architectural ideas are mine, the code is mostly written by AI, and I'd rather be upfront about that than pretend otherwise.&lt;/p&gt;

&lt;p&gt;Then, last week, something happened that I genuinely didn't expect: someone I've never met opened a pull request. Then another. By the end of the week it was &lt;strong&gt;five fixes and a really sharp bug report&lt;/strong&gt; — all from one first-time contributor, none of whom I know.&lt;/p&gt;

&lt;h2&gt;
  
  
  The release named itself
&lt;/h2&gt;

&lt;p&gt;A little habit I picked up along the way: every RemotePower release gets a codename ending in "Matters." VisualMatters, TrustMatters, FortifyMatters, OnboardingMatters, PerimeterMatters, CTRLMatters. It started as a bit of fun and stuck around, and it turned into a nice forcing function — each release gets one thing to care about most.&lt;/p&gt;

&lt;p&gt;This one named itself. The thing that mattered most about it wasn't a feature I built. It was that, for the first time, the "ours" in "the tool is yours, ours really" had someone else in it. So: &lt;strong&gt;UnityMatters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are three of the fixes that came with it, because honestly they're the good kind of bug — the boring-looking ones that are quietly nasty.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The device record that deleted itself
&lt;/h2&gt;

&lt;p&gt;This is the bug report that made me sit up. A handful of API handlers were doing an unlocked read-modify-write of the device store: load the whole set, change one field, save the whole set back. Classic, and fine until two writes overlap.&lt;/p&gt;

&lt;p&gt;On the flat-JSON backend that's the usual lost-update — a dropped field, annoying but survivable. But on the SQL backend it's worse than that. The save reconciles the &lt;em&gt;entire&lt;/em&gt; device set and deletes any row that isn't in the payload. So picture a slow admin edit that loaded the device list a moment before a brand-new agent enrolled. When that edit saves its now-stale snapshot, the freshly-enrolled device — and its auth token — simply &lt;strong&gt;vanishes&lt;/strong&gt;. The agent's enrollment, gone, because an unrelated edit happened to be in flight.&lt;/p&gt;

&lt;p&gt;The fix is the boring, correct one: every device-write now does its read-modify-write under a single lock (&lt;code&gt;_LockedUpdate(DEVICES_FILE)&lt;/code&gt;), so the load and the save can't be split by another writer. There's a guardrail test now too, so the next person who adds a handler can't quietly reintroduce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The backend that recompiled itself 50,000 times a day
&lt;/h2&gt;

&lt;p&gt;This one's pure "deliberately boring architecture" coming back to bite. RemotePower's backend is a single ~50,000-line Python file, run as a CGI script via fcgiwrap. Turns out CPython &lt;strong&gt;never uses the cached &lt;code&gt;.pyc&lt;/code&gt; for a script you run as the main program&lt;/strong&gt; — it recompiles the source every single time. So every request, even a health check, was paying ~0.9s to recompile fifty thousand lines before doing any work.&lt;/p&gt;

&lt;p&gt;The contributed fix is a four-line shim: a tiny entry script that &lt;em&gt;imports&lt;/em&gt; the big module (so CPython loads its cached bytecode) and runs it as &lt;code&gt;__main__&lt;/code&gt;. The big file is untouched. Per-request time went from &lt;strong&gt;~0.9s to ~0.15s&lt;/strong&gt; — about 6× — for the cost of a file that does basically nothing. My favourite kind of patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Proxmox, but the whole cluster this time
&lt;/h2&gt;

&lt;p&gt;The Proxmox integration only ever asked the one node it was configured against, so on a cluster you saw one node's guests and nothing else. It now lists guests cluster-wide and tags each one with its owning node, and resolves that node per guest — so start/stop/snapshot/migrate hit the right host even for a guest living somewhere else.&lt;/p&gt;

&lt;p&gt;The part I appreciated: node names come back from the cluster, and they flow into request paths. So they're validated as hostnames before they can ever reach a &lt;code&gt;/nodes/&amp;lt;node&amp;gt;/…&lt;/code&gt; URL — a hostile or misconfigured cluster can't steer an authenticated call somewhere it shouldn't go. Someone thought about the threat model, not just the happy path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm writing this down
&lt;/h2&gt;

&lt;p&gt;There's no "buy me a coffee" button on RemotePower. It's MIT licensed, and it stays that way — homelab niceties and enterprise functions, all of it, free. Instead of a tip jar there's a pull request, an issue tracker, and a security workflow. The tool is yours. Ours, really.&lt;/p&gt;

&lt;p&gt;This release is the first time that last sentence had receipts. If you run a Linux fleet or a homelab and you want to kick the tyres, it installs in about five minutes. And if you hit a bug, or have an idea, or just want to say hi — that's all very welcome. Clearly it goes somewhere now. :-)&lt;/p&gt;

&lt;p&gt;Under all the AI, there's still a friendly human being behind this.&lt;/p&gt;

&lt;p&gt;/jake&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RemotePower is self-hosted, MIT-licensed, Linux/Windows/macOS agents, no inbound ports. Repo + install docs: &lt;a href="https://github.com/tyxak/remotepower" rel="noopener noreferrer"&gt;https://github.com/tyxak/remotepower&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>selfhosted</category>
      <category>devops</category>
    </item>
    <item>
      <title>RemotePower – self-hosted remote power management</title>
      <dc:creator>tyxak</dc:creator>
      <pubDate>Fri, 05 Jun 2026 21:20:24 +0000</pubDate>
      <link>https://dev.to/tyxak/remotepower-self-hosted-remote-power-management-2f5h</link>
      <guid>https://dev.to/tyxak/remotepower-self-hosted-remote-power-management-2f5h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuhgrspjxmsntv0b277g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuhgrspjxmsntv0b277g.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built RemotePower because I wanted to power devices on and off across my own infrastructure without depending on a cloud service or some vendor's phone-home dashboard. Everything I run is self-hosted, and remote power was the one annoying gap I kept working around with SSH scripts and cron hacks. So I wrote a proper tool for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The itch
&lt;/h2&gt;

&lt;p&gt;If you run your own hardware, you know the pattern: a box needs rebooting, a device needs cycling, and you're either physically there or you've cobbled together a one-off SSH script that you half-remember how to use. Every commercial answer to this wants you to route your power management through &lt;em&gt;their&lt;/em&gt; cloud, install &lt;em&gt;their&lt;/em&gt; agent that phones home to &lt;em&gt;their&lt;/em&gt; dashboard, and trust that the company still exists next year. For something as sensitive as "turn my machines on and off," that never sat right with me.&lt;/p&gt;

&lt;p&gt;I wanted something I fully own, that runs entirely on infrastructure I control, and that I can read top to bottom when something breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;RemotePower is a polling-agent setup. Clients phone home to a small server, so you don't have to expose anything inbound on the machines you're managing. The agent reaches out; the server never has to reach in. That means no inbound firewall holes on your managed boxes, which is the whole point when the thing you're controlling is power state.&lt;/p&gt;

&lt;p&gt;The flow is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An agent runs on (or alongside) each managed device.&lt;/li&gt;
&lt;li&gt;On an interval, the agent polls the server: "anything for me?"&lt;/li&gt;
&lt;li&gt;The server hands back any queued commands.&lt;/li&gt;
&lt;li&gt;The agent executes and reports back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the agent initiates every connection, the managed machines can sit behind NAT, behind a restrictive firewall, or on a network you don't fully control, and it still works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backend is boring on purpose
&lt;/h2&gt;

&lt;p&gt;This is the part I'm actually proud of. The backend is deliberately, aggressively boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python CGI behind nginx&lt;/strong&gt; — each request is a short-lived process. No long-running app server to leak memory or wedge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat JSON files for storage&lt;/strong&gt; — no database to babysit, no migrations, no connection pool, no separate service to keep alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An HMAC token per device&lt;/strong&gt; for authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes it trivial to deploy on a cheap VPS or a box in your closet, and easy to reason about when something breaks. When your storage layer is &lt;code&gt;cat packages.json&lt;/code&gt;, debugging is a very different experience than chasing a query planner.&lt;/p&gt;

&lt;p&gt;A rough sense of the layout on the server side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;remotepower&lt;/span&gt;/&lt;span class="n"&gt;cgi&lt;/span&gt;-&lt;span class="n"&gt;bin&lt;/span&gt;/   &lt;span class="c"&gt;# the CGI handlers (api.py and friends)
&lt;/span&gt;/&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;lib&lt;/span&gt;/&lt;span class="n"&gt;remotepower&lt;/span&gt;/           &lt;span class="c"&gt;# flat JSON state files
&lt;/span&gt;/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;remotepower&lt;/span&gt;/               &lt;span class="c"&gt;# config
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is flat-file storage the "right" choice at scale? No. But for the scale most self-hosters actually operate at — a handful to a few dozen devices — it removes an entire category of operational pain, and that tradeoff has been worth it every single time so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agent does beyond power
&lt;/h2&gt;

&lt;p&gt;Once you have an agent phoning home anyway, it's a natural place to hang a few more useful things. The Linux agent also does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Package enumeration&lt;/strong&gt; — it reads installed packages via &lt;code&gt;dpkg-query&lt;/code&gt; / &lt;code&gt;rpm&lt;/code&gt; / &lt;code&gt;pacman&lt;/code&gt; / &lt;code&gt;apk&lt;/code&gt;, so the server has an inventory of what's actually on each box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CVE scan against &lt;a href="https://osv.dev/" rel="noopener noreferrer"&gt;OSV.dev&lt;/a&gt;&lt;/strong&gt; — it cross-references those installed packages against known vulnerabilities and surfaces findings per device, with a dashboard view and an ignore-list for the noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Prometheus &lt;code&gt;/metrics&lt;/code&gt; endpoint&lt;/strong&gt; — so if you already run Prometheus + Grafana (and if you're self-hosting, you probably do), you can graph it alongside everything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CVE piece in particular turned a power tool into something I check more often than I expected — knowing which of my boxes are carrying a vulnerable package, without standing up a separate scanner, is genuinely handy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm posting this
&lt;/h2&gt;

&lt;p&gt;It's still very much a personal project that grew. There are rough edges. A few I already know about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The flat-JSON storage works great until it doesn't — there's no row-level locking, so very high concurrency isn't its strength (it's not trying to be).&lt;/li&gt;
&lt;li&gt;CGI-per-request is simple but not the fastest model under heavy polling.&lt;/li&gt;
&lt;li&gt;The auth model (HMAC per device) is solid for the threat model I designed for, but I'd love more eyes on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is really why I'm writing this. I'd genuinely like to hear where the design choices look wrong to people who run more than I do. If you've operated fleets, managed power at scale, or just have strong opinions about polling-vs-push or flat-files-vs-database, I want the pushback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it / tear it apart
&lt;/h2&gt;

&lt;p&gt;The repo is here: &lt;strong&gt;&lt;a href="https://github.com/tyxak/remotepower" rel="noopener noreferrer"&gt;github.com/tyxak/remotepower&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer anything about how it's put together — the architecture decisions, the agent protocol, why CGI in 2026, any of it. And if you spot something I got wrong, that's exactly the feedback I'm here for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci52dg33fryctjaxq5ik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci52dg33fryctjaxq5ik.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75o8kazog5f6mg0q39b6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75o8kazog5f6mg0q39b6.png" alt=" " width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>security</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
