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