<?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>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>
