<?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: Alex Kernel</title>
    <description>The latest articles on DEV Community by Alex Kernel (@bitwiserokos).</description>
    <link>https://dev.to/bitwiserokos</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%2F3671226%2Fadc178bd-d32a-4bea-8fea-ef1b3c9980aa.png</url>
      <title>DEV Community: Alex Kernel</title>
      <link>https://dev.to/bitwiserokos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bitwiserokos"/>
    <language>en</language>
    <item>
      <title>7 Ways to Run a Server Security Audit Across a Fleet in Minutes (2026)</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Thu, 18 Jun 2026 14:20:47 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/7-ways-to-run-a-server-security-audit-across-a-fleet-in-minutes-2026-bfc</link>
      <guid>https://dev.to/bitwiserokos/7-ways-to-run-a-server-security-audit-across-a-fleet-in-minutes-2026-bfc</guid>
      <description>&lt;p&gt;Running a &lt;strong&gt;server security audit across a fleet&lt;/strong&gt; by hand is miserable: you open&lt;br&gt;
twenty SSH sessions, paste the same &lt;code&gt;grep&lt;/code&gt; into each, copy results into a&lt;br&gt;
spreadsheet, and pray you didn't fat-finger a &lt;code&gt;rm&lt;/code&gt; on a production box at 2 a.m.&lt;br&gt;
This guide shows you how to sweep an entire mixed fleet — web, API and database&lt;br&gt;
hosts across several OS families — for security problems in a single pass, in&lt;br&gt;
&lt;strong&gt;strict read-only mode&lt;/strong&gt;, using the open-source MCP server&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/remote-agents" rel="noopener noreferrer"&gt;&lt;code&gt;remote-agents&lt;/code&gt;&lt;/a&gt;. The whole point&lt;br&gt;
is that an audit literally cannot change anything: every host starts in &lt;code&gt;plan&lt;/code&gt;&lt;br&gt;
mode, so a compliance or incident sweep is safe by construction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; put the whole fleet into read-only &lt;code&gt;plan&lt;/code&gt; mode with &lt;code&gt;set_mode&lt;/code&gt;,&lt;br&gt;
then fan out one audit at a time — &lt;code&gt;fleet_exec&lt;/code&gt;, &lt;code&gt;fleet_read&lt;/code&gt;, &lt;code&gt;fleet_search&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;mapreduce&lt;/code&gt; — to check SSH hardening, file permissions, leaked secrets,&lt;br&gt;
brute-force IPs and pending patches. Payloads are end-to-end encrypted&lt;br&gt;
(AES-GCM-256); the relay only ever sees ciphertext, and a hard denylist for&lt;br&gt;
paths like &lt;code&gt;/etc/shadow&lt;/code&gt; holds even in bypass mode.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Architecture: ~20 hosts, one room&lt;/li&gt;
&lt;li&gt;Installing agents and tagging the fleet&lt;/li&gt;
&lt;li&gt;Why and when to run a fleet-wide audit&lt;/li&gt;
&lt;li&gt;Quick summary table&lt;/li&gt;
&lt;li&gt;Recipe 1 — Put the fleet in read-only &lt;code&gt;plan&lt;/code&gt; mode&lt;/li&gt;
&lt;li&gt;Recipe 2 — Find world-writable files and bad permissions&lt;/li&gt;
&lt;li&gt;Recipe 3 — Audit SSH hardening across the fleet&lt;/li&gt;
&lt;li&gt;Recipe 4 — Scan for leaked secrets and keys by content&lt;/li&gt;
&lt;li&gt;Recipe 5 — Aggregate brute-force IPs with MapReduce&lt;/li&gt;
&lt;li&gt;Recipe 6 — Check for pending security updates&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture: ~20 hosts, one room
&lt;/h2&gt;

&lt;p&gt;Let's take a realistic mid-size production fleet — about twenty hosts split into&lt;br&gt;
web, API and database tiers, with a couple of different OS families thrown in&lt;br&gt;
because real fleets are never homogeneous. Every node runs a lightweight agent&lt;br&gt;
that connects &lt;strong&gt;outbound&lt;/strong&gt; to one encrypted relay room. The AI assistant (Claude&lt;br&gt;
or opencode) sees the whole fleet as a single computer and addresses groups of&lt;br&gt;
hosts by tag or OS family.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;web-1&lt;/code&gt;, &lt;code&gt;web-2&lt;/code&gt; …&lt;/td&gt;
&lt;td&gt;Public web / reverse proxy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;nginx, Ubuntu 22.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;api-1&lt;/code&gt;, &lt;code&gt;api-2&lt;/code&gt; …&lt;/td&gt;
&lt;td&gt;Application backends&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node.js, Debian 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;db-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL, Rocky Linux 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(mixed)&lt;/td&gt;
&lt;td&gt;Edge / build boxes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;os:macos&lt;/code&gt;, &lt;code&gt;os:windows&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;macOS / Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The magic is &lt;strong&gt;targeting&lt;/strong&gt;. A single operation can hit the whole fleet&lt;br&gt;
(&lt;code&gt;target="all"&lt;/code&gt;), one tier (&lt;code&gt;target="web"&lt;/code&gt; or &lt;code&gt;target="api,db"&lt;/code&gt;), or one OS&lt;br&gt;
family (&lt;code&gt;target="os:linux"&lt;/code&gt;). That means a 20-host audit is &lt;em&gt;one&lt;/em&gt; tool call, not&lt;br&gt;
twenty SSH sessions — and results come back aggregated &lt;strong&gt;per host&lt;/strong&gt;, so one&lt;br&gt;
unreachable box never sinks the whole batch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            ┌──────────────── AI (Claude / opencode) ────────────────┐
            │              remote-agents (MCP, stdio)                 │
            └────────────────────────┬────────────────────────────────┘
                                     │ wss:// (E2E AES-GCM-256)
                             ┌───────┴────────┐  relay (CF Worker or self-host)
                             │   room=fleet   │  (forwards ciphertext only)
        ┌──────────┬─────────┼──────────┬──────────────┬───────────┐
     web-1 web-2  api-1 api-2 …        db-1          mac/win edge
      (web)        (api)               (db)         (os:macos/windows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing agents and tagging the fleet
&lt;/h2&gt;

&lt;p&gt;On each host you install one Rust binary and start an agent with the right tag.&lt;br&gt;
Tags are what make tier-wide audits possible, so be deliberate about them.&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="c"&gt;# once on every machine&lt;/span&gt;
npm i &lt;span class="nt"&gt;-g&lt;/span&gt; remote-agents

&lt;span class="c"&gt;# web tier&lt;/span&gt;
remote-agents run &lt;span class="nt"&gt;--relay&lt;/span&gt; wss://&amp;lt;relay&amp;gt; &lt;span class="nt"&gt;--room&lt;/span&gt; fleet &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;secret&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; web-1 &lt;span class="nt"&gt;--tags&lt;/span&gt; web
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; web-2 &lt;span class="nt"&gt;--tags&lt;/span&gt; web

&lt;span class="c"&gt;# api tier&lt;/span&gt;
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; api-1 &lt;span class="nt"&gt;--tags&lt;/span&gt; api
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; api-2 &lt;span class="nt"&gt;--tags&lt;/span&gt; api

&lt;span class="c"&gt;# database&lt;/span&gt;
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; db-1 &lt;span class="nt"&gt;--tags&lt;/span&gt; db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For 24/7 hosts, use &lt;code&gt;remote-agents install&lt;/code&gt; instead of &lt;code&gt;run&lt;/code&gt; — it registers a&lt;br&gt;
background service (systemd on Linux, launchd on macOS) so the agent survives&lt;br&gt;
reboots. To confirm the whole fleet is online, ask the AI "show me the agents in&lt;br&gt;
the room"; under the hood that calls &lt;strong&gt;&lt;code&gt;list_agents&lt;/code&gt;&lt;/strong&gt;, which returns each peer's&lt;br&gt;
OS family, distro, kernel, shell, tags and an &lt;code&gt;update_available&lt;/code&gt; flag.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The network is a flat &lt;strong&gt;peer&lt;/strong&gt; mesh — there is no controller/agent&lt;br&gt;
split. Your local machine joins as an equal peer and dispatches work. If you&lt;br&gt;
want a send-only controller that never executes commands itself, start it with&lt;br&gt;
&lt;code&gt;--no-agent&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why and when to run a fleet-wide audit
&lt;/h2&gt;

&lt;p&gt;A fleet-wide security sweep is the kind of task that &lt;em&gt;should&lt;/em&gt; be routine but&lt;br&gt;
rarely is, because doing it manually scales linearly with host count. Here is&lt;br&gt;
when this approach pays off the most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compliance &amp;amp; audit windows.&lt;/strong&gt; SOC 2, ISO 27001 and PCI all want evidence
that SSH is hardened, permissions are sane and patches are current — across
&lt;em&gt;every&lt;/em&gt; host, not a sample. Read-only &lt;code&gt;plan&lt;/code&gt; mode produces that evidence
without any chance of mutating the systems under review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident response.&lt;/strong&gt; After a suspected breach you need to know &lt;em&gt;right now&lt;/em&gt;
which hosts have world-writable files, leaked keys on disk, or a spike of
failed logins. Twenty parallel greps beat twenty serial SSH sessions when the
clock is ticking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift detection.&lt;/strong&gt; Configs rot. A host that was hardened six months ago may
have had &lt;code&gt;PasswordAuthentication&lt;/code&gt; flipped back during a debugging session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding new servers.&lt;/strong&gt; When you absorb a fleet you didn't build, a single
audit pass tells you what you actually inherited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform reality.&lt;/strong&gt; Real fleets mix Linux, macOS and Windows.
&lt;code&gt;os:&amp;lt;family&amp;gt;&lt;/code&gt; targeting lets one audit branch correctly per platform instead
of failing half the hosts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The recurring theme: &lt;strong&gt;you want to look, not touch.&lt;/strong&gt; Everything below is built&lt;br&gt;
around that guarantee.&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick summary table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Recipe&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Read-only &lt;code&gt;plan&lt;/code&gt; mode&lt;/td&gt;
&lt;td&gt;Locks the whole fleet so the audit &lt;em&gt;cannot&lt;/em&gt; write anything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;World-writable &amp;amp; bad perms&lt;/td&gt;
&lt;td&gt;Finds &lt;code&gt;0002&lt;/code&gt;-perm files and risky setuid binaries on every host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;SSH hardening sweep&lt;/td&gt;
&lt;td&gt;Checks &lt;code&gt;PermitRootLogin&lt;/code&gt; / &lt;code&gt;PasswordAuthentication&lt;/code&gt; fleet-wide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Secret scanning&lt;/td&gt;
&lt;td&gt;Greps file &lt;strong&gt;content&lt;/strong&gt; for AWS keys and private keys across hosts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Brute-force IP ranking&lt;/td&gt;
&lt;td&gt;MapReduce over &lt;code&gt;auth.log&lt;/code&gt; to rank top attacking IPs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Pending security updates&lt;/td&gt;
&lt;td&gt;Lists upgradable packages per OS family (&lt;code&gt;apt&lt;/code&gt; / &lt;code&gt;dnf&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 1 — Put the fleet in read-only &lt;code&gt;plan&lt;/code&gt; mode
&lt;/h2&gt;

&lt;p&gt;Before you run a single audit command, lock the fleet down. Each agent has a&lt;br&gt;
&lt;strong&gt;safety mode&lt;/strong&gt;, and &lt;code&gt;plan&lt;/code&gt; allows only safe reads — &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;git_status&lt;/code&gt;&lt;br&gt;
and non-mutating &lt;code&gt;exec&lt;/code&gt;. Writes, overwrites and risky operations are simply&lt;br&gt;
rejected. This is the foundation that makes a security audit safe for compliance&lt;br&gt;
and incident work: a host in &lt;code&gt;plan&lt;/code&gt; mode physically cannot be changed by the&lt;br&gt;
sweep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Switch every host to &lt;code&gt;plan&lt;/code&gt;.&lt;/strong&gt; You set the mode per host with&lt;br&gt;
&lt;strong&gt;&lt;code&gt;set_mode&lt;/code&gt;&lt;/strong&gt;. For a fleet, loop it over each agent (or ask the AI to apply it&lt;br&gt;
to all of them):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_mode  agent_id=web-1  mode=plan
set_mode  agent_id=web-2  mode=plan
set_mode  agent_id=api-1  mode=plan
set_mode  agent_id=api-2  mode=plan
set_mode  agent_id=db-1   mode=plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2. Confirm the lock with &lt;code&gt;get_info&lt;/code&gt;.&lt;/strong&gt; Don't take it on faith — read the&lt;br&gt;
mode back from each host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get_info  agent_id=web-1
get_info  agent_id=db-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;get_info&lt;/code&gt; response reports the active mode, so you have proof every host is&lt;br&gt;
read-only before the audit starts. That single field is exactly the kind of&lt;br&gt;
artifact an auditor wants to see captured at the top of an evidence log.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Even if a host were in &lt;code&gt;bypass&lt;/code&gt; mode, a &lt;strong&gt;hard denylist&lt;/strong&gt; for&lt;br&gt;
sensitive paths like &lt;code&gt;/etc/shadow&lt;/code&gt; and &lt;code&gt;/boot&lt;/code&gt; still applies — those reads and&lt;br&gt;
writes are refused unconditionally. &lt;code&gt;plan&lt;/code&gt; mode is your first guardrail; the&lt;br&gt;
denylist is the one that never comes off.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 2 — Find world-writable files and bad permissions
&lt;/h2&gt;

&lt;p&gt;World-writable files are a classic privilege-escalation vector: any local user&lt;br&gt;
can overwrite them, and if one happens to be a script run by root, you have a&lt;br&gt;
problem. Auditing this by hand across twenty hosts is exactly the kind of busywork&lt;br&gt;
that gets skipped — so let's do all of it in one command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Hunt for world-writable files fleet-wide.&lt;/strong&gt; Use &lt;strong&gt;&lt;code&gt;fleet_exec&lt;/code&gt;&lt;/strong&gt; with&lt;br&gt;
&lt;code&gt;target="all"&lt;/code&gt;. The &lt;code&gt;find&lt;/code&gt; expression &lt;code&gt;-perm -0002&lt;/code&gt; matches anything with the&lt;br&gt;
world-writable bit set; we prune the noisy virtual filesystems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="all"
            command="find / -xdev -type f -perm -0002 -not -path '/proc/*' -not -path '/sys/*' 2&amp;gt;/dev/null | head -n 50"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results come back &lt;strong&gt;per host&lt;/strong&gt;, so you immediately see that, say, &lt;code&gt;web-2&lt;/code&gt; has a&lt;br&gt;
stray world-writable file in &lt;code&gt;/var/www&lt;/code&gt; while every other host is clean. One&lt;br&gt;
slow or offline host doesn't block the rest of the batch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Audit setuid/setgid binaries.&lt;/strong&gt; Unexpected setuid binaries are another&lt;br&gt;
red flag. Same fan-out, different predicate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="all"
            command="find / -xdev -perm -4000 -o -perm -2000 -type f 2&amp;gt;/dev/null | sort"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3. Spot-check sensitive directories.&lt;/strong&gt; For example, confirm that no SSH&lt;br&gt;
private keys are group/world-readable:&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="c"&gt;# what fleet_exec runs on each host&lt;/span&gt;
find /home /root &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'id_*'&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.pub'&lt;/span&gt; &lt;span class="nt"&gt;-perm&lt;/span&gt; /0077 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Add &lt;code&gt;-xdev&lt;/code&gt; (shown above) to keep &lt;code&gt;find&lt;/code&gt; from descending into&lt;br&gt;
network mounts and bind mounts — otherwise an NFS share can make the audit&lt;br&gt;
crawl on every host that mounts it. Cap output with &lt;code&gt;head&lt;/code&gt; so a misbehaving&lt;br&gt;
box can't flood the per-host result back through the relay.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On macOS hosts the same &lt;code&gt;find&lt;/code&gt; works but paths differ (&lt;code&gt;/Users&lt;/code&gt; instead of&lt;br&gt;
&lt;code&gt;/home&lt;/code&gt;); on Windows you'd target &lt;code&gt;os:windows&lt;/code&gt; separately with a PowerShell ACL&lt;br&gt;
check rather than &lt;code&gt;find&lt;/code&gt;. This is why OS-family targeting matters — more on that&lt;br&gt;
in Recipe 6.&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 3 — Audit SSH hardening across the fleet
&lt;/h2&gt;

&lt;p&gt;SSH is the front door, so its config is the highest-value thing to audit. The two&lt;br&gt;
settings that bite people most often are &lt;code&gt;PermitRootLogin&lt;/code&gt; (should be &lt;code&gt;no&lt;/code&gt; or&lt;br&gt;
&lt;code&gt;prohibit-password&lt;/code&gt;) and &lt;code&gt;PasswordAuthentication&lt;/code&gt; (should be &lt;code&gt;no&lt;/code&gt; if you're using&lt;br&gt;
keys). Drift here is silent and dangerous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Read the raw config from every host.&lt;/strong&gt; Use &lt;strong&gt;&lt;code&gt;fleet_read&lt;/code&gt;&lt;/strong&gt; to pull&lt;br&gt;
&lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; across the fleet in one shot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_read  target="os:linux"  path=/etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get the full file back per host, which is great for archival evidence. But&lt;br&gt;
for a quick pass/fail you usually want just the relevant lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Extract just the hardening-relevant directives.&lt;/strong&gt; Switch to&lt;br&gt;
&lt;strong&gt;&lt;code&gt;fleet_exec&lt;/code&gt;&lt;/strong&gt; and grep the effective values, ignoring comments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="os:linux"
            command="grep -Ei '^\s*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|X11Forwarding|PermitEmptyPasswords)' /etc/ssh/sshd_config || echo 'NONE SET (defaults apply)'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The per-host output makes outliers jump out: if &lt;code&gt;api-2&lt;/code&gt; reports&lt;br&gt;
&lt;code&gt;PasswordAuthentication yes&lt;/code&gt; while the rest report &lt;code&gt;no&lt;/code&gt;, you've found your drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Check the &lt;em&gt;running&lt;/em&gt; config, not just the file.&lt;/strong&gt; Includes and drop-in&lt;br&gt;
&lt;code&gt;sshd_config.d/&lt;/code&gt; files can override the main file, so verify what &lt;code&gt;sshd&lt;/code&gt; actually&lt;br&gt;
loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="os:linux"
            command="sshd -T 2&amp;gt;/dev/null | grep -Ei 'permitrootlogin|passwordauthentication|pubkeyauthentication'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;sshd -T&lt;/code&gt; prints the &lt;em&gt;fully resolved&lt;/em&gt; effective configuration —&lt;br&gt;
including drop-ins under &lt;code&gt;/etc/ssh/sshd_config.d/&lt;/code&gt;. Auditing the main file&lt;br&gt;
alone can miss an override that re-enables password auth. Because everything is&lt;br&gt;
read-only, this is a perfectly safe command to run in &lt;code&gt;plan&lt;/code&gt; mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On macOS, SSH config lives at the same path but is managed through&lt;br&gt;
&lt;code&gt;systemsetup -getremotelogin&lt;/code&gt;; remember to scope macOS hosts separately rather&lt;br&gt;
than assuming the Linux command applies everywhere.&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 4 — Scan for leaked secrets and keys by content
&lt;/h2&gt;

&lt;p&gt;Secrets end up on disk far more often than anyone admits: an AWS key pasted into&lt;br&gt;
a &lt;code&gt;.env&lt;/code&gt;, a private key copied to the wrong host during a migration, a token in a&lt;br&gt;
forgotten shell history. The right tool here searches by &lt;strong&gt;content&lt;/strong&gt;, not just&lt;br&gt;
filename, because attackers don't name files &lt;code&gt;aws-key.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Search the fleet's content for AWS access keys.&lt;/strong&gt; Use&lt;br&gt;
&lt;strong&gt;&lt;code&gt;fleet_search&lt;/code&gt;&lt;/strong&gt; — it searches each host's files by content. AWS access key IDs&lt;br&gt;
match a recognizable pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_search  target="all"
              content="AKIA[0-9A-Z]{16}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each match comes back with the host, file path and surrounding context, so you&lt;br&gt;
know exactly which box and which file to rotate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Hunt for private key material.&lt;/strong&gt; PEM-encoded private keys all start&lt;br&gt;
with a tell-tale header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_search  target="all"
              content="BEGIN (RSA|OPENSSH|EC|DSA) PRIVATE KEY"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3. Catch generic high-entropy tokens.&lt;/strong&gt; For things like&lt;br&gt;
&lt;code&gt;password=&lt;/code&gt;, &lt;code&gt;secret_key&lt;/code&gt;, or bearer tokens, a looser content search across the&lt;br&gt;
fleet surfaces candidates worth a human review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_search  target="all"
              content="(api[_-]?key|secret|token|password)\s*[:=]"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fleet_search&lt;/code&gt; defaults to scanning sensible roots (home plus&lt;br&gt;
&lt;code&gt;Documents&lt;/code&gt;/&lt;code&gt;Downloads&lt;/code&gt;/&lt;code&gt;Desktop&lt;/code&gt;/&lt;code&gt;Pictures&lt;/code&gt;), which is where stray credentials&lt;br&gt;
usually land. For deployment configs under &lt;code&gt;/etc&lt;/code&gt; or &lt;code&gt;/srv&lt;/code&gt;, fall back to&lt;br&gt;
&lt;code&gt;fleet_exec&lt;/code&gt; with &lt;code&gt;grep -rIE&lt;/code&gt; scoped to those directories.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Treat the audit output itself as sensitive — it now contains the&lt;br&gt;
very secrets you were hunting. The relay never sees plaintext (payloads are&lt;br&gt;
AES-GCM-256 encrypted end-to-end and the relay forwards only ciphertext), but&lt;br&gt;
the results land in your AI chat transcript. Rotate any key the scan finds; a&lt;br&gt;
leaked key on disk should be considered compromised the moment you discover it.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 5 — Aggregate brute-force IPs with MapReduce
&lt;/h2&gt;

&lt;p&gt;Individual hosts each see their own slice of a brute-force campaign. The&lt;br&gt;
interesting question — &lt;em&gt;which IPs are hammering the **whole fleet&lt;/em&gt;&lt;em&gt;?&lt;/em&gt; — needs&lt;br&gt;
cross-host aggregation. That's exactly what &lt;strong&gt;&lt;code&gt;mapreduce&lt;/code&gt;&lt;/strong&gt; is for: it partitions&lt;br&gt;
the data across the fleet, runs a &lt;code&gt;map_fn&lt;/code&gt; per partition, then folds everything&lt;br&gt;
together with a &lt;code&gt;reduce_fn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Map failed-password IPs per host.&lt;/strong&gt; Each partition is a host's auth&lt;br&gt;
log; the map step extracts the offending IPs and counts them locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapreduce
  data=["web-1:/var/log/auth.log", "web-2:/var/log/auth.log", "api-1:/var/log/auth.log", "api-2:/var/log/auth.log", "db-1:/var/log/secure"]
  map_fn="grep 'Failed password' \"$(cut -d: -f2)\" | grep -oE 'from [0-9.]+' | awk '{print $2}' | sort | uniq -c"
  reduce_fn="awk '{c[$2]+=$1} END {for (ip in c) print c[ip], ip}' | sort -rn | head -n 20"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The map step does the heavy per-host counting in parallel; the reduce step merges&lt;br&gt;
those partial counts and ranks the &lt;strong&gt;top 20 attacking IPs across the entire&lt;br&gt;
fleet&lt;/strong&gt; — a single ranked list instead of five logs you'd otherwise correlate by&lt;br&gt;
hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Make the run resilient.&lt;/strong&gt; Add &lt;code&gt;max_retries&lt;/code&gt; so a momentarily&lt;br&gt;
unreachable host doesn't blow up the whole report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapreduce
  data=[ ... same as above ... ]
  map_fn="..."
  reduce_fn="..."
  max_retries=3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failed partitions are automatically re-dispatched up to the retry limit, so a&lt;br&gt;
box that hiccups gets a second (and third) chance rather than leaving a hole in&lt;br&gt;
your data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Different distros log SSH failures to different files —&lt;br&gt;
Debian/Ubuntu use &lt;code&gt;/var/log/auth.log&lt;/code&gt;, while RHEL/Rocky/Fedora use&lt;br&gt;
&lt;code&gt;/var/log/secure&lt;/code&gt;. Note how &lt;code&gt;db-1&lt;/code&gt; points at &lt;code&gt;/var/log/secure&lt;/code&gt; above. If your&lt;br&gt;
hosts use &lt;code&gt;systemd-journald&lt;/code&gt; with no flat file, swap the map command's &lt;code&gt;grep&lt;/code&gt;&lt;br&gt;
for &lt;code&gt;journalctl _COMM=sshd | grep 'Failed password'&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have the ranked list, feeding the worst offenders into &lt;code&gt;fail2ban&lt;/code&gt; or a&lt;br&gt;
firewall denylist is a natural follow-up — but that's a &lt;em&gt;write&lt;/em&gt;, so you'd&lt;br&gt;
deliberately move the relevant host to &lt;code&gt;edit&lt;/code&gt; mode first.&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 6 — Check for pending security updates
&lt;/h2&gt;

&lt;p&gt;Unpatched packages are the most common breach vector there is, and "are we&lt;br&gt;
patched?" should be answerable in one command. The catch is that the &lt;em&gt;answer&lt;/em&gt;&lt;br&gt;
differs by OS, so this recipe leans hard on &lt;strong&gt;&lt;code&gt;os:&amp;lt;family&amp;gt;&lt;/code&gt; targeting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. List upgradable packages on Debian/Ubuntu hosts.&lt;/strong&gt; Scope to the&lt;br&gt;
relevant family and ask &lt;code&gt;apt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="os:linux"
            command="command -v apt &amp;gt;/dev/null &amp;amp;&amp;amp; apt list --upgradable 2&amp;gt;/dev/null | grep -i security || true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2. Check RHEL-family hosts for security errata.&lt;/strong&gt; &lt;code&gt;dnf&lt;/code&gt; has a dedicated&lt;br&gt;
security view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="os:linux"
            command="command -v dnf &amp;gt;/dev/null &amp;amp;&amp;amp; dnf updateinfo list security 2&amp;gt;/dev/null || true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guarding each command with &lt;code&gt;command -v&lt;/code&gt; means a single &lt;code&gt;target="os:linux"&lt;/code&gt; call&lt;br&gt;
handles both Debian and RHEL hosts gracefully — the wrong tool simply no-ops&lt;br&gt;
instead of erroring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Don't forget non-Linux hosts.&lt;/strong&gt; macOS and Windows need entirely&lt;br&gt;
different commands, so target them separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="os:macos"    command="softwareupdate -l"
fleet_exec  target="os:windows"  command="powershell -Command \"Get-WindowsUpdate\""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is a read-only &lt;em&gt;check&lt;/em&gt; — none of the commands above install&lt;br&gt;
anything, so they're safe in &lt;code&gt;plan&lt;/code&gt; mode. When you're ready to actually&lt;br&gt;
patch, that's a mutating operation: move the target hosts to &lt;code&gt;edit&lt;/code&gt; (or&lt;br&gt;
&lt;code&gt;bypass&lt;/code&gt;) mode first, then run the upgrade. To find hosts running an outdated&lt;br&gt;
&lt;em&gt;agent&lt;/em&gt; binary, there's a dedicated tool — &lt;code&gt;fleet_update_check&lt;/code&gt; — which flags&lt;br&gt;
idle peers with a newer version available, after which you run&lt;br&gt;
&lt;code&gt;npm i -g remote-agents@latest&lt;/code&gt; on them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That cross-platform split is the whole reason &lt;code&gt;os:&lt;/code&gt; targeting exists: one logical&lt;br&gt;
audit ("are we patched?") branches into the right command per platform instead of&lt;br&gt;
failing on every host where the assumption doesn't hold.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is it really safe to run a security audit on production?
&lt;/h3&gt;

&lt;p&gt;Yes — that's the entire design goal. You start every host in &lt;strong&gt;&lt;code&gt;plan&lt;/code&gt;&lt;/strong&gt; (read-only)&lt;br&gt;
mode and confirm it with &lt;code&gt;get_info&lt;/code&gt;, so the audit physically cannot write,&lt;br&gt;
overwrite or delete anything. On top of that, a hard denylist for paths like&lt;br&gt;
&lt;code&gt;/etc/shadow&lt;/code&gt; and &lt;code&gt;/boot&lt;/code&gt; applies even in &lt;code&gt;bypass&lt;/code&gt; mode, and all command payloads&lt;br&gt;
and results are end-to-end encrypted, with the relay forwarding only ciphertext.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many servers can I audit in one command?
&lt;/h3&gt;

&lt;p&gt;There's no fixed cap — &lt;code&gt;fleet_exec&lt;/code&gt;, &lt;code&gt;fleet_read&lt;/code&gt;, &lt;code&gt;fleet_search&lt;/code&gt; and &lt;code&gt;mapreduce&lt;/code&gt;&lt;br&gt;
all fan out to as many hosts as match your target. A 20-host fleet is one&lt;br&gt;
command instead of twenty SSH sessions, and results are aggregated &lt;strong&gt;per host&lt;/strong&gt;&lt;br&gt;
so you can read them like a report. One unreachable or slow host doesn't sink the&lt;br&gt;
batch; with &lt;code&gt;mapreduce&lt;/code&gt; you also get &lt;code&gt;max_retries&lt;/code&gt; for transient failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is this different from a dedicated scanner like OpenSCAP or Lynis?
&lt;/h3&gt;

&lt;p&gt;It's complementary, not a replacement. Tools like Lynis and OpenSCAP run a fixed,&lt;br&gt;
standardized benchmark on a single host. &lt;code&gt;remote-agents&lt;/code&gt; is an &lt;em&gt;interactive&lt;/em&gt;,&lt;br&gt;
AI-driven layer that lets you run &lt;strong&gt;any&lt;/strong&gt; check — including those scanners&lt;br&gt;
themselves — across a whole fleet at once, correlate results, and pivot&lt;br&gt;
instantly ("now show me which of those hosts also has password auth enabled").&lt;br&gt;
You can even &lt;code&gt;fleet_exec&lt;/code&gt; &lt;code&gt;lynis audit system&lt;/code&gt; everywhere and aggregate the&lt;br&gt;
scores.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the relay or any cloud service see my data?
&lt;/h3&gt;

&lt;p&gt;No. Payloads are encrypted with AES-GCM-256 using a key derived from your room&lt;br&gt;
token, and the relay — whether the Cloudflare Worker or a self-hosted Rust relay&lt;br&gt;
(&lt;code&gt;remote-agents-relay&lt;/code&gt;) — only ever forwards opaque ciphertext. You can run the&lt;br&gt;
relay entirely on your own infrastructure and point agents at &lt;code&gt;ws://your-host:8080&lt;/code&gt;,&lt;br&gt;
so nothing leaves your environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I schedule the audit to run automatically?
&lt;/h3&gt;

&lt;p&gt;Yes. Use &lt;code&gt;schedule_add&lt;/code&gt; to register a 6-field cron job (&lt;code&gt;sec min hour day month&lt;br&gt;
dow&lt;/code&gt;) directly on a host; it runs &lt;strong&gt;on the agent itself&lt;/strong&gt;, so it keeps firing&lt;br&gt;
even if the relay link drops. A nightly world-writable-file check or weekly SSH&lt;br&gt;
config diff is a natural fit.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A thorough &lt;strong&gt;server security audit across a fleet&lt;/strong&gt; doesn't have to mean a day of&lt;br&gt;
SSH tedium. With every host locked into read-only &lt;code&gt;plan&lt;/code&gt; mode, one operator can&lt;br&gt;
sweep twenty machines for world-writable files, SSH-hardening drift, leaked&lt;br&gt;
secrets, brute-force IPs and missing patches — each in a single command, with&lt;br&gt;
results aggregated per host and the worst offenders ranked automatically by&lt;br&gt;
MapReduce. Because the work happens in &lt;code&gt;plan&lt;/code&gt; mode behind a hard denylist and&lt;br&gt;
end-to-end encryption, the same sweep that powers your incident response also&lt;br&gt;
doubles as clean, repeatable compliance evidence — without ever risking the&lt;br&gt;
systems under review.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Install: &lt;code&gt;npm i -g remote-agents&lt;/code&gt; →&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/remote-agents" rel="noopener noreferrer"&gt;package on npm&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://github.com/47-ronn/tunshell_mcp_agents" rel="noopener noreferrer"&gt;source &amp;amp; documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>claude</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>Homelab Fleet Management with AI: 7 remote-agents Recipes (2026)</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Thu, 18 Jun 2026 14:17:26 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/homelab-fleet-management-with-ai-7-remote-agents-recipes-2026-hb2</link>
      <guid>https://dev.to/bitwiserokos/homelab-fleet-management-with-ai-7-remote-agents-recipes-2026-hb2</guid>
      <description>&lt;p&gt;If your idea of &lt;strong&gt;homelab fleet management&lt;/strong&gt; is currently five terminal tabs, a sticky note with IP addresses, and the dawning horror of remembering which Pi runs &lt;code&gt;apt&lt;/code&gt; and which runs &lt;code&gt;dnf&lt;/code&gt; — this guide is for you. We'll wire up a real mixed-architecture homelab (NAS, two Raspberry Pis, a Docker media box, and your desktop) so you can drive the whole thing from &lt;strong&gt;one AI chat&lt;/strong&gt; instead of hopping between SSH sessions. The engine is the open-source MCP server &lt;a href="https://www.npmjs.com/package/remote-agents" rel="noopener noreferrer"&gt;&lt;code&gt;remote-agents&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; a lightweight agent runs on every machine and dials &lt;em&gt;outbound&lt;/em&gt; into an encrypted relay room. Your AI assistant (Claude or opencode) sees the whole homelab as one computer and calls tools like &lt;code&gt;fleet_exec&lt;/code&gt;, &lt;code&gt;fleet_read&lt;/code&gt;, &lt;code&gt;schedule_add&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, and &lt;code&gt;set_mode&lt;/code&gt;. Command payloads are end-to-end encrypted (AES-GCM-256) — the relay only ever forwards ciphertext, never plaintext, never keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Architecture: five hosts, one room&lt;/li&gt;
&lt;li&gt;Recipe 1 — Install agents and tag the homelab&lt;/li&gt;
&lt;li&gt;Recipe 2 — One-command system updates across every Linux host&lt;/li&gt;
&lt;li&gt;Recipe 3 — Nightly backups to the NAS that survive a relay outage&lt;/li&gt;
&lt;li&gt;Recipe 4 — Whole-fleet health, disk, and temperature sweep&lt;/li&gt;
&lt;li&gt;Recipe 5 — Docker management on the media host&lt;/li&gt;
&lt;li&gt;Recipe 6 — Safe read-only inspection with plan mode and the denylist&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: five hosts, one room
&lt;/h2&gt;

&lt;p&gt;A homelab is rarely uniform. You've got x86 boxes, ARM boards, mostly Linux with maybe a stray macOS mini, and a wild mix of stacks. That heterogeneity is exactly what makes manual &lt;strong&gt;self-hosted server management&lt;/strong&gt; painful — every host wants a slightly different incantation. Tags and OS-family targeting smooth that over: you address a &lt;em&gt;group&lt;/em&gt; of machines in a single call, and the failures stay per-host instead of sinking the whole batch.&lt;/p&gt;

&lt;p&gt;Here's the topology we'll use for the rest of this guide:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Storage, backup target, shares&lt;/td&gt;
&lt;td&gt;&lt;code&gt;storage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TrueNAS / Linux, ZFS, NFS/SMB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pi-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DNS / ad-block, light services&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raspberry Pi (ARM64), Debian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pi-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensors / home automation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raspberry Pi (ARM64), Debian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;media&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jellyfin / Plex, Docker host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;media&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x86 Linux, Docker Compose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;desk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your main desktop / workstation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;workstation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;x86 Linux (or macOS mini)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every agent joins the same relay room (say, &lt;code&gt;homelab&lt;/code&gt;). It's a &lt;strong&gt;flat peer network&lt;/strong&gt; — there's no controller node and no agent node, every machine joins as an equal peer that can both execute and dispatch. Tags let you fan out: &lt;code&gt;target="pi"&lt;/code&gt; hits both Raspberry Pis, &lt;code&gt;target="os:linux"&lt;/code&gt; hits every Linux box, and &lt;code&gt;target="all"&lt;/code&gt; hits the entire fleet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            ┌───────────── AI (Claude / opencode) ──────────────┐
            │            remote-agents (MCP, stdio)              │
            └──────────────────────┬────────────────────────────┘
                                   │ wss:// (E2E encrypted)
                           ┌───────┴────────┐  relay (CF Worker or self-hosted)
                           │   room=homelab  │
        ┌──────────┬───────┴────┬───────────┬──────────────┐
      nas         pi-1        pi-2        media           desk
   (storage)      (pi)        (pi)       (media)      (workstation)
    x86/ZFS       ARM64       ARM64      x86/Docker      x86 desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The neat part: that relay can be Cloudflare's hosted Worker, or your own &lt;strong&gt;self-hosted Rust relay&lt;/strong&gt; (&lt;code&gt;remote-agents-relay --bind 0.0.0.0:8080&lt;/code&gt;) running on the NAS itself. For a homelab that never wants to phone home to a cloud, self-hosting the relay keeps the entire control plane inside your own four walls.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick summary: the 6 recipes at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Recipe&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Install &amp;amp; tag&lt;/td&gt;
&lt;td&gt;Put an agent on every host and tag it (&lt;code&gt;storage&lt;/code&gt;, &lt;code&gt;pi&lt;/code&gt;, &lt;code&gt;media&lt;/code&gt;, &lt;code&gt;workstation&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Fleet updates&lt;/td&gt;
&lt;td&gt;Patch every Linux host with one &lt;code&gt;fleet_exec&lt;/code&gt;; refresh the agents with &lt;code&gt;fleet_update_check&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Nightly backups&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;schedule_add&lt;/code&gt; a 6-field on-host cron that backs up to the NAS even if the relay drops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Health sweep&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fleet_exec&lt;/code&gt; &lt;code&gt;df -h&lt;/code&gt; / &lt;code&gt;sensors&lt;/code&gt; / &lt;code&gt;uptime&lt;/code&gt; with per-host results across x86 and ARM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;exec&lt;/code&gt; &lt;code&gt;docker compose pull &amp;amp;&amp;amp; up -d&lt;/code&gt; and prune on the &lt;code&gt;media&lt;/code&gt; host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Safe inspection&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;plan&lt;/code&gt; mode for read-only audits, plus the E2E + denylist guarantees&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why manage a homelab fleet this way
&lt;/h2&gt;

&lt;p&gt;Before the recipes, it's worth being honest about &lt;em&gt;when&lt;/em&gt; this approach earns its keep — and when it doesn't. Reach for AI-driven &lt;strong&gt;homelab automation&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You're tired of SSH hopping.&lt;/strong&gt; Patching five hosts by hand is five logins, five &lt;code&gt;sudo&lt;/code&gt; prompts, and at least one "wait, was that the Pi or the NAS?" moment. One &lt;code&gt;fleet_exec&lt;/code&gt; replaces all of it — roughly &lt;strong&gt;one command instead of N SSH sessions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your hardware is mixed.&lt;/strong&gt; ARM Raspberry Pis and x86 boxes need different package managers and report sensors differently. OS-family targeting (&lt;code&gt;os:linux&lt;/code&gt;) and tags let one request adapt per host without you branching by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want unattended, resilient automation.&lt;/strong&gt; Scheduled backups should fire even if your laptop is closed and the relay is unreachable. On-host cron via &lt;code&gt;schedule_add&lt;/code&gt; keeps running locally regardless of link state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want a single audit surface.&lt;/strong&gt; A read-only &lt;code&gt;plan&lt;/code&gt;-mode sweep across the whole fleet gives you a snapshot of every machine's disk, temperature, and uptime in one shot — perfect for the weekly "is anything quietly dying?" check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's &lt;em&gt;not&lt;/em&gt; a replacement for Ansible playbooks or a Kubernetes orchestrator. Think of &lt;code&gt;remote-agents&lt;/code&gt; as interactive plus autonomous access to shell, files, git, and cron across many hosts through one AI interface — ideal for small-to-medium fleets, dev/lab environments, and ad-hoc operations.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 1 — Install agents and tag the homelab
&lt;/h2&gt;

&lt;p&gt;Everything starts with putting an agent on each box and giving it a tag. The package is a single Rust binary, so install is the same everywhere — only the &lt;code&gt;--name&lt;/code&gt; and &lt;code&gt;--tags&lt;/code&gt; change per host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Install and run on each machine.&lt;/strong&gt; On 24/7 hosts like the NAS and the media box, use &lt;code&gt;remote-agents install&lt;/code&gt; so the agent comes back as a background service (systemd on Linux, launchd on macOS) after a reboot. On the Pis and desktop you can do the same, or just &lt;code&gt;run&lt;/code&gt; it interactively while you experiment.&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="c"&gt;# once on every machine&lt;/span&gt;
npm i &lt;span class="nt"&gt;-g&lt;/span&gt; remote-agents

&lt;span class="c"&gt;# NAS — 24/7, install as a background service&lt;/span&gt;
remote-agents &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--relay&lt;/span&gt; wss://&amp;lt;relay&amp;gt; &lt;span class="nt"&gt;--room&lt;/span&gt; homelab &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;secret&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; nas &lt;span class="nt"&gt;--tags&lt;/span&gt; storage

&lt;span class="c"&gt;# the two Raspberry Pis (ARM)&lt;/span&gt;
remote-agents &lt;span class="nb"&gt;install&lt;/span&gt; ... &lt;span class="nt"&gt;--name&lt;/span&gt; pi-1 &lt;span class="nt"&gt;--tags&lt;/span&gt; pi
remote-agents &lt;span class="nb"&gt;install&lt;/span&gt; ... &lt;span class="nt"&gt;--name&lt;/span&gt; pi-2 &lt;span class="nt"&gt;--tags&lt;/span&gt; pi

&lt;span class="c"&gt;# Docker media host&lt;/span&gt;
remote-agents &lt;span class="nb"&gt;install&lt;/span&gt; ... &lt;span class="nt"&gt;--name&lt;/span&gt; media &lt;span class="nt"&gt;--tags&lt;/span&gt; media

&lt;span class="c"&gt;# your desktop / workstation&lt;/span&gt;
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; desk &lt;span class="nt"&gt;--tags&lt;/span&gt; workstation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2. Confirm the whole fleet is online.&lt;/strong&gt; In your AI chat, just ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Show me the agents in the homelab room."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Under the hood that calls &lt;strong&gt;&lt;code&gt;list_agents&lt;/code&gt;&lt;/strong&gt;, which returns each peer's OS family, distro, kernel, shell, tags, and an &lt;code&gt;update_available&lt;/code&gt; flag. This is your inventory — in one response you can see that &lt;code&gt;pi-1&lt;/code&gt; and &lt;code&gt;pi-2&lt;/code&gt; report &lt;code&gt;aarch64&lt;/code&gt;/Debian while &lt;code&gt;nas&lt;/code&gt; and &lt;code&gt;media&lt;/code&gt; report &lt;code&gt;x86_64&lt;/code&gt;, which is exactly the kind of detail that decides whether you use &lt;code&gt;apt&lt;/code&gt; or &lt;code&gt;dnf&lt;/code&gt; later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Resolution order for settings is &lt;strong&gt;CLI flag &amp;gt; &lt;code&gt;REMOTE_AGENTS_*&lt;/code&gt; env &amp;gt; &lt;code&gt;config.toml&lt;/code&gt; &amp;gt; default&lt;/strong&gt;. For 24/7 hosts, drop the relay/room/token into &lt;code&gt;config.toml&lt;/code&gt; so the systemd unit stays clean and secrets aren't baked into the command line.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 2 — One-command system updates across every Linux host
&lt;/h2&gt;

&lt;p&gt;This is the headline win for &lt;strong&gt;homelab fleet management&lt;/strong&gt;: patching the entire fleet in a single call instead of logging into each box. Because your hosts span Debian-based Pis and possibly an RPM-based NAS, you'll target by OS family and let each host run its own package manager.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Patch every Linux host at once.&lt;/strong&gt; The &lt;code&gt;target="os:linux"&lt;/code&gt; selector hits &lt;code&gt;nas&lt;/code&gt;, &lt;code&gt;pi-1&lt;/code&gt;, &lt;code&gt;pi-2&lt;/code&gt;, &lt;code&gt;media&lt;/code&gt;, and &lt;code&gt;desk&lt;/code&gt; (if it's Linux) in one shot. Use a command that works regardless of distro by trying both managers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="os:linux"
            command="(command -v apt-get &amp;gt;/dev/null &amp;amp;&amp;amp; sudo apt-get update &amp;amp;&amp;amp; sudo apt-get -y upgrade) || (command -v dnf &amp;gt;/dev/null &amp;amp;&amp;amp; sudo dnf -y upgrade)"
            timeout_ms=600000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results come back &lt;strong&gt;per host&lt;/strong&gt;: &lt;code&gt;pi-1&lt;/code&gt; and &lt;code&gt;pi-2&lt;/code&gt; report their &lt;code&gt;apt&lt;/code&gt; upgrade, the NAS reports its own, and if one box is offline the others still complete — one failing host does not sink the batch. The Raspberry Pis, being ARM, pull &lt;code&gt;arm64&lt;/code&gt; packages while the x86 boxes pull &lt;code&gt;amd64&lt;/code&gt;; you don't have to think about it, each host resolves its own architecture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Bump &lt;code&gt;timeout_ms&lt;/code&gt; for upgrades — a big &lt;code&gt;apt upgrade&lt;/code&gt; on a first-gen Pi over a slow SD card can easily run past the default. 600000 ms (10 minutes) is a safe ceiling for a homelab.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Update the agents themselves.&lt;/strong&gt; Software that manages your fleet should keep itself current. &lt;strong&gt;&lt;code&gt;fleet_update_check&lt;/code&gt;&lt;/strong&gt; reports which &lt;em&gt;idle&lt;/em&gt; hosts are running an older version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_update_check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then refresh the ones that are behind:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="all"  command="npm i -g remote-agents@latest"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;code&gt;fleet_update_check&lt;/code&gt; deliberately only flags &lt;em&gt;idle&lt;/em&gt; hosts. A host mid-task won't get yanked out from under a running job — you can patch it on the next pass.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 3 — Nightly backups to the NAS that survive a relay outage
&lt;/h2&gt;

&lt;p&gt;Backups are the one job you absolutely cannot have depend on your laptop being awake. The trick here is &lt;strong&gt;&lt;code&gt;schedule_add&lt;/code&gt;&lt;/strong&gt;, which installs a cron job that runs &lt;strong&gt;on the host itself&lt;/strong&gt; — so it fires every night even if the relay link is down, your AI client is closed, or your internet is out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Schedule a nightly pull from a Pi to the NAS.&lt;/strong&gt; The cron spec is a &lt;strong&gt;6-field&lt;/strong&gt; string (&lt;code&gt;sec min hour day month dow&lt;/code&gt;), which is one field more than classic crontab — the extra leading field is seconds. For 02:30 every night that's &lt;code&gt;0 30 2 * * *&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;schedule_add  agent_id=pi-1  name=nightly-backup
              cron="0 30 2 * * *"
              command="rsync -aH --delete /home/pi/data/ nas:/mnt/tank/backups/pi-1/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2. Do the same for the other Pi and the media box,&lt;/strong&gt; pointing each at its own folder under the NAS backup pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;schedule_add  agent_id=pi-2   name=nightly-backup  cron="0 35 2 * * *"  command="rsync -aH --delete /home/pi/data/ nas:/mnt/tank/backups/pi-2/"
schedule_add  agent_id=media  name=config-backup   cron="0 40 2 * * *"  command="tar czf - /srv/docker | ssh nas 'cat &amp;gt; /mnt/tank/backups/media/docker-configs.tgz'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Staggering the start times (02:30, 02:35, 02:40) keeps three machines from hammering the NAS at the same second. The backups land in ZFS-backed storage on TrueNAS, so you can layer snapshots on top for point-in-time recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Verify what's scheduled.&lt;/strong&gt; List and prune jobs at any time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;schedule_list    agent_id=pi-1
schedule_remove  agent_id=pi-1  name=nightly-backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Because the schedule lives on the host, a &lt;code&gt;schedule_add&lt;/code&gt; you set today keeps running even if you never open your AI chat again. Treat &lt;code&gt;schedule_list&lt;/code&gt; as your "what cron jobs did past-me set up?" audit — run it across hosts occasionally so nothing becomes a forgotten mystery.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 4 — Whole-fleet health, disk, and temperature sweep
&lt;/h2&gt;

&lt;p&gt;Once a week, you want a single answer to "is anything in my homelab quietly dying?" One &lt;strong&gt;&lt;code&gt;fleet_exec&lt;/code&gt;&lt;/strong&gt; gives you disk usage, temperature, and uptime from every host, with results grouped per machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Disk usage across the whole fleet.&lt;/strong&gt; Free space is the classic silent killer — a full &lt;code&gt;/&lt;/code&gt; will brick a Pi and stall Docker on the media box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="all"  command="df -h / /mnt 2&amp;gt;/dev/null"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2. Temperatures, accounting for x86 vs ARM.&lt;/strong&gt; This is where the architecture split matters. On x86 boxes (&lt;code&gt;nas&lt;/code&gt;, &lt;code&gt;media&lt;/code&gt;, &lt;code&gt;desk&lt;/code&gt;) the standard tool is &lt;code&gt;sensors&lt;/code&gt; from &lt;code&gt;lm-sensors&lt;/code&gt;. On the ARM Raspberry Pis, &lt;code&gt;sensors&lt;/code&gt; often returns nothing useful — the canonical reading comes from &lt;code&gt;vcgencmd&lt;/code&gt; instead. A command that adapts on each host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="all"
            command="(vcgencmd measure_temp 2&amp;gt;/dev/null) || (sensors 2&amp;gt;/dev/null | grep -iE 'temp|core' | head -n 5) || echo 'no temp sensor'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Pis will answer with something like &lt;code&gt;temp=48.3'C&lt;/code&gt; from &lt;code&gt;vcgencmd&lt;/code&gt;, while the x86 hosts answer with &lt;code&gt;Core 0: +42.0°C&lt;/code&gt; from &lt;code&gt;sensors&lt;/code&gt;. You get one tidy per-host readout despite two completely different sensor stacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Uptime and load to spot the limpers:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="all"  command="uptime &amp;amp;&amp;amp; free -h | head -n 2"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; ARM Raspberry Pis and x86 hosts report differently almost everywhere — package managers, sensors, even the meaning of high load (a four-core Pi saturates faster than a desktop). When a per-host result looks weird, check whether you're reading a Pi (&lt;code&gt;os:linux&lt;/code&gt; + tag &lt;code&gt;pi&lt;/code&gt;, &lt;code&gt;aarch64&lt;/code&gt;) before you panic. Throttling on a Pi (&lt;code&gt;throttled=0x...&lt;/code&gt; from &lt;code&gt;vcgencmd get_throttled&lt;/code&gt;) is the homelab equivalent of a check-engine light.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you'd rather get the disk sweep &lt;em&gt;separately&lt;/em&gt; for just the storage tier, swap &lt;code&gt;target="all"&lt;/code&gt; for &lt;code&gt;target="storage"&lt;/code&gt; and you'll only hit the NAS.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 5 — Docker management on the media host
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;media&lt;/code&gt; box runs your Jellyfin/Plex stack under Docker Compose. Container lifecycle is a great fit for plain &lt;strong&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/strong&gt; scoped to that single host — no need to fan out a fleet operation for a box-specific job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Pull updated images and recreate the stack.&lt;/strong&gt; A &lt;code&gt;pull&lt;/code&gt; followed by &lt;code&gt;up -d&lt;/code&gt; is the standard zero-fuss update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exec  agent_id=media
      command="cd /srv/docker/media &amp;amp;&amp;amp; docker compose pull &amp;amp;&amp;amp; docker compose up -d"
      cwd=/srv/docker/media
      timeout_ms=300000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker compose up -d&lt;/code&gt; only recreates the containers whose images actually changed, so unchanged services (and your active Jellyfin streams) stay up. The pull can be chunky on a media stack, hence the generous 300000 ms (5-minute) timeout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Check container health and recent logs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exec  agent_id=media  command="docker compose ps"
exec  agent_id=media  command="docker compose logs --tail 50 jellyfin"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3. Reclaim disk after updates.&lt;/strong&gt; Old image layers pile up fast on a media host. Prune them — but be deliberate about it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exec  agent_id=media  command="docker image prune -f &amp;amp;&amp;amp; docker builder prune -f"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Plain &lt;code&gt;docker system prune -af&lt;/code&gt; will also remove &lt;em&gt;all&lt;/em&gt; unused images and can nuke volumes if you add &lt;code&gt;--volumes&lt;/code&gt;. On a media host where you cache large images, prefer the narrower &lt;code&gt;docker image prune -f&lt;/code&gt; shown above so you don't re-download tens of gigabytes on the next &lt;code&gt;up&lt;/code&gt;. Let the AI propose the command in &lt;code&gt;plan&lt;/code&gt; mode first (next recipe) and read it before you let it run.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 6 — Safe read-only inspection with plan mode and the denylist
&lt;/h2&gt;

&lt;p&gt;You should never have to choose between "convenient" and "safe" on machines that hold your photos, backups, and home automation. &lt;code&gt;remote-agents&lt;/code&gt; gives every host a &lt;strong&gt;safety mode&lt;/strong&gt; you set with &lt;strong&gt;&lt;code&gt;set_mode&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;plan&lt;/code&gt;&lt;/strong&gt; — read-only. Only &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;git_status&lt;/code&gt;, and safe &lt;code&gt;exec&lt;/code&gt; are allowed. Perfect for inspecting a host without any chance of changing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;edit&lt;/code&gt;&lt;/strong&gt; — writes allowed, and the agent makes an automatic &lt;strong&gt;backup&lt;/strong&gt; before overwriting any file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bypass&lt;/code&gt;&lt;/strong&gt; — unrestricted access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/strong&gt; — the agent rejects everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The natural homelab workflow is: inspect in &lt;code&gt;plan&lt;/code&gt;, then flip to &lt;code&gt;edit&lt;/code&gt; only for the moment you actually need to change something, then flip back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_mode  agent_id=nas  mode=plan     # look around, change nothing
# ... you've decided a config needs editing ...
set_mode  agent_id=nas  mode=edit     # writes now allowed, with auto-backup
# ... done ...
set_mode  agent_id=nas  mode=plan     # back to read-only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A read-only sweep pairs beautifully with this. Set the whole fleet to &lt;code&gt;plan&lt;/code&gt; and ask the AI to report what it finds — it physically can't modify anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_read  target="all"  path=/etc/os-release
fleet_search  target="storage"  query="zpool"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two guarantees hold no matter what mode you're in. First, every command and result payload is &lt;strong&gt;end-to-end encrypted&lt;/strong&gt; (AES-GCM-256, key derived from your room token) — the relay forwards ciphertext blind and never sees your data or keys. Second, a &lt;strong&gt;hard deny-list&lt;/strong&gt; (paths like &lt;code&gt;/etc/shadow&lt;/code&gt; and &lt;code&gt;/boot&lt;/code&gt;) is enforced &lt;strong&gt;even in &lt;code&gt;bypass&lt;/code&gt; mode&lt;/strong&gt;, so an over-eager AI literally cannot read your shadow file or scribble in your boot partition.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For a recurring "is anything wrong?" audit, keep the whole fleet in &lt;code&gt;plan&lt;/code&gt; mode by default and only promote a single host to &lt;code&gt;edit&lt;/code&gt; for the exact window you're making a change. It turns a slightly scary AI-with-shell-access into a read-only dashboard that you consciously unlock.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How is this different from Ansible or a dashboard like Cockpit?
&lt;/h3&gt;

&lt;p&gt;It's complementary, not a replacement. Ansible is great for declarative, repeatable provisioning; Cockpit is a per-host web UI. &lt;code&gt;remote-agents&lt;/code&gt; gives you &lt;em&gt;interactive and autonomous&lt;/em&gt; access to shell, files, git, and cron across your whole homelab through one AI conversation, with end-to-end encryption. It shines for ad-hoc operations, mixed x86/ARM fleets, and "just go do this across everything" tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a cloud account to manage my home servers with AI?
&lt;/h3&gt;

&lt;p&gt;No. You can use the hosted Cloudflare relay, or run the &lt;strong&gt;self-hosted Rust relay&lt;/strong&gt; (&lt;code&gt;remote-agents-relay --bind 0.0.0.0:8080&lt;/code&gt;) on a box you already own — the NAS is a natural home for it — and point every agent at &lt;code&gt;ws://your-host:8080&lt;/code&gt;. The entire control plane then stays inside your LAN.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will my Raspberry Pis and x86 boxes need different commands?
&lt;/h3&gt;

&lt;p&gt;Often, yes — and that's handled for you. Use &lt;code&gt;target="os:linux"&lt;/code&gt; or tags like &lt;code&gt;target="pi"&lt;/code&gt; to address groups, and write commands that adapt per host (try &lt;code&gt;apt&lt;/code&gt; then &lt;code&gt;dnf&lt;/code&gt;, try &lt;code&gt;vcgencmd&lt;/code&gt; then &lt;code&gt;sensors&lt;/code&gt;). Each host runs the branch that fits its architecture, and results come back per-host so you can see exactly what each machine did.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens to my scheduled backups if the internet goes down?
&lt;/h3&gt;

&lt;p&gt;They still run. &lt;code&gt;schedule_add&lt;/code&gt; installs a 6-field cron job that lives &lt;strong&gt;on the host itself&lt;/strong&gt;, so it fires on time even if the relay link drops, your AI client is closed, or your WAN is out. The relay is only needed when &lt;em&gt;you&lt;/em&gt; want to issue or inspect commands live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to give an AI shell access to my homelab?
&lt;/h3&gt;

&lt;p&gt;Start every host in &lt;code&gt;plan&lt;/code&gt; mode (read-only). The hard deny-list on critical paths like &lt;code&gt;/etc/shadow&lt;/code&gt; and &lt;code&gt;/boot&lt;/code&gt; is enforced even in &lt;code&gt;bypass&lt;/code&gt; mode, all payloads are end-to-end encrypted, and the relay forwards them blind. You promote a host to &lt;code&gt;edit&lt;/code&gt; or &lt;code&gt;bypass&lt;/code&gt; only for the specific window you need it, then drop it back.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's &lt;strong&gt;homelab fleet management&lt;/strong&gt; without the tab-juggling: one AI chat patches every Linux host with a single &lt;code&gt;fleet_exec&lt;/code&gt;, schedules NAS backups that survive an outage, sweeps disk and temperature across x86 and ARM in one readout, manages Docker on the media box, and keeps the dangerous stuff behind &lt;code&gt;plan&lt;/code&gt; mode, end-to-end encryption, and an unbreakable deny-list. Five machines, zero manual SSH sessions, and a homelab that finally behaves like one computer you can talk to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Install: &lt;code&gt;npm i -g remote-agents&lt;/code&gt; →&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/remote-agents" rel="noopener noreferrer"&gt;package on npm&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://github.com/47-ronn/tunshell_mcp_agents" rel="noopener noreferrer"&gt;source &amp;amp; docs on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Automate Database Backups Across a Server Fleet with AI: 7 Recipes for 2026</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Thu, 18 Jun 2026 14:13:35 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/automate-database-backups-across-a-server-fleet-with-ai-7-recipes-for-2026-395n</link>
      <guid>https://dev.to/bitwiserokos/automate-database-backups-across-a-server-fleet-with-ai-7-recipes-for-2026-395n</guid>
      <description>&lt;p&gt;If you are still SSH-ing into four boxes to confirm last night's dump actually&lt;br&gt;
ran, &lt;strong&gt;automated database backups across servers&lt;/strong&gt; are exactly the kind of chore&lt;br&gt;
that should be running on autopilot — not eating your evenings. In this guide we&lt;br&gt;
wire up &lt;strong&gt;postgres backup automation&lt;/strong&gt; for a small fleet (one primary, two&lt;br&gt;
streaming replicas, an offsite backup box) and drive the whole thing from a&lt;br&gt;
single AI interface using the MCP server&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/remote-agents" rel="noopener noreferrer"&gt;&lt;code&gt;remote-agents&lt;/code&gt;&lt;/a&gt;. No agent&lt;br&gt;
controller, no central scheduler to babysit — every host runs its own cron and&lt;br&gt;
reports back per-host.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; every database host runs a lightweight agent connected to an&lt;br&gt;
encrypted relay room. Your AI assistant (Claude or opencode) sees the whole&lt;br&gt;
fleet as one machine and calls tools like &lt;code&gt;schedule_add&lt;/code&gt;, &lt;code&gt;fleet_exec&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;file_stat&lt;/code&gt;, &lt;code&gt;fleet_git&lt;/code&gt;, &lt;code&gt;send_file&lt;/code&gt;, and &lt;code&gt;set_mode&lt;/code&gt;. Cron jobs live &lt;strong&gt;on the&lt;br&gt;
host&lt;/strong&gt;, so a nightly &lt;code&gt;pg_dump&lt;/code&gt; keeps running even if the relay link drops.&lt;br&gt;
Payloads are end-to-end encrypted (AES-GCM-256) — the relay forwards&lt;br&gt;
ciphertext blind.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Architecture: four hosts, one room&lt;/li&gt;
&lt;li&gt;Installing agents and tagging db hosts&lt;/li&gt;
&lt;li&gt;Quick summary of the seven recipes&lt;/li&gt;
&lt;li&gt;Why and when to use this&lt;/li&gt;
&lt;li&gt;Recipe 1 — Tag db hosts and look around in plan mode&lt;/li&gt;
&lt;li&gt;Recipe 2 — Schedule a nightly pg_dump with retention&lt;/li&gt;
&lt;li&gt;Recipe 3 — Verify backups are fresh across the fleet&lt;/li&gt;
&lt;li&gt;Recipe 4 — Run a schema migration across replicas safely&lt;/li&gt;
&lt;li&gt;Recipe 5 — Replication-lag and health checks&lt;/li&gt;
&lt;li&gt;Recipe 6 — Read a prod .env read-only&lt;/li&gt;
&lt;li&gt;Recipe 7 — Ship a dump host→host, SHA-256 verified&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture: four hosts, one room
&lt;/h2&gt;

&lt;p&gt;Let's take a realistic small-SaaS database tier: a Postgres primary, two&lt;br&gt;
streaming replicas for read scaling and failover, and an offsite box that only&lt;br&gt;
stores compressed dumps. We'll also nod to a Windows SQL Server host at the end&lt;br&gt;
to show the cross-platform story.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;db-primary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Postgres primary (writes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;db,primary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL 16, Linux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;db-replica-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Streaming replica&lt;/td&gt;
&lt;td&gt;&lt;code&gt;db,replica&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL 16, Linux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;db-replica-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Streaming replica&lt;/td&gt;
&lt;td&gt;&lt;code&gt;db,replica&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL 16, Linux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backup-box&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offsite dump target&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rsync, gzip, Linux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sql-win&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQL Server (optional)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;db,windows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQL Server 2022, Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All agents join one relay room (say &lt;code&gt;dbfleet&lt;/code&gt;). Tags let you address a &lt;strong&gt;group&lt;/strong&gt;&lt;br&gt;
in a single call: &lt;code&gt;target="db,replica"&lt;/code&gt; hits both replicas, &lt;code&gt;target="os:linux"&lt;/code&gt;&lt;br&gt;
hits every Linux box, and &lt;code&gt;target="all"&lt;/code&gt; sweeps the whole fleet. Note that&lt;br&gt;
multi-tag targets match a host carrying &lt;strong&gt;either&lt;/strong&gt; tag, so &lt;code&gt;db,primary&lt;/code&gt; resolves&lt;br&gt;
to every database host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            ┌────────────── AI (Claude / opencode) ──────────────┐
            │            remote-agents (MCP, stdio)               │
            └───────────────────────┬────────────────────────────┘
                                    │ wss:// (E2E-encrypted)
                            ┌───────┴────────┐  relay (CF Worker or self-hosted)
                            │   room=dbfleet  │
        ┌───────────┬───────┴────┬────────────┬─────────────┐
   db-primary   db-replica-1  db-replica-2  backup-box   sql-win
  (db,primary)  (db,replica)  (db,replica)  (backup)    (db,windows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relay is interchangeable: use the hosted Cloudflare Worker, or run your own&lt;br&gt;
Rust relay with &lt;code&gt;remote-agents-relay --bind 0.0.0.0:8080&lt;/code&gt; and point agents at&lt;br&gt;
&lt;code&gt;ws://your-host:8080&lt;/code&gt;. Either way the relay only ever sees encrypted frames.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Installing agents and tagging db hosts
&lt;/h2&gt;

&lt;p&gt;On each host you install the package once and start the agent with the right&lt;br&gt;
tags. For 24/7 database servers you'll want the background service form so the&lt;br&gt;
agent survives reboots:&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="c"&gt;# once on every machine&lt;/span&gt;
npm i &lt;span class="nt"&gt;-g&lt;/span&gt; remote-agents

&lt;span class="c"&gt;# the Postgres primary&lt;/span&gt;
remote-agents run &lt;span class="nt"&gt;--relay&lt;/span&gt; wss://&amp;lt;relay&amp;gt; &lt;span class="nt"&gt;--room&lt;/span&gt; dbfleet &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;secret&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; db-primary &lt;span class="nt"&gt;--tags&lt;/span&gt; db,primary

&lt;span class="c"&gt;# the two replicas&lt;/span&gt;
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; db-replica-1 &lt;span class="nt"&gt;--tags&lt;/span&gt; db,replica
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; db-replica-2 &lt;span class="nt"&gt;--tags&lt;/span&gt; db,replica

&lt;span class="c"&gt;# the offsite backup target&lt;/span&gt;
remote-agents run ... &lt;span class="nt"&gt;--name&lt;/span&gt; backup-box &lt;span class="nt"&gt;--tags&lt;/span&gt; backup

&lt;span class="c"&gt;# install as a systemd service for 24/7 hosts (instead of `run`)&lt;/span&gt;
remote-agents &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--relay&lt;/span&gt; wss://&amp;lt;relay&amp;gt; &lt;span class="nt"&gt;--room&lt;/span&gt; dbfleet &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;secret&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; db-primary &lt;span class="nt"&gt;--tags&lt;/span&gt; db,primary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then confirm the whole fleet is online. Ask your AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"List the agents in the room."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Under the hood that calls &lt;strong&gt;&lt;code&gt;list_agents&lt;/code&gt;&lt;/strong&gt;, which returns each peer's OS family,&lt;br&gt;
distro, kernel, shell, tags, and an &lt;code&gt;update_available&lt;/code&gt; flag when a newer agent&lt;br&gt;
version is out. It's a flat peer network — there is no controller node; every&lt;br&gt;
host both executes and dispatches.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Also read: &lt;a href="//./fleet-security-audit-sweep.en.md"&gt;Run a security audit across your whole fleet&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Quick summary of the seven recipes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;# Recipe&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1. Plan-mode look&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tag db hosts, set them to read-only &lt;code&gt;plan&lt;/code&gt;, and inspect Postgres safely before touching anything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2. Nightly pg_dump&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;schedule_add&lt;/code&gt; a &lt;code&gt;0 3 * * * *&lt;/code&gt; host-local cron that dumps + gzips and prunes anything older than 14 days.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3. Verify freshness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fleet_exec&lt;/code&gt; + &lt;code&gt;file_stat&lt;/code&gt; to confirm the dump exists, its size, and its mtime on every host at once.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4. Replica migration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fleet_git&lt;/code&gt; pull migrations + &lt;code&gt;fleet_exec&lt;/code&gt; across &lt;code&gt;db,replica&lt;/code&gt; with per-host results so a failed node is obvious.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5. Lag / health check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fleet_exec target="db,replica"&lt;/code&gt; running &lt;code&gt;pg_stat_replication&lt;/code&gt; to catch lag and broken streaming.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6. Read prod config&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;read_file&lt;/code&gt; a production &lt;code&gt;.env&lt;/code&gt; in &lt;code&gt;plan&lt;/code&gt; mode — zero write risk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;7. Ship a dump&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;send_file&lt;/code&gt; a dump from primary to &lt;code&gt;backup-box&lt;/code&gt; over a direct UDP channel, SHA-256 verified.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why and when to use this
&lt;/h2&gt;

&lt;p&gt;Database fleets are where "I'll just SSH in real quick" goes to die. A single&lt;br&gt;
forgotten retention prune fills a disk; a silent &lt;code&gt;pg_dump&lt;/code&gt; failure isn't noticed&lt;br&gt;
until the day you need the dump; a schema migration applied to the primary but&lt;br&gt;
not the replicas causes mysterious read errors hours later. Here's where driving&lt;br&gt;
the fleet through one AI interface pays off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cron that survives the network.&lt;/strong&gt; &lt;code&gt;schedule_add&lt;/code&gt; installs the cron &lt;strong&gt;on the
host itself&lt;/strong&gt;, so your nightly &lt;code&gt;0 3 * * * *&lt;/code&gt; dump runs even if the relay link
is down, your laptop is closed, or the AI session has long ended. The schedule
is not tied to your connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-host truth in one call.&lt;/strong&gt; &lt;code&gt;fleet_exec&lt;/code&gt; and &lt;code&gt;file_stat&lt;/code&gt; give you the dump
size and mtime on all four boxes in a single round trip instead of four SSH
sessions. One failing host does not sink the batch — it just shows up red in
the aggregated result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replica maintenance without drift.&lt;/strong&gt; &lt;code&gt;fleet_git&lt;/code&gt; + &lt;code&gt;fleet_exec&lt;/code&gt; against the
&lt;code&gt;db,replica&lt;/code&gt; tag apply the same migration to both replicas and report each one
separately, so a half-applied change is impossible to miss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe-by-default reads.&lt;/strong&gt; &lt;code&gt;plan&lt;/code&gt; mode makes a host read-only, so you can pull
a prod &lt;code&gt;.env&lt;/code&gt; or run &lt;code&gt;pg_stat_replication&lt;/code&gt; during an incident with no chance of
fat-fingering a write.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform reach.&lt;/strong&gt; &lt;code&gt;target="os:linux"&lt;/code&gt; hits the Postgres boxes; a
Windows SQL Server host joins the same room and answers &lt;code&gt;os:windows&lt;/code&gt; targeting
with a &lt;code&gt;sqlcmd&lt;/code&gt; backup instead of &lt;code&gt;pg_dump&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a replacement for a declarative backup orchestrator or a managed&lt;br&gt;
RDS-style service. It shines for self-hosted databases, small-to-mid fleets,&lt;br&gt;
dev/staging tiers, and the operational glue around backups that nobody wants to&lt;br&gt;
maintain in YAML.&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 1 — Tag db hosts and look around in plan mode
&lt;/h2&gt;

&lt;p&gt;Before automating anything, look first and touch nothing. Set every database&lt;br&gt;
host to &lt;strong&gt;&lt;code&gt;plan&lt;/code&gt;&lt;/strong&gt; mode — read-only &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;git_status&lt;/code&gt;, and safe &lt;code&gt;exec&lt;/code&gt;&lt;br&gt;
only — and get oriented.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Flip the database hosts to read-only.&lt;/li&gt;
&lt;li&gt;Confirm Postgres is up and check current data directory size.&lt;/li&gt;
&lt;li&gt;Eyeball where existing backups (if any) land.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_mode    target="db,replica"   mode=plan
set_mode    agent_id=db-primary   mode=plan

fleet_exec  target="db,primary"   command="systemctl is-active postgresql &amp;amp;&amp;amp; psql -tAc 'SELECT version();'"
fleet_exec  target="db,primary"   command="du -sh /var/lib/postgresql/16/main; df -h /var"
list_dir    agent_id=backup-box   path=/srv/backups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;plan&lt;/code&gt; mode still allows a read-only &lt;code&gt;exec&lt;/code&gt;, so &lt;code&gt;psql -tAc 'SELECT ...'&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;du -sh&lt;/code&gt; work fine. The moment a command would write, the agent rejects it.&lt;br&gt;
Start every incident here — you literally cannot break prod from &lt;code&gt;plan&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The aggregated reply comes back &lt;strong&gt;per host&lt;/strong&gt;: you'll see &lt;code&gt;db-primary&lt;/code&gt; reporting&lt;br&gt;
&lt;code&gt;active&lt;/code&gt; and a 240 GB data directory, while a replica that's catching up might&lt;br&gt;
report differently. That per-host shape is the whole point — no guessing which&lt;br&gt;
box you're looking at.&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 2 — Schedule a nightly pg_dump with retention
&lt;/h2&gt;

&lt;p&gt;This is the core of &lt;strong&gt;scheduled pg_dump&lt;/strong&gt; automation. We install a host-local&lt;br&gt;
cron on &lt;code&gt;db-primary&lt;/code&gt; that dumps the database, gzips it, and prunes anything&lt;br&gt;
older than 14 days. Because &lt;code&gt;schedule_add&lt;/code&gt; writes the cron &lt;strong&gt;to the host&lt;/strong&gt;, it&lt;br&gt;
keeps firing at 03:00 every night whether or not anyone is connected.&lt;/p&gt;

&lt;p&gt;Remember the cron is a &lt;strong&gt;6-field&lt;/strong&gt; spec — &lt;code&gt;sec min hour day month dow&lt;/code&gt; — so&lt;br&gt;
"3 AM nightly" is &lt;code&gt;0 0 3 * * *&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Switch the primary to &lt;code&gt;edit&lt;/code&gt; so the agent may create the dump file and the
wrapper script.&lt;/li&gt;
&lt;li&gt;Add the nightly dump-and-prune schedule.&lt;/li&gt;
&lt;li&gt;List schedules to confirm it registered.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the command the cron runs on db-primary (one line, gzip + 14-day prune)&lt;/span&gt;
pg_dump &lt;span class="nt"&gt;-Fc&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; postgres app_prod &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /srv/backups/app_prod-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="se"&gt;\%&lt;/span&gt;F&lt;span class="si"&gt;)&lt;/span&gt;.sql.gz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; find /srv/backups &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'app_prod-*.sql.gz'&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +14 &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_mode      agent_id=db-primary  mode=edit

schedule_add  agent_id=db-primary  name=nightly-pgdump \
  cron="0 0 3 * * *" \
  command="pg_dump -Fc -U postgres app_prod | gzip &amp;gt; /srv/backups/app_prod-$(date +%F).sql.gz &amp;amp;&amp;amp; find /srv/backups -name 'app_prod-*.sql.gz' -mtime +14 -delete"

schedule_list agent_id=db-primary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;-Fc&lt;/code&gt; custom format gives you a compressed, &lt;code&gt;pg_restore&lt;/code&gt;-friendly archive; a&lt;br&gt;
240 GB cluster typically lands around a 30–45 GB gzipped dump depending on how&lt;br&gt;
much of it is indexes and TOAST. The &lt;code&gt;-mtime +14 -delete&lt;/code&gt; clause keeps roughly&lt;br&gt;
two weeks of dumps and nothing more, so the disk doesn't quietly fill.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; for a &lt;strong&gt;mysqldump cron&lt;/strong&gt; the only thing that changes is the command —&lt;br&gt;
&lt;code&gt;mysqldump --single-transaction app_prod | gzip &amp;gt; ...&lt;/code&gt; — wrapped in the exact&lt;br&gt;
same &lt;code&gt;schedule_add&lt;/code&gt;. And for the Windows SQL Server host you'd schedule a&lt;br&gt;
&lt;code&gt;sqlcmd -Q "BACKUP DATABASE ..."&lt;/code&gt; instead. The scheduler, retention, and&lt;br&gt;
verification flow are identical across all of them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To remove the schedule later (say you've migrated to WAL archiving):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;schedule_remove  agent_id=db-primary  name=nightly-pgdump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 3 — Verify backups are fresh across the fleet
&lt;/h2&gt;

&lt;p&gt;A backup you never check is a backup you don't have. This recipe answers the only&lt;br&gt;
question that matters at 9 AM: &lt;strong&gt;did last night's dump actually run, and is it the&lt;br&gt;
right size, on every host?&lt;/strong&gt; We combine &lt;code&gt;fleet_exec&lt;/code&gt; to find the newest dump with&lt;br&gt;
&lt;code&gt;file_stat&lt;/code&gt; to read its exact size and mtime.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the newest dump file on each db host.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file_stat&lt;/code&gt; that file for an authoritative size and modification time.&lt;/li&gt;
&lt;li&gt;Flag anything older than ~26 hours as stale.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="db,primary"  \
  command="ls -t /srv/backups/app_prod-*.sql.gz 2&amp;gt;/dev/null | head -n1"

file_stat   agent_id=db-primary  path=/srv/backups/app_prod-2026-06-18.sql.gz

fleet_exec  target="db,primary"  \
  command="find /srv/backups -name 'app_prod-*.sql.gz' -mmin -1560 -printf '%p %s bytes\n' || echo NO_FRESH_BACKUP"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;file_stat&lt;/code&gt; returns the size and mtime directly, which is more trustworthy than&lt;br&gt;
parsing &lt;code&gt;ls&lt;/code&gt; output. The &lt;code&gt;-mmin -1560&lt;/code&gt; window (26 hours) gives the 03:00 job some&lt;br&gt;
slack — anything that didn't write a fresh file in that window prints&lt;br&gt;
&lt;code&gt;NO_FRESH_BACKUP&lt;/code&gt;, and because results are aggregated &lt;strong&gt;per host&lt;/strong&gt;, a replica&lt;br&gt;
that skipped its dump stands out instantly while the healthy boxes report a clean&lt;br&gt;
&lt;code&gt;~42 GB&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; size is your early-warning signal. A dump that suddenly drops&lt;br&gt;
from 42 GB to 200 KB almost always means &lt;code&gt;pg_dump&lt;/code&gt; errored out mid-run (bad&lt;br&gt;
credentials, a dropped connection, a full disk) and gzipped only the error.&lt;br&gt;
Watching size over time catches silent failures that a green exit code hides.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can fold this into a once-a-day self-check by wrapping the &lt;code&gt;find&lt;/code&gt; in another&lt;br&gt;
&lt;code&gt;schedule_add&lt;/code&gt; that appends to a log — same host-local cron pattern as Recipe 2.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Also read: &lt;a href="//./distributed-ci-test-farm.en.md"&gt;Build a cross-platform CI test farm&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 4 — Run a schema migration across replicas safely
&lt;/h2&gt;

&lt;p&gt;Replicas in &lt;strong&gt;database fleet management&lt;/strong&gt; drift the moment a migration lands on&lt;br&gt;
one box but not another. With &lt;code&gt;fleet_git&lt;/code&gt; to pull the migration repo and&lt;br&gt;
&lt;code&gt;fleet_exec&lt;/code&gt; to apply it across the &lt;code&gt;db,replica&lt;/code&gt; tag, both replicas move in&lt;br&gt;
lockstep and you get a per-host verdict.&lt;/p&gt;

&lt;p&gt;On true streaming replicas you don't run DDL directly (they're read-only and&lt;br&gt;
replay WAL from the primary). This recipe fits the common real-world setup where&lt;br&gt;
"replica" hosts also run migration tooling against a logical target, or where you&lt;br&gt;
roll out an out-of-band maintenance script. Adjust to your topology.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull the latest migrations on both replicas at once.&lt;/li&gt;
&lt;li&gt;Apply them, capturing per-host success/failure.&lt;/li&gt;
&lt;li&gt;Re-check that both ended on the same schema version.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_git   target="db,replica"  op=pull  repo=/srv/migrations  remote=origin  branch=main

fleet_exec  target="db,replica"  \
  command="cd /srv/migrations &amp;amp;&amp;amp; ./run_migrations.sh 2&amp;gt;&amp;amp;1 | tail -n 5 || echo MIGRATION_FAILED"

fleet_exec  target="db,replica"  \
  command="psql -tAc 'SELECT max(version) FROM schema_migrations;'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The payoff is the &lt;strong&gt;per-host aggregation&lt;/strong&gt;: if &lt;code&gt;db-replica-2&lt;/code&gt; returns&lt;br&gt;
&lt;code&gt;MIGRATION_FAILED&lt;/code&gt; while &lt;code&gt;db-replica-1&lt;/code&gt; reports the new version, you know exactly&lt;br&gt;
which node to fix — no scrolling through interleaved SSH output trying to figure&lt;br&gt;
out whose error you're reading. One failing replica does not block the batch from&lt;br&gt;
finishing on the healthy one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; put the hosts in &lt;code&gt;edit&lt;/code&gt; mode (not &lt;code&gt;bypass&lt;/code&gt;) for migrations. In &lt;code&gt;edit&lt;/code&gt;,&lt;br&gt;
the agent auto-creates a &lt;strong&gt;backup before any overwrite&lt;/strong&gt;, so a botched&lt;br&gt;
&lt;code&gt;write_file&lt;/code&gt; to a config or script leaves you the original. Drop back to &lt;code&gt;plan&lt;/code&gt;&lt;br&gt;
the moment you're done.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 5 — Replication-lag and health checks
&lt;/h2&gt;

&lt;p&gt;The single most useful &lt;strong&gt;replica maintenance&lt;/strong&gt; check is replication lag: how far&lt;br&gt;
behind the primary each replica is. From the primary you query&lt;br&gt;
&lt;code&gt;pg_stat_replication&lt;/code&gt;; from each replica you check &lt;code&gt;pg_last_wal_replay_lag&lt;/code&gt;.&lt;br&gt;
Both are one &lt;code&gt;fleet_exec&lt;/code&gt; away.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;From the primary, list connected replicas and their byte lag.&lt;/li&gt;
&lt;li&gt;From each replica, read its replay delay.&lt;/li&gt;
&lt;li&gt;Alert on anything beyond your threshold.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fleet_exec  target="db,primary"  \
  command="psql -xtAc \"SELECT client_addr, state, pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes FROM pg_stat_replication;\""

fleet_exec  target="db,replica"  \
  command="psql -tAc 'SELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;'"

fleet_exec  target="db,replica"  \
  command="psql -tAc 'SELECT CASE WHEN pg_is_in_recovery() THEN 1 ELSE 0 END;' | grep -q 1 &amp;amp;&amp;amp; echo OK_RECOVERY || echo NOT_A_REPLICA"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run from &lt;code&gt;plan&lt;/code&gt; mode — these are all read-only queries, so there's zero risk even&lt;br&gt;
on a busy primary. The first query, sent to &lt;code&gt;db-primary&lt;/code&gt;, shows you both replicas&lt;br&gt;
as rows with their &lt;code&gt;state&lt;/code&gt; (&lt;code&gt;streaming&lt;/code&gt; is what you want) and &lt;code&gt;lag_bytes&lt;/code&gt;. The&lt;br&gt;
second, fanned out across the &lt;code&gt;db,replica&lt;/code&gt; tag, returns each replica's replay lag&lt;br&gt;
side by side. A replica that has fallen out of streaming shows up either as a&lt;br&gt;
missing row in the primary's view or a ballooning &lt;code&gt;replay_lag&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; a healthy LAN replica usually sits under a few hundred milliseconds&lt;br&gt;
of replay lag. A sudden jump to tens of seconds (or a replica vanishing from&lt;br&gt;
&lt;code&gt;pg_stat_replication&lt;/code&gt; entirely) means streaming broke — often a WAL retention&lt;br&gt;
or network issue. Catching it here, across both replicas in one call, beats&lt;br&gt;
discovering it when a read query returns stale data.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 6 — Read a prod .env read-only
&lt;/h2&gt;

&lt;p&gt;Sometimes you just need to confirm a connection string or a backup target path&lt;br&gt;
without any chance of editing it. With the host in &lt;strong&gt;&lt;code&gt;plan&lt;/code&gt;&lt;/strong&gt; mode, &lt;code&gt;read_file&lt;/code&gt;&lt;br&gt;
hands you the file contents and nothing about the session can write.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensure the host is in &lt;code&gt;plan&lt;/code&gt; mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read_file&lt;/code&gt; the config.&lt;/li&gt;
&lt;li&gt;Optionally grep for one key with a read-only &lt;code&gt;exec&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_mode   agent_id=db-primary  mode=plan

read_file  agent_id=db-primary  path=/srv/app/.env

exec       agent_id=db-primary  command="grep -E '^(PGHOST|PGDATABASE|BACKUP_DIR)=' /srv/app/.env"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because the agent is read-only, even if you (or the AI) followed up with a&lt;br&gt;
&lt;code&gt;write_file&lt;/code&gt;, it would be rejected. On top of that, a hard &lt;strong&gt;deny-list&lt;/strong&gt; —&lt;br&gt;
covering paths like &lt;code&gt;/etc/shadow&lt;/code&gt; and &lt;code&gt;/boot&lt;/code&gt; — applies on &lt;strong&gt;every&lt;/strong&gt; host in&lt;br&gt;
&lt;strong&gt;every&lt;/strong&gt; mode, including &lt;code&gt;bypass&lt;/code&gt;, so the truly sensitive system files are never&lt;br&gt;
readable or writable through the agent regardless of how you set things up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;code&gt;plan&lt;/code&gt; mode is the right default for production database hosts.&lt;br&gt;
Keep them in &lt;code&gt;plan&lt;/code&gt; day to day and only flip a single host to &lt;code&gt;edit&lt;/code&gt; for the&lt;br&gt;
specific window you're changing something — then flip it straight back. It&lt;br&gt;
turns "I'll be careful" into "I literally can't write right now."&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Recipe 7 — Ship a dump host→host, SHA-256 verified
&lt;/h2&gt;

&lt;p&gt;Finally, get the dump off the primary and onto the offsite &lt;code&gt;backup-box&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;send_file&lt;/code&gt; streams a file &lt;strong&gt;host→host over a direct UDP data channel&lt;/strong&gt; (opened on&lt;br&gt;
demand, with automatic relay fallback) and verifies it end-to-end with a&lt;br&gt;
&lt;strong&gt;SHA-256&lt;/strong&gt; checksum. Because the receiving side performs a write, the&lt;br&gt;
destination host needs &lt;code&gt;edit&lt;/code&gt; or &lt;code&gt;bypass&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Put &lt;code&gt;backup-box&lt;/code&gt; in &lt;code&gt;edit&lt;/code&gt; so it can write the incoming file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;send_file&lt;/code&gt; the latest dump from &lt;code&gt;db-primary&lt;/code&gt; to &lt;code&gt;backup-box&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirm arrival and size with &lt;code&gt;file_stat&lt;/code&gt; on the receiver.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_mode     agent_id=backup-box  mode=edit

send_file    agent_id=db-primary  \
  path=/srv/backups/app_prod-2026-06-18.sql.gz \
  to=backup-box \
  dest=/srv/offsite/app_prod-2026-06-18.sql.gz

file_stat    agent_id=backup-box  path=/srv/offsite/app_prod-2026-06-18.sql.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The transfer goes peer-to-peer where the network allows, so a 42 GB dump doesn't&lt;br&gt;
have to round-trip through the relay — and if a direct UDP path can't be&lt;br&gt;
established, it transparently falls back to the relay so the copy still&lt;br&gt;
completes. The SHA-256 check means you find out about a corrupted transfer&lt;br&gt;
immediately, not when a restore fails three weeks later. If you'd rather pull&lt;br&gt;
from the receiving side, &lt;code&gt;transfer_get&lt;/code&gt; is the mirror-image tool.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; in the browser fleet-chat panel there's a Files view that does the same&lt;br&gt;
move with a live progress bar, plus a binary-safe chunked download of any file&lt;br&gt;
through the relay. Handy when you want to eyeball a dump's size or grab one to&lt;br&gt;
your laptop without dropping to the CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Set &lt;code&gt;backup-box&lt;/code&gt; back to &lt;code&gt;plan&lt;/code&gt; when the copy is done, so the offsite store is&lt;br&gt;
read-only at rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_mode  agent_id=backup-box  mode=plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How is this different from a cron job I'd write myself?
&lt;/h3&gt;

&lt;p&gt;It's the same idea, but the cron is installed and managed through one AI&lt;br&gt;
interface across the whole fleet, and &lt;code&gt;schedule_add&lt;/code&gt; registers it &lt;strong&gt;on the host&lt;/strong&gt;&lt;br&gt;
so it survives relay outages and reboots. The difference is operational: you add,&lt;br&gt;
list, verify, and remove backup schedules on four boxes from one place, and you&lt;br&gt;
get per-host confirmation that each dump actually ran, instead of trusting four&lt;br&gt;
independent crontabs you never look at.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will my nightly pg_dump still run if the relay or my laptop is offline?
&lt;/h3&gt;

&lt;p&gt;Yes. &lt;code&gt;schedule_add&lt;/code&gt; writes a host-local cron that lives entirely on the database&lt;br&gt;
server. The relay link and your AI session are only needed to &lt;strong&gt;create, list, or&lt;br&gt;
remove&lt;/strong&gt; the schedule — not to run it. Your &lt;code&gt;0 0 3 * * *&lt;/code&gt; dump fires at 03:00&lt;br&gt;
regardless of whether anything is connected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to point this at a production database?
&lt;/h3&gt;

&lt;p&gt;Start every host in &lt;code&gt;plan&lt;/code&gt; mode, which is read-only — you can run&lt;br&gt;
&lt;code&gt;pg_stat_replication&lt;/code&gt;, &lt;code&gt;read_file&lt;/code&gt; a &lt;code&gt;.env&lt;/code&gt;, and check dump freshness with zero&lt;br&gt;
write risk. Flip a single host to &lt;code&gt;edit&lt;/code&gt; only for the specific change you're&lt;br&gt;
making (&lt;code&gt;edit&lt;/code&gt; auto-backs-up before overwriting), and a hard deny-list on paths&lt;br&gt;
like &lt;code&gt;/etc/shadow&lt;/code&gt; and &lt;code&gt;/boot&lt;/code&gt; applies even in &lt;code&gt;bypass&lt;/code&gt;. All command payloads and&lt;br&gt;
results are end-to-end encrypted; the relay forwards ciphertext and never sees&lt;br&gt;
your data or keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I back up MySQL or SQL Server the same way?
&lt;/h3&gt;

&lt;p&gt;Yes — the workflow is database-agnostic. Swap the command inside &lt;code&gt;schedule_add&lt;/code&gt;:&lt;br&gt;
&lt;code&gt;mysqldump --single-transaction ... | gzip&lt;/code&gt; for MySQL, or&lt;br&gt;
&lt;code&gt;sqlcmd -Q "BACKUP DATABASE ..."&lt;/code&gt; on a Windows host targeted via &lt;code&gt;os:windows&lt;/code&gt;.&lt;br&gt;
The scheduling, 14-day retention prune, freshness verification, and SHA-256&lt;br&gt;
transfer all stay identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I keep the agents themselves up to date across the fleet?
&lt;/h3&gt;

&lt;p&gt;Run &lt;strong&gt;&lt;code&gt;fleet_update_check&lt;/code&gt;&lt;/strong&gt;, which tells you which idle hosts have a newer agent&lt;br&gt;
version available, then run &lt;code&gt;npm i -g remote-agents@latest&lt;/code&gt; on those hosts. It&lt;br&gt;
won't interrupt a host mid-task, so you can update the fleet without disrupting a&lt;br&gt;
running backup or migration.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;One MCP interface, five hosts, zero manual SSH sessions: a nightly &lt;strong&gt;scheduled&lt;br&gt;
pg_dump&lt;/strong&gt; with 14-day retention runs host-local on the primary, freshness is&lt;br&gt;
verified across the fleet with &lt;code&gt;file_stat&lt;/code&gt;, replicas get migrations and lag checks&lt;br&gt;
in lockstep, prod config is read safely in &lt;code&gt;plan&lt;/code&gt; mode, and the dump lands offsite&lt;br&gt;
with a SHA-256 guarantee. That's what &lt;strong&gt;automated database backups across servers&lt;/strong&gt;&lt;br&gt;
should feel like in 2026 — boring, observable, and running whether you're watching&lt;br&gt;
or not. Add cross-platform targeting and the same recipes cover your MySQL and SQL&lt;br&gt;
Server hosts too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Install: &lt;code&gt;npm i -g remote-agents&lt;/code&gt; →&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/remote-agents" rel="noopener noreferrer"&gt;package on npm&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://github.com/47-ronn/tunshell_mcp_agents" rel="noopener noreferrer"&gt;source and docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>claude</category>
      <category>mcp</category>
      <category>backend</category>
    </item>
    <item>
      <title>Add authentication to your Nuxt 3 and Vue 3 applications (Logto)</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Mon, 22 Dec 2025 11:45:51 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/add-authentication-to-your-nuxt-3-and-vue-3-applications-logto-4oeh</link>
      <guid>https://dev.to/bitwiserokos/add-authentication-to-your-nuxt-3-and-vue-3-applications-logto-4oeh</guid>
      <description>&lt;p&gt;Logto provides official SDKs for multiple frameworks, but the &lt;strong&gt;integration approach depends on your app type&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSR / full-stack frameworks (Nuxt, Next.js, etc.)&lt;/strong&gt; typically need a server-aware SDK that can safely store secrets and manage redirect-based flows on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SPA frameworks (Vue, React, etc.)&lt;/strong&gt; typically use a client-side SDK and rely on redirect-based login without server secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this guide, you’ll implement Logto authentication &lt;strong&gt;twice&lt;/strong&gt;:&lt;br&gt;
1) &lt;strong&gt;Nuxt 3 (SSR)&lt;/strong&gt; using &lt;code&gt;@logto/nuxt&lt;/code&gt;&lt;br&gt;&lt;br&gt;
2) &lt;strong&gt;Vue 3 (SPA)&lt;/strong&gt; using Logto’s Vue SDK&lt;/p&gt;

&lt;p&gt;No fluff — just a production-ready, copy-paste-friendly walkthrough.&lt;/p&gt;


&lt;h2&gt;
  
  
  Before you start: create a Logto app in the Console
&lt;/h2&gt;

&lt;p&gt;In Logto Console, create an application that matches your framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;Nuxt 3&lt;/strong&gt;, create a &lt;strong&gt;Traditional Web&lt;/strong&gt; application.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Vue 3&lt;/strong&gt;, create a &lt;strong&gt;Single Page Application (SPA)&lt;/strong&gt; application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll need these values for both setups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Endpoint&lt;/strong&gt; (your &lt;a href="https://logto.io/" rel="noopener noreferrer"&gt;https://logto.io/&lt;/a&gt; tenant URL)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;App ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;(Nuxt only) &lt;strong&gt;App Secret&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  Part 1 — Nuxt 3 (SSR): add authentication with &lt;code&gt;@logto/nuxt&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Nuxt is often deployed with SSR (or hybrid rendering). Logto’s Nuxt SDK is designed for SSR and uses secure cookies + server-side handling to complete authentication.&lt;/p&gt;
&lt;h2&gt;
  
  
  1) Install the Nuxt SDK
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @logto/nuxt
&lt;span class="c"&gt;# or&lt;/span&gt;
pnpm add @logto/nuxt
&lt;span class="c"&gt;# or&lt;/span&gt;
yarn add @logto/nuxt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  2) Add environment variables
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file:&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="nv"&gt;NUXT_LOGTO_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://your-tenant.logto.app"&lt;/span&gt;
&lt;span class="nv"&gt;NUXT_LOGTO_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-app-id"&lt;/span&gt;
&lt;span class="nv"&gt;NUXT_LOGTO_APP_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-app-secret"&lt;/span&gt;
&lt;span class="nv"&gt;NUXT_LOGTO_COOKIE_ENCRYPTION_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"a-strong-random-string"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;code&gt;NUXT_LOGTO_COOKIE_ENCRYPTION_KEY&lt;/code&gt; must be a strong random string because it protects encrypted auth cookies.&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Configure &lt;code&gt;nuxt.config.ts&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@logto/nuxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;runtimeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;logto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUXT_LOGTO_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUXT_LOGTO_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;appSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUXT_LOGTO_APP_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cookieEncryptionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUXT_LOGTO_COOKIE_ENCRYPTION_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4) Configure redirect URIs in Logto Console
&lt;/h2&gt;

&lt;p&gt;Assuming your Nuxt app runs on &lt;code&gt;http://localhost:3000&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redirect URI&lt;/strong&gt;: &lt;code&gt;http://localhost:3000/callback&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post sign-out redirect URI&lt;/strong&gt;: &lt;code&gt;http://localhost:3000/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These must match the callback and return URLs used by the SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  5) Understand the built-in routes
&lt;/h2&gt;

&lt;p&gt;The Nuxt module provides built-in routes for the auth flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/sign-in&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/sign-out&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/callback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can customize them if you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;logto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pathnames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/logout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you change the callback path, update the redirect URI in Logto Console accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  6) Implement a simple Sign in / Sign out button
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- pages/index.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLogtoUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;NuxtLink&lt;/span&gt; &lt;span class="na"&gt;:to=&lt;/span&gt;&lt;span class="s"&gt;"`/sign-$&lt;/span&gt;{user ? 'out' : 'in'}`"&amp;gt;
    Sign &lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/NuxtLink&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-top: 16px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useLogtoUser()&lt;/code&gt; is reactive — it updates when the session changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  7) Request additional user claims (scopes)
&lt;/h2&gt;

&lt;p&gt;By default, you get basic OIDC claims. If you need more (e.g., email/phone), add scopes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nuxt.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserScope&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@logto/nuxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;logto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;UserScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UserScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Phone&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8) Use the Logto client (server-only)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useLogtoClient()&lt;/code&gt; is server-only. On the client it returns &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example: fetch user info on the server once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// composables/useServerUserInfo.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLogtoClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callOnce&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#imports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useServerUserInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLogtoClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logto-user-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="nx"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchUserInfo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;userInfo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Part 2 — Vue 3 (SPA): add authentication with the Vue SDK
&lt;/h1&gt;

&lt;p&gt;Vue SPAs don’t have a server runtime to securely store secrets, so the recommended setup uses the Logto Vue SDK and SPA application settings in Logto Console.&lt;/p&gt;

&lt;h2&gt;
  
  
  1) Install the Vue SDK
&lt;/h2&gt;

&lt;p&gt;Install Logto’s Vue SDK (Vue 3 only):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @logto/vue
&lt;span class="c"&gt;# or&lt;/span&gt;
pnpm add @logto/vue
&lt;span class="c"&gt;# or&lt;/span&gt;
yarn add @logto/vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2) Configure redirect URIs in Logto Console
&lt;/h2&gt;

&lt;p&gt;Assuming your Vue app runs on &lt;code&gt;http://localhost:5173&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redirect URI&lt;/strong&gt;: &lt;code&gt;http://localhost:5173/callback&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post sign-out redirect URI&lt;/strong&gt;: &lt;code&gt;http://localhost:5173/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Use your actual dev server origin if it’s different.)&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Initialize Logto in &lt;code&gt;main.ts&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/main.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createLogto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@logto/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;createLogto&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_LOGTO_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_LOGTO_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; variables:&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="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;VITE_LOGTO_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://your-tenant.logto.app"&lt;/span&gt;
&lt;span class="nv"&gt;VITE_LOGTO_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-app-id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4) Add a callback route
&lt;/h2&gt;

&lt;p&gt;In SPAs, you must handle the redirect callback route.&lt;/p&gt;

&lt;p&gt;Example with Vue Router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/router/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createWebHistory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../pages/Home.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Callback&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../pages/Callback.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;createRouter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;createWebHistory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Callback&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Callback page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/pages/Callback.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMounted&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLogto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@logto/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handleSignInCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLogto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleSignInCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Signing you in…&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5) Sign in / Sign out buttons
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- src/pages/Home.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLogto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@logto/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getIdTokenClaims&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLogto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSignIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_LOGTO_REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSignOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_LOGTO_POST_SIGN_OUT_REDIRECT_URI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"!isAuthenticated"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"handleSignIn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Sign in
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"handleSignOut"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Sign out
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add redirect-related env vars:&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="nv"&gt;VITE_LOGTO_REDIRECT_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:5173/callback"&lt;/span&gt;
&lt;span class="nv"&gt;VITE_LOGTO_POST_SIGN_OUT_REDIRECT_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:5173/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to display the current user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLogto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@logto/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getIdTokenClaims&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLogto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadClaims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getIdTokenClaims&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"isAuthenticated"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"loadClaims"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Load user claims&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"claims"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Choosing between Nuxt and Vue integrations
&lt;/h2&gt;

&lt;p&gt;If you are building a Nuxt app with SSR (or hybrid rendering), prefer &lt;strong&gt;&lt;code&gt;@logto/nuxt&lt;/code&gt;&lt;/strong&gt; because it’s designed for server-side flows and secure cookie handling.&lt;/p&gt;

&lt;p&gt;If you are building a Vue SPA (no server runtime), use &lt;strong&gt;&lt;code&gt;@logto/vue&lt;/code&gt;&lt;/strong&gt; and the SPA application type in Logto Console.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick sanity check
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clicking &lt;strong&gt;Sign in&lt;/strong&gt; should redirect you to Logto’s hosted sign-in screen.&lt;/li&gt;
&lt;li&gt;After signing in, you should land on your callback route and end up back in the app.&lt;/li&gt;
&lt;li&gt;Clicking &lt;strong&gt;Sign out&lt;/strong&gt; should clear the shared session and return you to your post sign-out URL.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;p&gt;This document intentionally keeps the examples minimal and production-oriented. Once authentication works, the next steps are typically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protecting routes (Nuxt middleware / Vue router guards)&lt;/li&gt;
&lt;li&gt;Calling your APIs with access tokens&lt;/li&gt;
&lt;li&gt;Adding organization/roles/permissions if your product needs it&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Algolia Search in Nuxt 3: Production-Ready Integration Guide</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Mon, 22 Dec 2025 10:33:23 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/algolia-search-in-nuxt-3-production-ready-integration-guide-18fg</link>
      <guid>https://dev.to/bitwiserokos/algolia-search-in-nuxt-3-production-ready-integration-guide-18fg</guid>
      <description>&lt;p&gt;Algolia is a hosted search engine designed for speed, relevance, and scalability. When combined with Nuxt 3, it allows you to build fast, SEO-friendly search experiences with minimal effort and full SSR support.&lt;/p&gt;

&lt;p&gt;This guide walks through a &lt;strong&gt;practical, production-ready integration of Algolia in a Nuxt 3 application&lt;/strong&gt; using the official &lt;code&gt;@nuxtjs/algolia&lt;/code&gt; module. The focus is on real usage patterns, not abstractions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Use Algolia with Nuxt 3
&lt;/h2&gt;

&lt;p&gt;Nuxt 3 applications often require advanced search capabilities for blogs, documentation, dashboards, and e-commerce platforms. Algolia fits naturally into this ecosystem because it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast full-text search&lt;/li&gt;
&lt;li&gt;Built-in typo tolerance and ranking&lt;/li&gt;
&lt;li&gt;Seamless SSR compatibility&lt;/li&gt;
&lt;li&gt;Auto-imported composables in Nuxt&lt;/li&gt;
&lt;li&gt;Type-safe APIs with first-class TypeScript support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official Nuxt Algolia module removes most of the boilerplate and exposes clean, composable APIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Before starting, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Nuxt 3 project&lt;/li&gt;
&lt;li&gt;An Algolia account&lt;/li&gt;
&lt;li&gt;At least one Algolia index with data&lt;/li&gt;
&lt;li&gt;Algolia Application ID and Search API Key&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Installing the Algolia Module
&lt;/h2&gt;

&lt;p&gt;Install the official Nuxt module using the Nuxt CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nuxi@latest module add algolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with pnpm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add @nuxtjs/algolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module will be registered automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;Store Algolia credentials in environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALGOLIA_APPLICATION_ID=YOUR_APP_ID
ALGOLIA_API_KEY=YOUR_SEARCH_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never commit API keys to version control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nuxt Configuration
&lt;/h2&gt;

&lt;p&gt;Enable the module in &lt;code&gt;nuxt.config.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nuxtjs/algolia&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;algolia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// optional configuration&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module automatically reads credentials from environment variables.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Search Implementation
&lt;/h2&gt;

&lt;p&gt;The most common way to query Algolia is via &lt;code&gt;useAlgoliaSearch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAlgoliaSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Laptop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"item in result.hits"&lt;/span&gt; &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"item.objectID"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This performs a client-side search against the specified index.&lt;/p&gt;




&lt;h2&gt;
  
  
  Server-Side Search with SSR
&lt;/h2&gt;

&lt;p&gt;For SEO-critical pages, use &lt;code&gt;useAsyncAlgoliaSearch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pending&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;useAsyncAlgoliaSearch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Phone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"pending"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading…&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This executes during server rendering and works seamlessly with Nuxt SSR.&lt;/p&gt;




&lt;h2&gt;
  
  
  Advanced Search Features
&lt;/h2&gt;

&lt;p&gt;The module exposes additional composables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;useAlgoliaFacetedSearch&lt;/code&gt; for filters and facets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useAlgoliaInitIndex&lt;/code&gt; for index initialization&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useAlgoliaRecommend&lt;/code&gt; for recommendations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useAlgoliaRef&lt;/code&gt; for direct client access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cover most advanced search scenarios without manual client setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Edge and Cloudflare Support
&lt;/h2&gt;

&lt;p&gt;When deploying to edge runtimes like Cloudflare Workers, enable fetch-based transport.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;algolia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;useFetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures compatibility with restricted runtime environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Problems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No search results&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check index name&lt;/li&gt;
&lt;li&gt;Verify index contains data&lt;/li&gt;
&lt;li&gt;Confirm API key permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SSR errors&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;useAsyncAlgoliaSearch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable &lt;code&gt;useFetch&lt;/code&gt; for edge runtimes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;TypeScript issues&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update to the latest module version&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Production Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use SSR search only where SEO matters&lt;/li&gt;
&lt;li&gt;Cache popular queries&lt;/li&gt;
&lt;li&gt;Never expose Admin API keys&lt;/li&gt;
&lt;li&gt;Index only searchable fields&lt;/li&gt;
&lt;li&gt;Separate read and write operations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;The official Algolia module for Nuxt 3 provides a clean and scalable way to add search to modern applications. With minimal configuration, you get fast search, SSR compatibility, and a composable API that fits naturally into the Nuxt ecosystem.&lt;/p&gt;

&lt;p&gt;This setup is suitable for both small projects and large production systems and can be extended with InstantSearch or recommendation features when needed.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Digler — Open-Source Disk Forensics and File Recovery Tool</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Sun, 21 Dec 2025 17:13:30 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/digler-open-source-disk-forensics-and-file-recovery-tool-3nb</link>
      <guid>https://dev.to/bitwiserokos/digler-open-source-disk-forensics-and-file-recovery-tool-3nb</guid>
      <description>&lt;h1&gt;
  
  
  File Browser — Open-Source Web File Manager You Can Self-Host
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Digler — Open-Source Disk Forensics and File Recovery Tool
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Digler&lt;/strong&gt; is an open-source &lt;strong&gt;disk forensics and file recovery utility&lt;/strong&gt; designed for analyzing raw disks and disk images. It is written in &lt;strong&gt;Go&lt;/strong&gt;, distributed as a CLI tool, and focuses on &lt;strong&gt;recovering deleted files and extracting forensic metadata&lt;/strong&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%2Fq48xda5rjxcijeclhxl4.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%2Fq48xda5rjxcijeclhxl4.png" alt=" " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;a href="https://track.appstash.xyz/digler-opensource" rel="noopener noreferrer"&gt;Quick and Easy Installation&lt;/a&gt;
&lt;/h1&gt;




&lt;h2&gt;
  
  
  What Problem Does Digler Solve?
&lt;/h2&gt;

&lt;p&gt;When files are deleted, the file system metadata may be lost, but the raw data often remains on disk. Digler helps by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scanning raw disks or disk images&lt;/li&gt;
&lt;li&gt;carving files without filesystem metadata&lt;/li&gt;
&lt;li&gt;extracting forensic evidence&lt;/li&gt;
&lt;li&gt;producing machine-readable forensic reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;digital forensics&lt;/li&gt;
&lt;li&gt;incident response&lt;/li&gt;
&lt;li&gt;data recovery&lt;/li&gt;
&lt;li&gt;security research&lt;/li&gt;
&lt;li&gt;educational labs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SEO keywords covered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;disk forensics tool, file recovery CLI, digital forensics Go, raw disk analysis, DFXML&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔍 Scan raw disks and disk images&lt;/li&gt;
&lt;li&gt;🧩 File carving without filesystem metadata&lt;/li&gt;
&lt;li&gt;📄 DFXML (Digital Forensics XML) report generation&lt;/li&gt;
&lt;li&gt;🧱 Modular plugin-based architecture&lt;/li&gt;
&lt;li&gt;⚡ Cross-platform (Linux, macOS, Windows)&lt;/li&gt;
&lt;li&gt;🔓 Open-source&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Disk / Image
   ↓
Block Scanner
   ↓
Signature Detection
   ↓
File Carving Engine
   ↓
Recovered Files + DFXML Report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin system allows Digler to recognize multiple file formats.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Go&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface:&lt;/strong&gt; CLI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; Files + DFXML XML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; Modular / plugin-based&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;




&lt;h1&gt;
  
  
  &lt;a href="https://track.appstash.xyz/digler-opensource" rel="noopener noreferrer"&gt;Start Quickly with Easy Installation&lt;/a&gt;
&lt;/h1&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%2Fbk5eurobf9uq6xlie0se.gif" 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%2Fbk5eurobf9uq6xlie0se.gif" alt=" " width="760" height="760"&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%2F4hwgf5u09mqo3l8ou8o7.gif" 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%2F4hwgf5u09mqo3l8ou8o7.gif" alt=" " width="760" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build from Source&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ostafen/digler.git
&lt;span class="nb"&gt;cd &lt;/span&gt;digler
make build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiled binary will be available in the &lt;code&gt;bin/&lt;/code&gt; directory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scan a Disk Image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;digler scan disk.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Recover Files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;digler recover disk.img &lt;span class="nt"&gt;--output&lt;/span&gt; recovered_files/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Generate Forensic Report
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;digler scan disk.img &lt;span class="nt"&gt;--dfxml&lt;/span&gt; report.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DFXML file can be used in forensic pipelines and automated analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Plugin System
&lt;/h2&gt;

&lt;p&gt;Digler uses plugins to identify and recover file types.&lt;/p&gt;

&lt;p&gt;Each plugin defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;file signature&lt;/li&gt;
&lt;li&gt;block structure&lt;/li&gt;
&lt;li&gt;extraction logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows easy extension for new formats.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Digital forensics investigations&lt;/li&gt;
&lt;li&gt;Malware and incident response&lt;/li&gt;
&lt;li&gt;Recovering accidentally deleted files&lt;/li&gt;
&lt;li&gt;Security training and labs&lt;/li&gt;
&lt;li&gt;Research on file systems&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Safety Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Always work on disk images, not live disks&lt;/li&gt;
&lt;li&gt;Use read-only access when possible&lt;/li&gt;
&lt;li&gt;Ensure you have legal authorization to analyze data&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Digler Is Worth Using
&lt;/h2&gt;

&lt;p&gt;Digler is valuable because it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focuses on raw forensic workflows&lt;/li&gt;
&lt;li&gt;avoids unnecessary UI complexity&lt;/li&gt;
&lt;li&gt;produces structured forensic output&lt;/li&gt;
&lt;li&gt;is easy to extend and audit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a solid open-source alternative for forensic tooling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;disk images&lt;/li&gt;
&lt;li&gt;forensic analysis&lt;/li&gt;
&lt;li&gt;data recovery&lt;/li&gt;
&lt;li&gt;security tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Digler is worth exploring and learning from.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>web</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>File Browser — Open-Source Web File Manager You Can Self-Host</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Sun, 21 Dec 2025 17:09:14 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/file-browser-open-source-web-file-manager-you-can-self-host-5chp</link>
      <guid>https://dev.to/bitwiserokos/file-browser-open-source-web-file-manager-you-can-self-host-5chp</guid>
      <description>&lt;h1&gt;
  
  
  File Browser — Open-Source Web File Manager You Can Self-Host
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;File Browser&lt;/strong&gt; is a fast, lightweight, open-source &lt;strong&gt;web-based file manager&lt;/strong&gt; that lets you manage files on a server through a clean browser UI.&lt;/p&gt;

&lt;p&gt;It’s written in &lt;strong&gt;Go&lt;/strong&gt;, distributed as a single binary, and designed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easy to deploy&lt;/li&gt;
&lt;li&gt;easy to secure&lt;/li&gt;
&lt;li&gt;easy to self-host&lt;/li&gt;
&lt;/ul&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%2F6p5awu5asn9ebuk8lkt9.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%2F6p5awu5asn9ebuk8lkt9.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/gtsteffaniak/filebrowser" rel="noopener noreferrer"&gt;https://github.com/gtsteffaniak/filebrowser&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Why File Browser Is So Popular
&lt;/h2&gt;

&lt;p&gt;File Browser solves a very practical problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I want to manage files on my server without SSH, FTP, or heavy admin panels.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of complex setups, File Browser gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a web UI&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;file operations&lt;/li&gt;
&lt;li&gt;user permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in one small binary.&lt;/p&gt;

&lt;p&gt;SEO keywords naturally included:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;open-source file manager, web file browser, self-hosted file manager, Go web application, server file management&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📁 Browse files and directories from your browser&lt;/li&gt;
&lt;li&gt;⬆️ Upload / download files&lt;/li&gt;
&lt;li&gt;✏️ Rename, move, copy, delete&lt;/li&gt;
&lt;li&gt;👥 User and permission management&lt;/li&gt;
&lt;li&gt;🔐 Authentication support&lt;/li&gt;
&lt;li&gt;⚙️ Configurable root directory&lt;/li&gt;
&lt;li&gt;🖥️ Runs as a single Go binary&lt;/li&gt;
&lt;li&gt;🐧 Cross-platform (Linux, macOS, Windows)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser UI
   ↓
HTTP API
   ↓
File system access
   ↓
Config / auth layer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is no heavy database dependency by default, which keeps the system simple and fast.&lt;/p&gt;


&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Go&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Embedded web UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Go HTTP server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; Local filesystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Single binary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes File Browser ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPS servers&lt;/li&gt;
&lt;li&gt;NAS devices&lt;/li&gt;
&lt;li&gt;homelabs&lt;/li&gt;
&lt;li&gt;internal tools&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Installation Guide
&lt;/h2&gt;

&lt;p&gt;File Browser is extremely easy to install.&lt;/p&gt;


&lt;h1&gt;
  
  
  Option 1
&lt;/h1&gt;
&lt;h2&gt;
  
  
  &lt;a href="https://track.appstash.xyz/fileborwser-opensource" rel="noopener noreferrer"&gt;Quick and Easy Installation &lt;/a&gt;
&lt;/h2&gt;


&lt;h1&gt;
  
  
  Option 2: Download Prebuilt Binary (Recommended)
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Go to the releases page
&lt;/li&gt;
&lt;li&gt;Download the binary for your OS
&lt;/li&gt;
&lt;li&gt;Make it executable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example (Linux / macOS):&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;chmod&lt;/span&gt; +x filebrowser
./filebrowser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Option 2: Install via Package Managers
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Homebrew (macOS / Linux)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;filebrowser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Docker
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run   &lt;span class="nt"&gt;-v&lt;/span&gt; /path/to/your/files:/srv   &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80   filebrowser/filebrowser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Option 3: Build from Source
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/gtsteffaniak/filebrowser.git
&lt;span class="nb"&gt;cd &lt;/span&gt;filebrowser
go build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  First Run &amp;amp; Login
&lt;/h2&gt;

&lt;p&gt;On first launch, File Browser will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a configuration file&lt;/li&gt;
&lt;li&gt;start a web server (default port: 80 or 8080)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open in browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Username:&lt;/strong&gt; admin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password:&lt;/strong&gt; admin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Change credentials immediately after login.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Configuration
&lt;/h2&gt;

&lt;p&gt;Common flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;filebrowser   &lt;span class="nt"&gt;--root&lt;/span&gt; /path/to/files   &lt;span class="nt"&gt;--address&lt;/span&gt; 0.0.0.0   &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which directory is exposed&lt;/li&gt;
&lt;li&gt;network interface&lt;/li&gt;
&lt;li&gt;port&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;File Browser is widely used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;managing files on VPS servers&lt;/li&gt;
&lt;li&gt;NAS and home servers&lt;/li&gt;
&lt;li&gt;DevOps workflows&lt;/li&gt;
&lt;li&gt;temporary file sharing&lt;/li&gt;
&lt;li&gt;internal admin panels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s especially useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH access is limited&lt;/li&gt;
&lt;li&gt;non-technical users need access&lt;/li&gt;
&lt;li&gt;you want a lightweight solution&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Security Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Always change default credentials&lt;/li&gt;
&lt;li&gt;Use HTTPS (reverse proxy recommended)&lt;/li&gt;
&lt;li&gt;Limit root directory access&lt;/li&gt;
&lt;li&gt;Use firewall rules where possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;File Browser is simple, but security is still your responsibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Project Is Worth Using
&lt;/h2&gt;

&lt;p&gt;File Browser stands out because it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;does one thing very well&lt;/li&gt;
&lt;li&gt;avoids unnecessary complexity&lt;/li&gt;
&lt;li&gt;is easy to audit&lt;/li&gt;
&lt;li&gt;is easy to deploy anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like &lt;strong&gt;small, focused open-source tools&lt;/strong&gt;, this is a must-have.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;File Browser is a great example of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pragmatic Go development&lt;/li&gt;
&lt;li&gt;clean self-hosted tooling&lt;/li&gt;
&lt;li&gt;real-world open-source utility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐ Star the repository if you use it or learn from it.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>web</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>PearPass Desktop — Open-Source Peer-to-Peer Password Manager Built on Pear Runtime</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Sun, 21 Dec 2025 17:04:16 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/pearpass-desktop-open-source-peer-to-peer-password-manager-built-on-pear-runtime-1kop</link>
      <guid>https://dev.to/bitwiserokos/pearpass-desktop-open-source-peer-to-peer-password-manager-built-on-pear-runtime-1kop</guid>
      <description>&lt;h1&gt;
  
  
  PearPass Desktop — Open-Source Peer-to-Peer Password Manager Built on Pear Runtime
&lt;/h1&gt;

&lt;p&gt;If you’re tired of “password managers” that still depend on centralized cloud servers, &lt;strong&gt;PearPass Desktop&lt;/strong&gt; is a refreshing direction: a &lt;strong&gt;distributed password manager&lt;/strong&gt; built on &lt;strong&gt;Pear Runtime&lt;/strong&gt;, designed to keep your vault data &lt;strong&gt;on your devices&lt;/strong&gt; and sync it across your own endpoints.&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%2Fg960sj0wky9r0d9vaigr.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%2Fg960sj0wky9r0d9vaigr.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;a href="https://track.appstash.xyz/perpass" rel="noopener noreferrer"&gt;Download now&lt;/a&gt;
&lt;/h1&gt;




&lt;h2&gt;
  
  
  Why This Project Is Cool (and Why Devs Should Care)
&lt;/h2&gt;

&lt;p&gt;PearPass Desktop is interesting for more than just end-user security:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Peer-to-peer / distributed sync mindset&lt;/strong&gt; (no classic “one cloud to breach” architecture)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-source by default&lt;/strong&gt; (easier to audit and extend)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern desktop stack&lt;/strong&gt; (Pear Runtime + React ecosystem)&lt;/li&gt;
&lt;li&gt;Great real-world reference for:

&lt;ul&gt;
&lt;li&gt;crypto + security UX&lt;/li&gt;
&lt;li&gt;local-first apps&lt;/li&gt;
&lt;li&gt;end-to-end encryption product design&lt;/li&gt;
&lt;li&gt;multi-device sync without centralized infra&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;SEO keywords naturally covered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;open-source password manager, peer-to-peer password vault, local-first security app, end-to-end encrypted vault, Pear Runtime desktop app&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Features (What You Get)
&lt;/h2&gt;

&lt;p&gt;PearPass Desktop focuses on secure storage and usability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure storage for &lt;strong&gt;passwords, identities, credit cards, notes, and custom fields&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-device and cross-platform synchronization&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline access&lt;/strong&gt; (local-first usage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt; for vault security&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password strength analysis&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Random password generator&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Simple, clean UI&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;PearPass Desktop follows a local-first model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI (React)
  ↓
Vault / state management
  ↓
Local encrypted storage
  ↓
Peer-to-peer distribution (Pear Runtime)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means your “source of truth” is your devices, not a centralized web account.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started (Installation &amp;amp; Dev Setup)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This repo is primarily a &lt;strong&gt;developer setup&lt;/strong&gt; flow (clone → install deps → run via Pear).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  0) Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; (match the version in &lt;code&gt;.nvmrc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pear Runtime&lt;/strong&gt; installed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check Node version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  1) Clone the Repo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tetherto/pearpass-app-desktop.git
&lt;span class="nb"&gt;cd &lt;/span&gt;pearpass-app-desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2) Update Submodules
&lt;/h3&gt;

&lt;p&gt;PearPass uses submodules. Update them with the provided script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run update-submodules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need a specific remote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run update-submodules &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;remote-name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3) Install Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4) Generate i18n (Translations)
&lt;/h3&gt;

&lt;p&gt;PearPass uses Lingui. Generate and compile message catalogs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run lingui:extract
npm run lingui:compile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5) Run the Desktop App (Dev Mode)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pear run &lt;span class="nt"&gt;--dev&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is set correctly, the app should launch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;PearPass uses Jest for unit testing.&lt;/p&gt;

&lt;p&gt;Run tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Usage: What to Try First
&lt;/h2&gt;

&lt;p&gt;Once the app runs, a good “first session” checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a vault and set a strong master password&lt;/li&gt;
&lt;li&gt;Add sample entries:

&lt;ul&gt;
&lt;li&gt;login&lt;/li&gt;
&lt;li&gt;note&lt;/li&gt;
&lt;li&gt;identity&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Try password generator + strength checks&lt;/li&gt;
&lt;li&gt;Explore sync / distribution options (if you have multiple devices)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pear Runtime&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Styled Components&lt;/li&gt;
&lt;li&gt;Redux&lt;/li&gt;
&lt;li&gt;Lingui (i18n)&lt;/li&gt;
&lt;li&gt;Jest (tests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a great repo to learn how a security-focused desktop app structures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;state&lt;/li&gt;
&lt;li&gt;encryption boundaries&lt;/li&gt;
&lt;li&gt;UX flows for sensitive data&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Who Should Fork This?
&lt;/h2&gt;

&lt;p&gt;This repo is perfect if you want to build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a local-first password manager fork&lt;/li&gt;
&lt;li&gt;a secure “vault” module for another app&lt;/li&gt;
&lt;li&gt;P2P sync experiments&lt;/li&gt;
&lt;li&gt;privacy-first productivity tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add hardware key / OS keychain integrations&lt;/li&gt;
&lt;li&gt;add vault export formats&lt;/li&gt;
&lt;li&gt;add threat-model docs + security tooling&lt;/li&gt;
&lt;li&gt;build a plugin system for record types&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Related Projects in the Ecosystem
&lt;/h2&gt;

&lt;p&gt;PearPass also has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser extension&lt;/li&gt;
&lt;li&gt;mobile app&lt;/li&gt;
&lt;li&gt;vault core libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want full-stack parity (desktop + browser autofill), look at the extension repo too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;PearPass Desktop is one of those repos that’s both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;immediately useful&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;and a &lt;strong&gt;very teachable architecture&lt;/strong&gt; for local-first security apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re exploring modern, open-source security software — this is absolutely worth starring and reading.&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>javascript</category>
      <category>passwordmanager</category>
    </item>
    <item>
      <title>BookHunter — Open-Source CLI Tool for Downloading and Managing eBooks</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Sun, 21 Dec 2025 17:01:29 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/bookhunter-open-source-cli-tool-for-downloading-and-managing-ebooks-502h</link>
      <guid>https://dev.to/bitwiserokos/bookhunter-open-source-cli-tool-for-downloading-and-managing-ebooks-502h</guid>
      <description>&lt;h1&gt;
  
  
  BookHunter — Open-Source CLI Tool for Downloading and Managing eBooks
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;BookHunter&lt;/strong&gt; is an open-source command-line tool written in &lt;strong&gt;Go&lt;/strong&gt; that allows users to download and manage eBooks from multiple online sources using a single unified CLI interface.&lt;/p&gt;

&lt;p&gt;The project focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automation&lt;/li&gt;
&lt;li&gt;multi-source support&lt;/li&gt;
&lt;li&gt;cross-platform binaries&lt;/li&gt;
&lt;li&gt;fast and simple CLI usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/bookstairs/bookhunter/" rel="noopener noreferrer"&gt;https://github.com/bookstairs/bookhunter/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What Problem Does BookHunter Solve?
&lt;/h2&gt;

&lt;p&gt;Downloading large collections of eBooks manually is time-consuming and repetitive.&lt;br&gt;&lt;br&gt;
BookHunter automates this process by providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a single CLI tool instead of multiple scripts&lt;/li&gt;
&lt;li&gt;built-in parsers for different sources&lt;/li&gt;
&lt;li&gt;multi-threaded downloads&lt;/li&gt;
&lt;li&gt;consistent output formats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;personal libraries&lt;/li&gt;
&lt;li&gt;research collections&lt;/li&gt;
&lt;li&gt;automation workflows&lt;/li&gt;
&lt;li&gt;CLI tool practice and learning&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📚 Download eBooks from multiple sources&lt;/li&gt;
&lt;li&gt;⚡ Fast, multi-threaded downloads&lt;/li&gt;
&lt;li&gt;🖥️ Cross-platform (Windows, macOS, Linux)&lt;/li&gt;
&lt;li&gt;🧰 Simple and consistent CLI interface&lt;/li&gt;
&lt;li&gt;📦 Supports common formats (PDF, EPUB, AZW3)&lt;/li&gt;
&lt;li&gt;🔓 Open-source (MIT License)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SEO keywords covered naturally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ebook downloader CLI, Go command line tool, open-source ebook downloader, automation with Go&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Supported Sources
&lt;/h2&gt;

&lt;p&gt;Depending on configuration and version, BookHunter supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SoBooks&lt;/li&gt;
&lt;li&gt;TaleBook&lt;/li&gt;
&lt;li&gt;Telegram channels&lt;/li&gt;
&lt;li&gt;Other community-maintained sources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each source is implemented as a separate command module.&lt;/p&gt;


&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bookhunter (CLI)
├── source parsers
├── download manager
├── concurrency control
├── format handling
└── output directory management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The modular design makes it easy to extend with new sources.&lt;/p&gt;


&lt;h2&gt;
  
  
  Installation Guide
&lt;/h2&gt;

&lt;p&gt;BookHunter provides prebuilt binaries for all major platforms.&lt;/p&gt;


&lt;h3&gt;
  
  
  macOS / Linux (Homebrew)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap bookstairs/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;bookhunter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Windows (Scoop)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;scoop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bookstairs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/bookstairs/scoop-bucket&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;scoop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bookstairs/bookhunter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manual Installation (All Platforms)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the GitHub releases page
&lt;/li&gt;
&lt;li&gt;Download the archive for your OS
&lt;/li&gt;
&lt;li&gt;Extract the binary
&lt;/li&gt;
&lt;li&gt;Move it to a directory in your PATH
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example (Linux / macOS):&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;chmod&lt;/span&gt; +x bookhunter
&lt;span class="nb"&gt;mv &lt;/span&gt;bookhunter /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Verify Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bookhunter &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If installed correctly, you should see the available commands and options.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Download from SoBooks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bookhunter sobooks   &lt;span class="nt"&gt;--download&lt;/span&gt; ./ebooks   &lt;span class="nt"&gt;--thread&lt;/span&gt; 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Download from Telegram Channel
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bookhunter telegram   &lt;span class="nt"&gt;--appID&lt;/span&gt; YOUR_APP_ID   &lt;span class="nt"&gt;--appHash&lt;/span&gt; YOUR_APP_HASH   &lt;span class="nt"&gt;--channelID&lt;/span&gt; https://t.me/example_channel   &lt;span class="nt"&gt;--download&lt;/span&gt; ./tgbooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will need Telegram API credentials for this command.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Options
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--download&lt;/code&gt; — output directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--thread&lt;/code&gt; — number of concurrent downloads&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--proxy&lt;/code&gt; — use HTTP/SOCKS proxy&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--verbose&lt;/code&gt; — detailed logs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Building a personal digital library&lt;/li&gt;
&lt;li&gt;Automating downloads in scripts&lt;/li&gt;
&lt;li&gt;Learning Go by reading real-world CLI code&lt;/li&gt;
&lt;li&gt;Teaching CLI tools and concurrency concepts&lt;/li&gt;
&lt;li&gt;Research and offline reading workflows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Limitations and Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Availability of sources may change&lt;/li&gt;
&lt;li&gt;Download speed depends on source limits&lt;/li&gt;
&lt;li&gt;Users are responsible for complying with local laws and content licenses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BookHunter is a &lt;strong&gt;tool&lt;/strong&gt;, not a content provider.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Project Is Interesting
&lt;/h2&gt;

&lt;p&gt;BookHunter is a good example of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clean Go CLI design&lt;/li&gt;
&lt;li&gt;modular architecture&lt;/li&gt;
&lt;li&gt;real-world automation use cases&lt;/li&gt;
&lt;li&gt;cross-platform distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s useful both as a tool and as a learning resource.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;If you are interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI tools&lt;/li&gt;
&lt;li&gt;Go automation&lt;/li&gt;
&lt;li&gt;managing large collections of files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BookHunter is worth checking out.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>opensource</category>
      <category>books</category>
      <category>automation</category>
    </item>
    <item>
      <title>DiscovAI Search — Open‑Source AI Search Engine for Tools, Docs, and Custom Data</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Sun, 21 Dec 2025 16:53:36 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/discovai-search-open-source-ai-search-engine-for-tools-docs-and-custom-data-4ko0</link>
      <guid>https://dev.to/bitwiserokos/discovai-search-open-source-ai-search-engine-for-tools-docs-and-custom-data-4ko0</guid>
      <description>&lt;h1&gt;
  
  
  DiscovAI Search — Open‑Source AI Search Engine for Tools and Knowledge Bases
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;DiscovAI Search&lt;/strong&gt; is an open‑source, AI‑powered search engine designed to index, understand, and search AI tools and custom knowledge bases using modern &lt;strong&gt;vector search + LLM reasoning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The project combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;semantic search (embeddings)&lt;/li&gt;
&lt;li&gt;fast caching (Redis)&lt;/li&gt;
&lt;li&gt;structured storage (Supabase / PostgreSQL)&lt;/li&gt;
&lt;li&gt;modern frontend (Next.js)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is suitable both as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;production-ready AI search layer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;educational reference project&lt;/strong&gt; for AI + web developers&lt;/li&gt;
&lt;/ul&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%2F12cbv1idnha4dqgyw74c.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%2F12cbv1idnha4dqgyw74c.png" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/DiscovAI/DiscovAI-search/" rel="noopener noreferrer"&gt;https://github.com/DiscovAI/DiscovAI-search/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What Problem Does DiscovAI Search Solve?
&lt;/h2&gt;

&lt;p&gt;Traditional keyword search fails when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;queries are vague or natural language&lt;/li&gt;
&lt;li&gt;data is unstructured&lt;/li&gt;
&lt;li&gt;users don’t know exact keywords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DiscovAI Search solves this by using &lt;strong&gt;semantic vector search&lt;/strong&gt;, allowing users to search by &lt;em&gt;meaning&lt;/em&gt;, not exact words.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“tools for image generation”
&lt;/li&gt;
&lt;li&gt;“AI for document summarization”
&lt;/li&gt;
&lt;li&gt;“open-source alternatives to ChatGPT plugins”&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔍 &lt;strong&gt;Semantic Search with Embeddings&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;LLM‑powered answer generation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Redis caching for fast responses&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🗄️ &lt;strong&gt;Supabase (PostgreSQL + pgvector) backend&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Next.js frontend&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🔓 &lt;strong&gt;Fully open source&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SEO keywords naturally covered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI search engine, vector search, semantic search, OpenAI embeddings, LLM search, Next.js AI app&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  High‑Level Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query
   ↓
Next.js API Route
   ↓
Embedding (OpenAI)
   ↓
Vector Search (Supabase / pgvector)
   ↓
Redis Cache (optional)
   ↓
LLM‑generated response
   ↓
UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This design keeps the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scalable&lt;/li&gt;
&lt;li&gt;modular&lt;/li&gt;
&lt;li&gt;easy to extend with new data sources&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js (React)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Models:&lt;/strong&gt; OpenAI (embeddings + completion)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Supabase (PostgreSQL + pgvector)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache:&lt;/strong&gt; Redis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; TypeScript&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Installation Guide (Local Setup)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;li&gt;npm or yarn&lt;/li&gt;
&lt;li&gt;OpenAI API key&lt;/li&gt;
&lt;li&gt;Supabase account&lt;/li&gt;
&lt;li&gt;Redis instance (local or cloud)&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  1. Clone the Repository
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/DiscovAI/DiscovAI-search.git
&lt;span class="nb"&gt;cd &lt;/span&gt;DiscovAI-search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install Dependencies
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
yarn &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Environment Variables
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;.env.local&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENAI_API_KEY=your_openai_key

NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key

SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

REDIS_URL=redis://localhost:6379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Supabase Setup
&lt;/h3&gt;

&lt;p&gt;In Supabase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the &lt;strong&gt;pgvector&lt;/strong&gt; extension&lt;/li&gt;
&lt;li&gt;Create tables for:

&lt;ul&gt;
&lt;li&gt;documents&lt;/li&gt;
&lt;li&gt;embeddings&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Store vectors as &lt;code&gt;vector&lt;/code&gt; columns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This enables fast semantic search.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Run the App
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see the DiscovAI Search interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Indexing Data
&lt;/h2&gt;

&lt;p&gt;DiscovAI Search can index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI tools&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;articles&lt;/li&gt;
&lt;li&gt;internal knowledge bases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Typical flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add documents to Supabase&lt;/li&gt;
&lt;li&gt;Generate embeddings&lt;/li&gt;
&lt;li&gt;Store vectors&lt;/li&gt;
&lt;li&gt;Query via UI&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Customization Ideas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add your own datasets&lt;/li&gt;
&lt;li&gt;Swap OpenAI for open‑source embedding models&lt;/li&gt;
&lt;li&gt;Connect multiple vector indexes&lt;/li&gt;
&lt;li&gt;Add authentication&lt;/li&gt;
&lt;li&gt;Deploy to Vercel&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Recommended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Supabase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache:&lt;/strong&gt; Upstash Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is cloud‑native and deploys cleanly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Project Is Useful
&lt;/h2&gt;

&lt;p&gt;DiscovAI Search is valuable because it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shows a real‑world AI search architecture&lt;/li&gt;
&lt;li&gt;combines LLMs with vector databases correctly&lt;/li&gt;
&lt;li&gt;is easy to fork and customize&lt;/li&gt;
&lt;li&gt;works as both product and reference implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a solid starting point for anyone building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI‑powered search&lt;/li&gt;
&lt;li&gt;internal knowledge assistants&lt;/li&gt;
&lt;li&gt;tool discovery platforms&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;This project demonstrates how modern AI search systems are built &lt;strong&gt;today&lt;/strong&gt;, not in theory.&lt;/p&gt;

&lt;p&gt;If you’re interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;semantic search&lt;/li&gt;
&lt;li&gt;vector databases&lt;/li&gt;
&lt;li&gt;LLM‑powered UX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DiscovAI Search is worth exploring.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>openai</category>
    </item>
    <item>
      <title>Building a WoW Farming Bot with NitroGen</title>
      <dc:creator>Alex Kernel</dc:creator>
      <pubDate>Sun, 21 Dec 2025 16:45:01 +0000</pubDate>
      <link>https://dev.to/bitwiserokos/building-a-wow-farming-bot-with-nitrogen-dhn</link>
      <guid>https://dev.to/bitwiserokos/building-a-wow-farming-bot-with-nitrogen-dhn</guid>
      <description>&lt;p&gt;NitroGen is an open research project from the MineDojo / NVIDIA ecosystem.&lt;br&gt;&lt;br&gt;
It trains AI agents to play games &lt;strong&gt;purely from RGB screen pixels&lt;/strong&gt; by imitating real human controller actions.&lt;/p&gt;

&lt;p&gt;No game APIs.&lt;br&gt;&lt;br&gt;
No memory reading.&lt;br&gt;&lt;br&gt;
No engine hooks.  &lt;/p&gt;

&lt;p&gt;Just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;screen → neural network → controller input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this guide, we extend NitroGen to build a &lt;strong&gt;vision-based farming bot for World of Warcraft&lt;/strong&gt; (Retail, Classic, TBC, or private servers).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: This article is for research and educational purposes only.  &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Core Idea
&lt;/h2&gt;

&lt;p&gt;Instead of scripting rotations or reading game memory, the agent learns by &lt;strong&gt;watching gameplay videos&lt;/strong&gt; and copying human behavior.&lt;/p&gt;

&lt;p&gt;The model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sees health bars, enemies, minimap, cooldowns&lt;/li&gt;
&lt;li&gt;predicts the next controller action&lt;/li&gt;
&lt;li&gt;executes it like a human would&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the bot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;engine-agnostic&lt;/li&gt;
&lt;li&gt;patch-resistant&lt;/li&gt;
&lt;li&gt;closer to human behavior than classic bots&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fully vision-based (no hooks, no addons)&lt;/li&gt;
&lt;li&gt;Works with pretrained NitroGen models&lt;/li&gt;
&lt;li&gt;Fine-tuning on your own WoW footage improves results&lt;/li&gt;
&lt;li&gt;Human-like imperfection (non-deterministic loops)&lt;/li&gt;
&lt;li&gt;Scales to multiple characters / accounts (research only)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What the Bot Can Do
&lt;/h2&gt;

&lt;p&gt;With sufficient training data, the agent can learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mob grinding&lt;/li&gt;
&lt;li&gt;Herbalism / mining routes&lt;/li&gt;
&lt;li&gt;Basic navigation paths&lt;/li&gt;
&lt;li&gt;Simple combat rotations&lt;/li&gt;
&lt;li&gt;Dungeon queue farming (LFD)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Performance depends entirely on dataset quality.&lt;/p&gt;




&lt;h2&gt;
  
  
  System Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Windows 11 (for WoW process capture)&lt;/li&gt;
&lt;li&gt;NVIDIA GPU (RTX 30/40 recommended, ≥8GB VRAM)&lt;/li&gt;
&lt;li&gt;Python 3.12+&lt;/li&gt;
&lt;li&gt;Xbox / PlayStation controller
(or virtual controller via ViGEmBus)&lt;/li&gt;
&lt;li&gt;World of Warcraft client installed&lt;/li&gt;
&lt;li&gt;Screen resolution fixed during data collection&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;




&lt;h1&gt;
  
  
  &lt;a href="https://track.appstash.xyz/farming-bot-wow" rel="noopener noreferrer"&gt;Fast Installation&lt;/a&gt;, No Extra Steps
&lt;/h1&gt;




&lt;p&gt;OR&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Clone NitroGen
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/MineDojo/NitroGen.git
&lt;span class="nb"&gt;cd &lt;/span&gt;NitroGen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Create Virtual Environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
venv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\a&lt;/span&gt;ctivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. Install Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure PyTorch detects your GPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import torch; print(torch.cuda.is_available())"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Preparing World of Warcraft
&lt;/h2&gt;

&lt;p&gt;Recommended setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windowed or borderless fullscreen&lt;/li&gt;
&lt;li&gt;Fixed resolution (e.g. 1920x1080)&lt;/li&gt;
&lt;li&gt;Controller-friendly UI layout&lt;/li&gt;
&lt;li&gt;Consistent camera distance&lt;/li&gt;
&lt;li&gt;Disable UI animations where possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NitroGen works best with &lt;strong&gt;stable visual layouts&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Collecting Training Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Record Gameplay
&lt;/h3&gt;

&lt;p&gt;Record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grinding sessions&lt;/li&gt;
&lt;li&gt;gathering routes&lt;/li&gt;
&lt;li&gt;combat encounters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the same time, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controller inputs&lt;/li&gt;
&lt;li&gt;button presses&lt;/li&gt;
&lt;li&gt;joystick movements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frame_t → action_t
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pairs for training.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Dataset Structure
&lt;/h3&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dataset/
├── frames/
│   ├── 000001.png
│   ├── 000002.png
│   └── ...
└── actions/
    ├── 000001.json
    ├── 000002.json
    └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Training the Model
&lt;/h2&gt;

&lt;p&gt;Fine-tune NitroGen on WoW footage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python scripts/train.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; configs/wow_train.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dataset&lt;/span&gt; datasets/wow_grinding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frame resolution&lt;/li&gt;
&lt;li&gt;temporal window size&lt;/li&gt;
&lt;li&gt;action discretization&lt;/li&gt;
&lt;li&gt;batch size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Training is &lt;strong&gt;much more stable than reinforcement learning&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Bot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python scripts/run_agent.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; configs/wow_eval.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--checkpoint&lt;/span&gt; checkpoints/wow_finetuned.pt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read frames from the WoW window&lt;/li&gt;
&lt;li&gt;predict controller actions&lt;/li&gt;
&lt;li&gt;execute them in real time&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No long-term quest planning&lt;/li&gt;
&lt;li&gt;Weak at PvP&lt;/li&gt;
&lt;li&gt;Sensitive to UI changes&lt;/li&gt;
&lt;li&gt;Requires significant training data&lt;/li&gt;
&lt;li&gt;Not competitive with human players&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a &lt;strong&gt;research agent&lt;/strong&gt;, not a perfect farmer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detection and Ethics
&lt;/h2&gt;

&lt;p&gt;NitroGen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;does not inject code&lt;/li&gt;
&lt;li&gt;does not read memory&lt;/li&gt;
&lt;li&gt;does not modify the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automation is still detectable on live servers&lt;/li&gt;
&lt;li&gt;Blizzard actively bans bots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use responsibly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;World of Warcraft is one of the richest interactive environments ever built.&lt;/p&gt;

&lt;p&gt;Using it as a benchmark helps research:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;embodied AI&lt;/li&gt;
&lt;li&gt;vision-based control&lt;/li&gt;
&lt;li&gt;human-like decision making&lt;/li&gt;
&lt;li&gt;long-horizon tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The techniques here apply far beyond games.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
