<?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: kusunoki</title>
    <description>The latest articles on DEV Community by kusunoki (@kusunoki).</description>
    <link>https://dev.to/kusunoki</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3864462%2F5f3e357a-9bef-4ea7-931a-391a54084585.png</url>
      <title>DEV Community: kusunoki</title>
      <link>https://dev.to/kusunoki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kusunoki"/>
    <language>en</language>
    <item>
      <title>I Built a Private Cloud + 4 AI Assistants on One Server (No DevOps Required)</title>
      <dc:creator>kusunoki</dc:creator>
      <pubDate>Sun, 12 Apr 2026 14:13:09 +0000</pubDate>
      <link>https://dev.to/kusunoki/i-built-a-private-cloud-4-ai-assistants-on-one-server-no-devops-required-4b5p</link>
      <guid>https://dev.to/kusunoki/i-built-a-private-cloud-4-ai-assistants-on-one-server-no-devops-required-4b5p</guid>
      <description>&lt;p&gt;Most discussions around AI systems begin and end with technology.&lt;/p&gt;

&lt;p&gt;Models, frameworks, and infrastructure are assembled with precision, and for a brief moment, everything appears complete.&lt;/p&gt;

&lt;p&gt;Yet, in practice, such systems rarely endure.&lt;/p&gt;

&lt;p&gt;They do not fail because of inadequate tools, but because they lack structure—structure that governs how they are operated, maintained, and trusted over time.&lt;/p&gt;




&lt;p&gt;This series was written to address that gap.&lt;/p&gt;

&lt;p&gt;Across five articles published on DEV, I documented not only how to construct a self-hosted AI environment, but how to transform it into something that can function beyond initial deployment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replacing recurring SaaS dependencies with a minimal, self-hosted stack&lt;/li&gt;
&lt;li&gt;Establishing a zero-trust architecture with a strictly controlled attack surface&lt;/li&gt;
&lt;li&gt;Building a private cloud environment under full ownership&lt;/li&gt;
&lt;li&gt;Introducing reliability through monitoring, backups, and remote access&lt;/li&gt;
&lt;li&gt;And finally, organizing the system into an operational framework that can be sustained&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each part was necessary.&lt;/p&gt;

&lt;p&gt;Taken together, they describe a single idea:&lt;/p&gt;

&lt;p&gt;A system is not defined by what it can do, but by how it is sustained.&lt;/p&gt;




&lt;p&gt;To complement these articles, I consolidated the operational layer into a structured package, documented and maintained separately.&lt;/p&gt;

&lt;p&gt;Notion serves here not merely as a documentation tool, but as a medium for operational clarity—housing runbooks, policies, and decision frameworks that extend beyond code.&lt;/p&gt;

&lt;p&gt;This distinction is intentional.&lt;/p&gt;

&lt;p&gt;Infrastructure can be reproduced.&lt;/p&gt;

&lt;p&gt;Operations must be understood.&lt;/p&gt;




&lt;p&gt;The transition from building systems to operating them introduces a different set of responsibilities.&lt;/p&gt;

&lt;p&gt;Questions emerge that are not technical in nature:&lt;/p&gt;

&lt;p&gt;Who takes action when the system deviates from expected behavior?&lt;br&gt;
How are decisions made under uncertainty?&lt;br&gt;
What ensures continuity when knowledge is no longer centralized in a single individual?&lt;/p&gt;

&lt;p&gt;Without explicit answers, even well-designed systems remain fragile.&lt;/p&gt;




&lt;p&gt;In SaaS environments, these questions are often abstracted away.&lt;/p&gt;

&lt;p&gt;Responsibility is externalized, processes are hidden, and operational boundaries are defined by vendors.&lt;/p&gt;

&lt;p&gt;Convenience is achieved, but at the cost of visibility and control.&lt;/p&gt;




&lt;p&gt;A self-hosted approach reverses this relationship.&lt;/p&gt;

&lt;p&gt;Control is restored, but so too is responsibility.&lt;/p&gt;

&lt;p&gt;Every configuration implies a decision.&lt;br&gt;
Every failure demands interpretation.&lt;br&gt;
Every recovery requires preparation.&lt;/p&gt;




&lt;p&gt;This is where most systems quietly break.&lt;/p&gt;

&lt;p&gt;Not at the level of infrastructure, but at the level of operation.&lt;/p&gt;




&lt;p&gt;The purpose of this work is therefore not limited to demonstrating what can be built.&lt;/p&gt;

&lt;p&gt;It is to establish how such systems can be sustained—independently, reliably, and with clarity of responsibility.&lt;/p&gt;

&lt;p&gt;To move from isolated technical success to a form of continuity that does not depend on constant intervention.&lt;/p&gt;




&lt;p&gt;Most people build systems.&lt;/p&gt;

&lt;p&gt;Fewer build environments.&lt;/p&gt;

&lt;p&gt;Very few build operations.&lt;/p&gt;




&lt;p&gt;That distinction defines whether a system remains an experiment or becomes something that can be relied upon in the real world.&lt;/p&gt;




&lt;p&gt;Kusunoki&lt;br&gt;
Certified Tax Accountant (Japan)&lt;/p&gt;

&lt;p&gt;Designing independent, self-hosted systems beyond SaaS.&lt;br&gt;
From infrastructure to operations.&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>ai</category>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>The Operations Manual for a System You Actually Own — Building Your Private AI Infrastructure [5/5]</title>
      <dc:creator>kusunoki</dc:creator>
      <pubDate>Wed, 08 Apr 2026 13:22:45 +0000</pubDate>
      <link>https://dev.to/kusunoki/i-didnt-build-an-ai-system-i-built-an-organization-pil</link>
      <guid>https://dev.to/kusunoki/i-didnt-build-an-ai-system-i-built-an-organization-pil</guid>
      <description>&lt;p&gt;Free series. All open-source. The complete operations manual for everything you built.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;You built this.&lt;/strong&gt; Not a vendor. Not a consultant. Not a managed service provider who will send you an invoice next month for the privilege of using what was always supposed to be yours. You opened a terminal, followed a guide, made decisions, fixed the things that broke, and kept going. The system running on your server right now — the eight security layers, the four AI assistants, the private cloud, the monitoring, the backups — exists because you decided it should exist and then made it real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That matters more than the cost savings.&lt;/strong&gt; More than the features. More than the security architecture. What you built in these four parts is not just infrastructure. It is proof that the tools which were once reserved for enterprises with six-figure IT budgets are now accessible to anyone willing to invest two weekends and follow a guide. The promise of personal computing — that individuals would own their own capability — took forty years to arrive. It arrived in the form of open-source software, commodity cloud servers, and a free-tier security layer that would have cost a fortune five years ago.&lt;/p&gt;

&lt;p&gt;This final part is the operations manual. It does not add features. It ensures that what you built continues to run, continues to be maintained, and continues to serve you for months and years to come. Building a system is an act of engineering. Maintaining a system is an act of responsibility. Both are yours now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cardinal Rule: The Human Decides
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before anything else in this operations manual, one principle must be stated clearly enough that it cannot be forgotten or rationalized away:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every initial data entry, every final verification, every irreversible action — a human performs it. Not AI. Not automation. Not a scheduled script running at 3 AM. You.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI drafts your email. You read it before you send it. OpenClaw organizes your files. You check the result before you archive it. The backup script runs nightly. You verify it monthly. The accounting integration queries your books. You review the answer before you act on it.&lt;/p&gt;

&lt;p&gt;This is not a limitation on the technology. It is the condition under which the technology operates safely. AI is an extraordinarily capable instrument. The quality of its output depends entirely on the judgment directing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The system you built is powerful. The responsibility for what it does is yours.&lt;/strong&gt; That responsibility does not transfer to any software, any algorithm, or any automation. It remains with the human who gives the instruction, reviews the output, and authorizes the action. This is not a disclaimer. It is the operating principle that makes everything else in this guide trustworthy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Full LLM Proxy Application
&lt;/h2&gt;

&lt;p&gt;The proxy makes all four AI models accessible through one authenticated endpoint.&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/llm-proxy &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/llm-proxy
nano app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the complete application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&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;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;authenticate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization&lt;/span&gt;&lt;span class="dl"&gt;'&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;Bearer &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="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="nx"&gt;token&lt;/span&gt; &lt;span class="o"&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;AUTH_TOKEN&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&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="nf"&gt;next&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;models&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;openai&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;anthropic&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;google&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;perplexity&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authenticate&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&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="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&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="nx"&gt;response&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.openai.com/v1/chat/completions&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;temperature&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Content-Type&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;application/json&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&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;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic&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="nx"&gt;response&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.anthropic.com/v1/messages&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max_tokens&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&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;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&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;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic-version&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;2023-06-01&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;Content-Type&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;application/json&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&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;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assistant&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;model&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;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
      &lt;span class="nx"&gt;response&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=&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;GOOGLE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxOutputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;temperature&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&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;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-1.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;perplexity&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="nx"&gt;response&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.perplexity.ai/chat/completions&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sonar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;temperature&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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;PERPLEXITY_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Content-Type&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;application/json&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&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;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sonar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;perplexity&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Unknown model: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;] Error with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AI provider error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&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;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/usage-report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authenticate&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;configured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&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;OPENAI_API_KEY&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;configured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&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;ANTHROPIC_API_KEY&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;configured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&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;GOOGLE_API_KEY&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;perplexity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;configured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&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;PERPLEXITY_API_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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&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;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8000&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;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;] LLM Proxy running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;Install and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express axios dotenv
node app.js &amp;amp;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test a model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v1/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_AUTH_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"anthropic","messages":[{"role":"user","content":"Reply with: proxy confirmed."}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Systemd service (run permanently):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/systemd/system/llm-proxy.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;LLM Proxy Service&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;myadmin&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/myadmin/llm-proxy&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/node app.js&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/myadmin/llm-proxy/.env&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; llm-proxy
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status llm-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  OpenClaw Configuration Templates
&lt;/h2&gt;

&lt;p&gt;Place in &lt;code&gt;~/.openclaw/workflows/&lt;/code&gt;. Plain text. Read at startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  morning-briefing.txt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCHEDULE: weekdays 07:30
DELIVER_TO: primary_chat

Pull the following and format as a briefing:
1. Unread email count from primary Gmail. Flag any from known client domains.
2. Today's events from Nextcloud Calendar (all calendars).
3. Tasks due today or overdue.
4. Count of tasks due this week.
5. One-line server status: curl http://localhost:8000/health.

Format: plain text, no markdown. Under 200 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  email-to-task.txt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRIGGER: message contains "create task" OR "add task" OR "log task"
ACTION: extract task details, create in Nextcloud Tasks
REQUIRED_FIELDS: title, due_date
OPTIONAL_FIELDS: list_name, priority, notes
CONFIRM: always confirm before creating
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  weekly-summary.txt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SCHEDULE: friday 17:00
DELIVER_TO: primary_chat

1. Tasks completed this week.
2. Tasks due this week still open.
3. Tasks due next week (count + titles).
4. One observation about completion rate.

Format: plain text. Under 150 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  standing-rules.txt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EMAIL_DEFAULT: draft_only
# OpenClaw must never send email autonomously.
# All email actions produce a draft for human review.
# This rule cannot be overridden by any other instruction.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw reload-workflows
openclaw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Monthly Maintenance Checklist
&lt;/h2&gt;

&lt;p&gt;First of each month. ~30 minutes. Thirteen items.&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;# 1. Firewall&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 2. Listening ports&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 3. Docker health&lt;/span&gt;
docker ps &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'table {{.Names}}\t{{.Status}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 4. Disk usage&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; / &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; ~/backups/ ~/db-backups/ 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 5. Backup timestamps&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lht&lt;/span&gt; ~/backups/ | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 6. System updates&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 7. OpenClaw&lt;/span&gt;
openclaw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 8. LLM proxy&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;9. Grafana: review 30-day trends at &lt;a href="https://grafana.yourdomain.com" rel="noopener noreferrer"&gt;https://grafana.yourdomain.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;10. Cloudflare: review access logs at dash.cloudflare.com → Zero Trust → Logs.&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;# 11. Docker image updates&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/nextcloud &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose pull &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/guacamole &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose pull &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 12. Post-update verification&lt;/span&gt;
docker ps &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'table {{.Names}}\t{{.Status}}'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;13. AI spending audit (see dedicated section below).&lt;/p&gt;




&lt;h2&gt;
  
  
  The Annual Maintenance Checklist
&lt;/h2&gt;

&lt;p&gt;Once per year. ~90 minutes. Eight items.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Ubuntu lifecycle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu-support-status
lsb_release &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;24.04 LTS: standard support through April 2029.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. SSH key rotation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"annual-rotation-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/id_ed25519_new
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_ed25519_new.pub &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.ssh/authorized_keys
&lt;span class="c"&gt;# Test login with new key before removing old&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. cloudflared update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only-upgrade&lt;/span&gt; cloudflared
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart cloudflared
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. OpenClaw update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm update &lt;span class="nt"&gt;-g&lt;/span&gt; openclaw
openclaw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Password rotation
&lt;/h3&gt;

&lt;p&gt;Rotate: Nextcloud admin, Guacamole admin, Grafana admin, PostgreSQL, LLM proxy auth token. Update .env, restart affected services.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Access policy audit
&lt;/h3&gt;

&lt;p&gt;Cloudflare Zero Trust → Access → Applications. Remove departed users. Confirm remaining access is appropriate.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Backup restoration test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;CRITICAL: an untested backup is not a backup. This item cannot be deferred.&lt;/strong&gt;&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/restore-test

gpg &lt;span class="nt"&gt;--decrypt&lt;/span&gt; &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; ~/.backup-passphrase &lt;span class="se"&gt;\&lt;/span&gt;
  ~/backups/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; ~/backups/&lt;span class="k"&gt;*&lt;/span&gt;vps-config&lt;span class="k"&gt;*&lt;/span&gt;.gpg | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; | xargs &lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/restore-test/config-test.tar.gz

&lt;span class="nb"&gt;tar &lt;/span&gt;tzf /tmp/restore-test/config-test.tar.gz | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

psql &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUPABASE_CONNECTION_STRING&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;t"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt;

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /tmp/restore-test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Archive lists without errors + Supabase tables visible = pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Documentation review
&lt;/h3&gt;

&lt;p&gt;Confirm all credentials in Bitwarden are current. Confirm emergency runbook is accessible offline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Emergency Response Runbook
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Keep this accessible offline&lt;/strong&gt; — printed copy, Bitwarden note, or phone file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Tunnel down
&lt;/h3&gt;

&lt;p&gt;Symptom: all services unreachable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status cloudflared
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart cloudflared
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If restart fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; cloudflared &lt;span class="nt"&gt;-n&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common: update needed or API token expired.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker container down
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/nextcloud &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose restart
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/guacamole &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose restart
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart prometheus grafana-server prometheus-alertmanager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Disk full
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;--vacuum-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200M
docker system prune &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; ~/backups/&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rh&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API key compromised
&lt;/h3&gt;

&lt;p&gt;Provider dashboard → revoke immediately → generate new key → update .env:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart llm-proxy
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Device lost or stolen
&lt;/h3&gt;

&lt;p&gt;Cloudflare Zero Trust → Devices → revoke. Change Nextcloud + Guacamole passwords. Review Access logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenClaw acting unexpectedly
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw stop
openclaw logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Do not restart until root cause is understood.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete server loss (~2–3 hours)
&lt;/h3&gt;

&lt;p&gt;1. New Vultr server, Ubuntu 24.04 LTS.&lt;/p&gt;

&lt;p&gt;2. Rebuild: UFW, fail2ban, Node.js, Docker (Part 2).&lt;/p&gt;

&lt;p&gt;3. Restore DB from Supabase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dump &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUPABASE_CONNECTION_STRING&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/nextcloud-db-restore.sql
psql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-U&lt;/span&gt; nextcloud_user &lt;span class="nt"&gt;-d&lt;/span&gt; nextcloud_db &amp;lt; /tmp/nextcloud-db-restore.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Restore system config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpg &lt;span class="nt"&gt;--decrypt&lt;/span&gt; &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; ~/.backup-passphrase &lt;span class="se"&gt;\&lt;/span&gt;
  latest-vps-config.gpg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/restore-config.tar.gz
&lt;span class="nb"&gt;sudo tar &lt;/span&gt;xzf /tmp/restore-config.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5. Restore Nextcloud files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpg &lt;span class="nt"&gt;--decrypt&lt;/span&gt; &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; ~/.backup-passphrase &lt;span class="se"&gt;\&lt;/span&gt;
  latest-nextcloud-files.gpg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/restore-files.tar.gz
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; nextcloud_nextcloud_data:/data &lt;span class="nt"&gt;-v&lt;/span&gt; /tmp:/backup &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="nb"&gt;tar &lt;/span&gt;xzf /backup/restore-files.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6. Restart all:&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;cd&lt;/span&gt; ~/nextcloud &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/guacamole &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart prometheus grafana-server prometheus-alertmanager nginx cloudflared llm-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7. Run Part 4 verification checklist.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI Spending Audit
&lt;/h2&gt;

&lt;p&gt;Monthly. Five minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI:&lt;/strong&gt; platform.openai.com → Billing → Usage. GPT-4o is highest cost. Consider GPT-4o mini for routine tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anthropic:&lt;/strong&gt; console.anthropic.com → Billing. Sonnet handles most business writing at lower cost than Opus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google:&lt;/strong&gt; aistudio.google.com → Dashboard. Flash for routine, Pro for synthesis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perplexity:&lt;/strong&gt; perplexity.ai → Settings → API Usage. Sonar for most factual research.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_AUTH_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:8000/usage-report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any provider approaches cap with 10+ days remaining: raise cap or reduce workflow frequency.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to Build Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Multi-location remote desktop.&lt;/strong&gt; Add a second Guacamole connection for a different workstation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. AI-powered document OCR.&lt;/strong&gt; Tesseract + OpenClaw to classify scanned receipts and invoices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Multi-user access control.&lt;/strong&gt; Onboard colleagues with role-specific Nextcloud permissions and Cloudflare Access policies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Static business website.&lt;/strong&gt; Nginx serving public pages from the same server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Contract and invoice generation.&lt;/strong&gt; OpenClaw populates Collabora templates from structured data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Cost Statement
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vultr VPS (Recommended)&lt;/td&gt;
&lt;td&gt;~$24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain (amortized)&lt;/td&gt;
&lt;td&gt;~$1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Zero Trust&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supabase (free tier)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All software (open-source)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI usage (3 users, moderate)&lt;/td&gt;
&lt;td&gt;$15–$35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total (3–8 person team)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$35–$50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Equivalent SaaS for 3 users: $240/month AI alone, $400+ total.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This infrastructure costs less, does more, and belongs to you.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Series
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Part 1 — Architecture Overview&lt;/td&gt;
&lt;td&gt;Stack, costs, security model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 2 — Zero-Trust Server&lt;/td&gt;
&lt;td&gt;Vultr, Cloudflare, UFW, fail2ban&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 3 — The Intelligence Layer&lt;/td&gt;
&lt;td&gt;Docker, Nextcloud, Collabora, AI proxy, OpenClaw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 4 — Operations &amp;amp; Monitoring&lt;/td&gt;
&lt;td&gt;Guacamole, Prometheus, Grafana, backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 5 — The Operations Manual (you are here)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Maintenance, audits, runbook, proxy code&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;All five parts published and free.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Final Word: The System Is Yours. The Responsibility Is Yours.
&lt;/h2&gt;

&lt;p&gt;You built a system that monitors itself, backs itself up, and deploys four AI assistants to work on your behalf. That sentence would have been science fiction ten years ago and enterprise-only five years ago. Today it runs on a $24 server and you configured every line of it.&lt;/p&gt;

&lt;p&gt;But the system does not think. It does not exercise judgment. It does not know when a draft email will offend a client, when a file has been misclassified in a way that matters legally, or when a financial query has returned a number that is technically correct and practically misleading. Those determinations require a human mind — your mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI does not replace your judgment. It amplifies your capacity.&lt;/strong&gt; The difference is everything. A tool that amplifies good judgment produces extraordinary results. A tool that amplifies absent judgment produces extraordinary damage.&lt;/p&gt;

&lt;p&gt;Consult qualified professionals before acting on AI-generated legal, financial, tax, or medical information. Review every draft before it leaves your control. Verify every automated action. Test your backups. Read your alerts. Maintain your system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is self-responsibility. Not as a disclaimer — as a principle.&lt;/strong&gt; The sovereignty you gained by building this infrastructure comes with the obligation to operate it with care.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What you hold now is not a collection of software. It is a new way of working.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your data lives on your server. Your security is under your control. Your AI assistants answer to you, not to a subscription tier. SaaS was the bridge. You are no longer renting capability from someone else's servers under someone else's terms. You built your own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is not an optimization. That is a revolution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The age of system sovereignty has arrived. You are already in it. Not because someone sold it to you, but because you built it yourself, one command at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Welcome to the other side.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Legal Disclaimer
&lt;/h3&gt;

&lt;p&gt;The information provided in this series is for educational and informational purposes only. It does not constitute legal, financial, tax, accounting, cybersecurity, or professional advice. All use is at the sole risk of the user. To the maximum extent permitted by applicable law, including the laws of the State of California (Cal. Civ. Code §§1668, 3513) and the State of New York (N.Y. GOL §5-323), the author disclaims all liability for any damages arising from use of this content. References to all third-party products are for informational purposes only. The author has no commercial relationship with any provider mentioned. Non-commercial sharing and attribution are permitted. Commercial reproduction requires explicit written consent.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions, corrections, configuration issues: comments. Every one gets read. This is the final installment, but the conversation continues.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reference Materials&lt;br&gt;
&lt;a href="https://www.notion.so/Self-Hosted-AI-Operations-Package-33ca188be27c80099356cdd05cc4d8d3" rel="noopener noreferrer"&gt;https://www.notion.so/Self-Hosted-AI-Operations-Package-33ca188be27c80099356cdd05cc4d8d3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;— Kusunoki&lt;/strong&gt;&lt;br&gt;
International Tax Specialist &amp;amp; Systems Builder&lt;br&gt;
Sapporo, Japan | &lt;a class="mentioned-user" href="https://dev.to/kusunoki"&gt;@kusunoki&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Fast. Light. Visible."&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>security</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>I Made My Self-Hosted AI System Actually Reliable — Building Your Private AI Infrastructure [4/5]</title>
      <dc:creator>kusunoki</dc:creator>
      <pubDate>Wed, 08 Apr 2026 09:55:41 +0000</pubDate>
      <link>https://dev.to/kusunoki/i-made-my-self-hosted-ai-system-actually-reliable-monitoring-remote-access-backups-3cib</link>
      <guid>https://dev.to/kusunoki/i-made-my-self-hosted-ai-system-actually-reliable-monitoring-remote-access-backups-3cib</guid>
      <description>&lt;p&gt;Free series. All open-source. No DevOps background required. Estimated hands-on time: 60–90 minutes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;There is a moment in every serious build when you realize it is no longer a project. It is infrastructure.&lt;/strong&gt; You have Nextcloud running. Collabora is live. Four AI models are answering requests through your private proxy. OpenClaw is taking instructions from your phone. The stack works. It does real things. People could depend on this tomorrow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This part is where a weekend project becomes a production system.&lt;/strong&gt; Not because the features were missing — Parts 1 through 3 already deliver a working environment — but because production means you can walk away from it on a Friday evening and trust that it will still be running, still be monitored, and still be backed up when you return on Monday morning. That trust is what this part builds.&lt;/p&gt;

&lt;p&gt;When you finish today, you will have something that most engineers talk about building and never quite do: a self-hosted, zero-trust, AI-augmented infrastructure that monitors itself, alerts you before failures, backs itself up on three independent schedules, and lets you reach your office workstation from any browser on earth through five layers of authentication. For $35 to $50 a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the thing about building it yourself, one command at a time, is that you understand every piece of it.&lt;/strong&gt; There is no vendor abstraction you cannot look behind. There is no configuration you cannot change. There is no outage you cannot diagnose because you built the system that is running. That understanding — not the cost savings, not the features — is what changes how you work.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Will Complete
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; Parts 1–3 complete, all services running. A Gmail account for alerts.&lt;/p&gt;

&lt;p&gt;Seven capabilities are added in this part:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Apache Guacamole&lt;/strong&gt; — browser-based RDP gateway. Access any machine on your LAN from any browser, anywhere, through Cloudflare Access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. TimeTracker&lt;/strong&gt; — one-click billable hour logging with CSV export. IRS §6001 contemporaneous records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Calendar/Tasks/Deck&lt;/strong&gt; — cross-device sync via CalDAV. Single source of truth for all commitments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Accounting API integration&lt;/strong&gt; — QuickBooks Online, Xero, FreshBooks OAuth 2.0 connections for plain-English financial queries through OpenClaw.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Advanced OpenClaw workflows&lt;/strong&gt; — morning briefings, task-from-email, weekly summaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Prometheus + Grafana + Alertmanager&lt;/strong&gt; — metric collection, live dashboard, email alerts before thresholds become incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. AES-256 encrypted backup&lt;/strong&gt; — weekly full-system archive. 30-day retention. Documented 2-hour restore.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Apache Guacamole
&lt;/h2&gt;

&lt;p&gt;Guacamole is a clientless RDP gateway. It translates Remote Desktop Protocol into browser-renderable HTML5 output. Your Windows desktop appears in a tab, authenticated through five independent security layers:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Browser → WARP encryption → Cloudflare Access OTP → Tunnel → Guacamole on Vultr → your office workstation&lt;/em&gt;&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/guacamole &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/guacamole
nano docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;guacd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guacamole/guacd&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guacd&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;

  &lt;span class="na"&gt;guacamole-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guacamole-db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=guacamole_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=guacamole_user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=REPLACE_WITH_STRONG_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;guacamole_db_data:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;guacamole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guacamole/guacamole&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guacamole&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;guacd&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;guacamole-db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GUACD_HOSTNAME=guacd&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRESQL_HOSTNAME=guacamole-db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRESQL_DATABASE=guacamole_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRESQL_USER=guacamole_user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRESQL_PASSWORD=REPLACE_WITH_SAME_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:8888:8080"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;guacamole_db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace REPLACE_WITH_STRONG_PASSWORD in both locations. Store in Bitwarden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three containers Up: guacd, guacamole-db, guacamole.&lt;/p&gt;

&lt;p&gt;Browse to &lt;a href="https://remote.yourdomain.com" rel="noopener noreferrer"&gt;https://remote.yourdomain.com&lt;/a&gt;. Default: guacadmin / guacadmin. Change immediately: username → Settings → Change Password.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Preparing the Windows Machine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On the Windows workstation (not your VPS):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enable Remote Desktop: Settings → System → Remote Desktop → On. (Windows Home: upgrade to Pro ~$100, or install RustDesk as free drop-in.)&lt;/p&gt;

&lt;p&gt;Static local IP: Settings → Network → Edit → Manual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP: 192.168.1.100   Subnet: 255.255.255.0
Gateway: 192.168.1.1   DNS: 1.1.1.1 / 8.8.8.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Router port forwarding:&lt;/strong&gt; forward TCP 3389 to 192.168.1.100. &lt;strong&gt;Restrict source to your Vultr IP. Do not set source to "any."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;External IP: whatismyip.com. Dynamic IP? Request static from ISP (~$10–15/mo) or use No-IP DDNS (free).&lt;/p&gt;

&lt;p&gt;Register in Guacamole: Settings → Connections → New Connection → enter workstation IP/hostname, port 3389, Windows credentials → Save.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session discipline:&lt;/strong&gt; Ctrl+Alt+Shift → Disconnect when finished. Closing the tab without disconnecting leaves your desktop unlocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Time Tracking
&lt;/h2&gt;

&lt;p&gt;Nextcloud → Apps → "TimeTracker" → Enable.&lt;/p&gt;

&lt;p&gt;Usage: ▶ Start / ⏸ Pause / ■ Stop. Tag every session. Export CSV monthly.&lt;/p&gt;

&lt;p&gt;The IRS requires contemporaneous records under 26 U.S.C. §6001. A tagged time log maintained in real time is materially stronger at audit than a reconstruction from memory. Your future self — and your accountant — will thank you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Calendar, Tasks, and Deck
&lt;/h2&gt;

&lt;p&gt;Already installed in your Nextcloud deployment. No additional installation needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calendar
&lt;/h3&gt;

&lt;p&gt;Create color-coded calendars: Company, Client, Personal, Finance. Pre-populate Finance with the four federal estimated tax dates (Apr 15, Jun 16, Sep 15, Jan 15). Share via three-dot menu → Sharing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tasks
&lt;/h3&gt;

&lt;p&gt;Apps → "Tasks" → Enable. Recommended lists: This Week / In Progress / Awaiting Response / Backlog. Syncs to iPhone Reminders and Android OpenTasks via CalDAV.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deck (Kanban)
&lt;/h3&gt;

&lt;p&gt;Apps → "Deck" → Enable. One board per project. Columns: To Do / In Progress / Review / Done. Due dates auto-appear in Calendar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Accounting API Integration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Standing rule: AI generates drafts. Humans approve. No transaction is committed on AI output alone.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  QuickBooks Online
&lt;/h3&gt;

&lt;p&gt;developer.intuit.com → Create App → QBO. Redirect: &lt;a href="https://ai.yourdomain.com/callback/qbo" rel="noopener noreferrer"&gt;https://ai.yourdomain.com/callback/qbo&lt;/a&gt;. Copy Client ID + Secret.&lt;/p&gt;

&lt;p&gt;Append to ~/llm-proxy/.env:&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;QBO_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-client-id
&lt;span class="nv"&gt;QBO_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-client-secret
&lt;span class="nv"&gt;QBO_REDIRECT_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://ai.yourdomain.com/callback/qbo
&lt;span class="nv"&gt;QBO_ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Auth URL (paste 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;https://appcenter.intuit.com/connect/oauth2?client_id=YOUR_CLIENT_ID&amp;amp;redirect_uri=https://ai.yourdomain.com/callback/qbo&amp;amp;response_type=code&amp;amp;scope=com.intuit.quickbooks.accounting&amp;amp;state=secure_random_state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign in → Authorize → copy code= from redirect. Exchange:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"YOUR_CLIENT_ID:YOUR_CLIENT_SECRET"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"grant_type=authorization_code&amp;amp;code=YOUR_AUTH_CODE&amp;amp;redirect_uri=https://ai.yourdomain.com/callback/qbo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add access_token, refresh_token, realmId to .env. Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_ACCESS_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://quickbooks.api.intuit.com/v3/company/YOUR_REALM_ID/companyinfo/YOUR_REALM_ID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: JSON with company name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Xero
&lt;/h3&gt;

&lt;p&gt;developer.xero.com → New App (Web). Redirect: &lt;a href="https://ai.yourdomain.com/callback/xero" rel="noopener noreferrer"&gt;https://ai.yourdomain.com/callback/xero&lt;/a&gt;. Same pattern: .env → auth URL → exchange → verify via /Organisation endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  FreshBooks
&lt;/h3&gt;

&lt;p&gt;developer.freshbooks.com → Create Application. Redirect: &lt;a href="https://ai.yourdomain.com/callback/freshbooks" rel="noopener noreferrer"&gt;https://ai.yourdomain.com/callback/freshbooks&lt;/a&gt;. Same pattern: .env → auth URL → exchange → verify via /users/me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wave
&lt;/h3&gt;

&lt;p&gt;No developer API. Two paths: (1) OpenClaw generates import-ready CSV → review → Wave import, or (2) operate Wave directly through Guacamole remote desktop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Advanced OpenClaw Workflows
&lt;/h2&gt;

&lt;p&gt;With accounting connected and calendar/tasks active, three workflows are immediately practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Morning Briefing
&lt;/h3&gt;

&lt;p&gt;Send once to OpenClaw:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Every weekday 7:30 AM: unread email count + action items, today's calendar, tasks due today, tasks due this week, overdue invoices from [your accounting platform], one-line server status. Deliver to this chat."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Registers and executes daily without further input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task from Email
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"[Client] requested a revised proposal by April 18. Create task 'Deliver revised proposal' in Client Work, due April 16. Add calendar event."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The obligation moves from inbox to task system. The inbox clears.&lt;/p&gt;

&lt;h3&gt;
  
  
  Weekly Summary
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Every Friday 5 PM: completed tasks, incomplete tasks due this week, next week's due items, invoices sent/paid, open invoice total, one observation."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standing rule: EMAIL_DEFAULT = draft_only.&lt;/strong&gt; OpenClaw drafts email. It never sends autonomously. Configure this before using any email-related workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Prometheus + Grafana
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; prometheus prometheus-node-exporter
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apt-transport-https software-properties-common
wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - https://apt.grafana.com/gpg.key | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/share/keyrings/grafana-archive-keyring.gpg
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [signed-by=/usr/share/keyrings/grafana-archive-keyring.gpg] https://apt.grafana.com stable main"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/grafana.list
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/grafana/grafana.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find and update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[server]&lt;/span&gt;
&lt;span class="py"&gt;http_addr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;

&lt;span class="nn"&gt;[security]&lt;/span&gt;
&lt;span class="py"&gt;admin_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_STRONG_PASSWORD&lt;/span&gt;

&lt;span class="nn"&gt;[users]&lt;/span&gt;
&lt;span class="py"&gt;allow_sign_up&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;

&lt;span class="nn"&gt;[auth.anonymous]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; grafana-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browser: &lt;a href="https://grafana.yourdomain.com" rel="noopener noreferrer"&gt;https://grafana.yourdomain.com&lt;/a&gt;. Connections → Data Sources → Prometheus → URL: &lt;a href="http://localhost:9090" rel="noopener noreferrer"&gt;http://localhost:9090&lt;/a&gt; → Save &amp;amp; Test. Dashboards → Import → ID 1860 → Load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live metrics: CPU, memory, disk, network. Updating in real time.&lt;/strong&gt; This is the moment your server stops being something you hope is running and becomes something you know is running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Alertmanager
&lt;/h2&gt;

&lt;p&gt;Gmail app password: myaccount.google.com → Security → 2-Step → App passwords → "Alertmanager" → copy 16-char password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; prometheus-alertmanager
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/prometheus/alertmanager.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste (4 substitutions):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;resolve_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;smtp_smarthost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;smtp.gmail.com:587'&lt;/span&gt;
  &lt;span class="na"&gt;smtp_require_tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;smtp_auth_username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;youremail@gmail.com'&lt;/span&gt;
  &lt;span class="na"&gt;smtp_auth_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16-char-app-password'&lt;/span&gt;
  &lt;span class="na"&gt;smtp_from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;youremail@gmail.com'&lt;/span&gt;

&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;group_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
  &lt;span class="na"&gt;group_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;repeat_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4h&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email-alert'&lt;/span&gt;

&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email-alert'&lt;/span&gt;
    &lt;span class="na"&gt;email_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;youremail@gmail.com'&lt;/span&gt;
        &lt;span class="na"&gt;send_resolved&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/prometheus/alertmanager.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alert rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/prometheus/alert.rules.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server-alerts&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CloudflaredDown&lt;/span&gt;
        &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;up{job="cloudflared"} == &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;critical&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cloudflare&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tunnel&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;down"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HighCPU&lt;/span&gt;
        &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100 - (avg by(instance)(irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;85&lt;/span&gt;
        &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;warning&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;above&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;85%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minutes"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HighMemory&lt;/span&gt;
        &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;85&lt;/span&gt;
        &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;warning&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;above&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;85%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minutes"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DiskSpaceLow&lt;/span&gt;
        &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15m&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;warning&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Disk&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;above&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;80%"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart prometheus
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; prometheus-alertmanager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9093/api/v1/alerts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[{"labels":{"alertname":"TestAlert","severity":"warning"},"annotations":{"summary":"Email alert test — system working"}}]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Email with subject "TestAlert" arrives within minutes. When it does, monitoring is operational.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9: AES-256 Encrypted Backup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import secrets; print(secrets.token_urlsafe(48))"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.backup-passphrase
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.backup-passphrase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This passphrase is irreplaceable.&lt;/strong&gt; Lost = encrypted backups permanently unrecoverable. Copy to Bitwarden immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste (change myadmin if your user differs):&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;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/home/myadmin/backups"&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ARCHIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/vps-config-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Encrypted backup started"&lt;/span&gt;

&lt;span class="nb"&gt;sudo tar &lt;/span&gt;czf &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ARCHIVE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /home/myadmin/.cloudflared/ &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/prometheus/ &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/grafana/grafana.ini &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/nginx/ &lt;span class="se"&gt;\&lt;/span&gt;
  /home/myadmin/llm-proxy/.env &lt;span class="se"&gt;\&lt;/span&gt;
  /home/myadmin/nextcloud/ &lt;span class="se"&gt;\&lt;/span&gt;
  /home/myadmin/guacamole/ &lt;span class="se"&gt;\&lt;/span&gt;
  /home/myadmin/.supabase-backup.conf &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/ssh/sshd_config &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/ufw/ &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/fail2ban/ &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/sysctl.d/99-security.conf &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/ssl/cloudflare/ &lt;span class="se"&gt;\&lt;/span&gt;
  2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

&lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; nextcloud_nextcloud_data:/data:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/backup &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="nb"&gt;tar &lt;/span&gt;czf /backup/nextcloud-files-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /data &lt;span class="nb"&gt;.&lt;/span&gt;

gpg &lt;span class="nt"&gt;--symmetric&lt;/span&gt; &lt;span class="nt"&gt;--cipher-algo&lt;/span&gt; AES256 &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; /home/myadmin/.backup-passphrase &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ARCHIVE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

gpg &lt;span class="nt"&gt;--symmetric&lt;/span&gt; &lt;span class="nt"&gt;--cipher-algo&lt;/span&gt; AES256 &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; /home/myadmin/.backup-passphrase &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/nextcloud-files-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ARCHIVE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/nextcloud-files-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.gpg"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +30 &lt;span class="nt"&gt;-delete&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Encrypted backup complete"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;700 ~/backup.sh
~/backup.sh
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; ~/backups/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: two .gpg files — system config + Nextcloud data.&lt;/p&gt;

&lt;p&gt;Schedule weekly. Your crontab should now have both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&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;# Daily 2 AM — Supabase DB backup (Part 3)
0 2 * * * /home/myadmin/db-backup-to-supabase.sh &amp;gt;&amp;gt; /home/myadmin/db-backup.log 2&amp;gt;&amp;amp;1

# Weekly Sunday 3 AM — encrypted file backup (Part 4)
0 3 * * 0 /home/myadmin/backup.sh &amp;gt;&amp;gt; /home/myadmin/backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recovery from total server loss:&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;# Restore config&lt;/span&gt;
gpg &lt;span class="nt"&gt;--decrypt&lt;/span&gt; &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; ~/.backup-passphrase &lt;span class="se"&gt;\&lt;/span&gt;
  ~/backups/latest-vps-config.gpg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/restore-config.tar.gz
&lt;span class="nb"&gt;sudo tar &lt;/span&gt;xzf /tmp/restore-config.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /

&lt;span class="c"&gt;# Restore Nextcloud files&lt;/span&gt;
gpg &lt;span class="nt"&gt;--decrypt&lt;/span&gt; &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--passphrase-file&lt;/span&gt; ~/.backup-passphrase &lt;span class="se"&gt;\&lt;/span&gt;
  ~/backups/latest-nextcloud-files.gpg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/restore-files.tar.gz
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; nextcloud_nextcloud_data:/data &lt;span class="nt"&gt;-v&lt;/span&gt; /tmp:/backup &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="nb"&gt;tar &lt;/span&gt;xzf /backup/restore-files.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /data

&lt;span class="c"&gt;# Restart&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/nextcloud &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose restart
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/guacamole &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose restart
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart prometheus grafana-server prometheus-alertmanager nginx cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bare server to fully operational: approximately two hours.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Verification Checklist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All containers Up (Nextcloud stack + Guacamole stack).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://remote.yourdomain.com" rel="noopener noreferrer"&gt;https://remote.yourdomain.com&lt;/a&gt; → Guacamole loads. Click connection → Windows desktop appears.&lt;/p&gt;

&lt;p&gt;TimeTracker: Start → 30s → Stop. Session logged. Task with tomorrow's due date → appears on phone. Deck card with due date → appears in Calendar.&lt;/p&gt;

&lt;p&gt;Accounting: curl verify returns JSON with company data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://grafana.yourdomain.com" rel="noopener noreferrer"&gt;https://grafana.yourdomain.com&lt;/a&gt; → live CPU/memory/disk/network.&lt;/p&gt;

&lt;p&gt;Alertmanager test email arrives in Gmail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/backup.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; ~/backups/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two .gpg files with current timestamp.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Both cron entries visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All checked: Part 4 is complete. The build is finished.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Have Built — and What It Changes
&lt;/h2&gt;

&lt;p&gt;Four parts. Two weekends. One stack.&lt;/p&gt;

&lt;p&gt;A Vultr server ($12–$48/month) behind eight zero-trust security layers, invisible to the public internet except through Cloudflare's authenticated access. A private file cloud with collaborative editing. Four AI assistants through one portal. An agentic butler that organizes your files, drafts your correspondence, and delivers your briefings while you sleep. Browser-based remote desktop from anywhere on earth. Real-time monitoring with email alerts. Triple-redundant backup with a documented two-hour full recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total: ~$35–$50/month for a 3–8 person team.&lt;/strong&gt; The equivalent SaaS stack: $300–$400/month per team — with your data on someone else's servers, under terms that change without notice.&lt;/p&gt;




&lt;h3&gt;
  
  
  How Your Work Changes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Monday morning:&lt;/strong&gt; your phone buzzes at 7:30 AM with a briefing you did not write — unread emails categorized, overdue invoices flagged, calendar summarized, tasks prioritized. You scan it in 90 seconds over coffee. Your week is already organized before you sit down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A client emergency at 2 PM:&lt;/strong&gt; they need a file from the machine sitting in your office, 30 minutes away. You open a browser tab, authenticate through Cloudflare, and your office desktop materializes on your screen. You send the file. Your client assumes you were at your desk. Total elapsed time: four minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Friday evening:&lt;/strong&gt; your weekly summary arrives. Tasks completed: 14. Overdue: 0. Open invoices: two, both under 15 days. Server status: all green. Backup completed at 3 AM as scheduled. You close the laptop. The system runs through the weekend without you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tax preparer in April:&lt;/strong&gt; you hand them a CSV of tagged, timestamped billable hours, a Nextcloud folder of YYYYMMDD-named receipts with full version history, and a PostgreSQL audit trail that satisfies 26 U.S.C. §6001. They ask where you found a bookkeeper this organized. You didn't. You built a system.&lt;/p&gt;




&lt;h3&gt;
  
  
  How Your Business Changes
&lt;/h3&gt;

&lt;p&gt;You stop paying $131–$175 per user per month for tools you do not own, storing data on servers you cannot inspect, under terms you did not negotiate.&lt;/p&gt;

&lt;p&gt;You stop wondering whether your VPN is the weakest link in your security — because you eliminated the VPN entirely and replaced it with eight independent layers that follow NIST SP 800-207.&lt;/p&gt;

&lt;p&gt;You stop choosing between AI providers — because you have all four, at API pricing, through one portal, matched to each task.&lt;/p&gt;

&lt;p&gt;You gain something that SaaS cannot sell you: the knowledge that your data is on your server, your security is under your control, and your infrastructure continues running whether or not any particular vendor survives next quarter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is not a feature. That is sovereignty.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Part 5 Covers
&lt;/h2&gt;

&lt;p&gt;The system is built. Part 5 is the operations manual: monthly and annual maintenance checklists, the complete LLM proxy application code, OpenClaw configuration templates, the emergency response runbook, device loss procedures, and the monthly AI cost audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Part 5 is complete, the series is complete.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: Building Your Private AI Infrastructure
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Part 1 — Architecture Overview&lt;/td&gt;
&lt;td&gt;Stack, costs, security model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 2 — Zero-Trust Server&lt;/td&gt;
&lt;td&gt;Vultr, Cloudflare, UFW, fail2ban&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 3 — The Intelligence Layer&lt;/td&gt;
&lt;td&gt;Docker, Nextcloud, Collabora, AI proxy, OpenClaw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 4 — Operations &amp;amp; Monitoring (you are here)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Guacamole, Prometheus, Grafana, encrypted backups&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 5 — The Operations Manual&lt;/td&gt;
&lt;td&gt;Maintenance, audits, cost optimization, runbook&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;All five parts are published and free. No paywall, no signup, no follow-up sequence.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Legal Disclaimer
&lt;/h3&gt;

&lt;p&gt;The information provided in this series is for educational and informational purposes only. It does not constitute legal, financial, tax, accounting, cybersecurity, or professional advice. All use is at the sole risk of the user. To the maximum extent permitted by applicable law, including the laws of the State of California (Cal. Civ. Code §§1668, 3513) and the State of New York (N.Y. GOL §5-323), the author disclaims all liability for any damages arising from use of this content. References to all third-party products are for informational purposes only. The author has no commercial relationship with any provider mentioned.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 5 — the final installment — is next. Questions, errors, configuration issues: comments. Every one gets read. Technical ones get detailed answers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;— Kusunoki&lt;/strong&gt;&lt;br&gt;
International Tax Specialist &amp;amp; Systems Builder&lt;br&gt;
Sapporo, Japan | &lt;a class="mentioned-user" href="https://dev.to/kusunoki"&gt;@kusunoki&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tags: devops, linux, security, selfhosted&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>security</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>I Built My Own Private Cloud + 4 AI Assistants on One Server — Building Your Private AI Infrastructure [3/5]</title>
      <dc:creator>kusunoki</dc:creator>
      <pubDate>Wed, 08 Apr 2026 09:49:02 +0000</pubDate>
      <link>https://dev.to/kusunoki/i-built-my-own-private-cloud-4-ai-assistants-on-one-server-no-saas-full-control-80i</link>
      <guid>https://dev.to/kusunoki/i-built-my-own-private-cloud-4-ai-assistants-on-one-server-no-saas-full-control-80i</guid>
      <description>&lt;p&gt;Free series. All open-source. No DevOps background required. Estimated hands-on time: 60–90 minutes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;You have a hardened VPS, two open ports, and a Cloudflare tunnel that makes your server invisible to the public internet. The walls are up.&lt;/strong&gt; Now let's put something worth protecting inside them.&lt;/p&gt;

&lt;p&gt;This part installs Docker, PostgreSQL, Nextcloud, Collabora Online, a unified 4-model AI proxy, and OpenClaw — your agentic butler — plus CalDAV calendar sync, custom domain email, and nightly backups to Supabase. By the end, the system is operational for daily use.&lt;/p&gt;

&lt;p&gt;Every step follows the same pattern as Part 2: paste the command, observe the result, verify before proceeding. If something breaks, you know exactly where and how to fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Will Complete in This Part
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Read time:&lt;/strong&gt; ~20 min. &lt;strong&gt;Hands-on:&lt;/strong&gt; ~60–90 min. &lt;strong&gt;Prerequisites:&lt;/strong&gt; Parts 1–2 completed. Four API keys gathered. Password manager open.&lt;/p&gt;

&lt;p&gt;When this part is complete, eleven things will be true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Docker installed and running&lt;/li&gt;
&lt;li&gt;PostgreSQL containerized, holding Nextcloud data&lt;/li&gt;
&lt;li&gt;Nextcloud live at cloud.yourdomain.com, behind Cloudflare Access&lt;/li&gt;
&lt;li&gt;Collabora integrated — .docx/.xlsx open in-browser for real-time co-editing&lt;/li&gt;
&lt;li&gt;LLM proxy at ai.yourdomain.com, routing to 4 AI providers, keys in isolated .env&lt;/li&gt;
&lt;li&gt;OpenClaw installed, systemd-sandboxed, connected to your messaging app&lt;/li&gt;
&lt;li&gt;CalDAV sync active on all devices&lt;/li&gt;
&lt;li&gt;Custom domain email configured with DKIM/SPF/DMARC&lt;/li&gt;
&lt;li&gt;Supabase backup running nightly at 2 AM&lt;/li&gt;
&lt;li&gt;All 8 security layers active&lt;/li&gt;
&lt;li&gt;Monthly cost fully understood — no line surprises&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Before You Build: Four API Keys
&lt;/h2&gt;

&lt;p&gt;Collect all keys before touching the terminal:&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=sk-[your key]
ANTHROPIC_API_KEY=sk-ant-[your key]
GOOGLE_API_KEY=AIza-[your key]
PERPLEXITY_API_KEY=pplx-[your key]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Spending rule:&lt;/strong&gt; set a hard $20/month cap per provider before your first API request. Total exposure: $80. Realistic spend for a 3-person team: $15–$35/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI&lt;/strong&gt; — platform.openai.com. Broad reasoning, coding, general analysis. &lt;strong&gt;Anthropic&lt;/strong&gt; — console.anthropic.com. Nuanced prose, contracts, editorial. &lt;strong&gt;Google&lt;/strong&gt; — aistudio.google.com. Structured data, Workspace integration. &lt;strong&gt;Perplexity&lt;/strong&gt; — perplexity.ai. Source-cited real-time research.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Docker and PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Log in as myadmin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; docker.io docker-compose
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker myadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Restart session:&lt;/strong&gt; exit, log back in. Not optional — Docker commands fail without the group reload.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;docker --version: 24.x.x+. docker ps: empty table (correct — no containers yet).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: The Compose Blueprint
&lt;/h2&gt;

&lt;p&gt;Docker Compose manages the entire stack from one YAML 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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/nextcloud &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/nextcloud
nano docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste exactly. Three substitutions required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=nextcloud_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=nextcloud_user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=REPLACE_WITH_STRONG_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nextcloud_db_data:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_HOST=db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=nextcloud_db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=nextcloud_user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=REPLACE_WITH_SAME_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nextcloud_data:/var/www/html&lt;/span&gt;

  &lt;span class="na"&gt;collabora&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;collabora/code:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aliasgroup1=https://cloud.REPLACE_WITH_YOUR_DOMAIN:443&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;extra_params=--o:ssl.enable=false --o:ssl.termination=true&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:9980:9980"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nextcloud_db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nextcloud_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Substitutions:&lt;/strong&gt; (1) REPLACE_WITH_STRONG_PASSWORD — same 20+ char password in both locations. (2) REPLACE_WITH_YOUR_DOMAIN — your actual domain.&lt;/p&gt;

&lt;p&gt;Save: Ctrl+O → Enter → Ctrl+X.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run pulls all images (~5–10 min). Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three containers with Up status: PostgreSQL, Nextcloud, Collabora.&lt;/p&gt;

&lt;p&gt;Browse to &lt;a href="https://cloud.yourdomain.com" rel="noopener noreferrer"&gt;https://cloud.yourdomain.com&lt;/a&gt;. Cloudflare OTP → Nextcloud setup screen. Create admin account. Store credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the dashboard loads, your private cloud is operational.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Collabora Online
&lt;/h2&gt;

&lt;p&gt;Nextcloud → user icon → Apps → search "Nextcloud Office" → Install.&lt;/p&gt;

&lt;p&gt;User icon → Administration Settings → Nextcloud Office → Use your own server → URL: &lt;a href="https://office.yourdomain.com" rel="noopener noreferrer"&gt;https://office.yourdomain.com&lt;/a&gt; → Save.&lt;/p&gt;

&lt;p&gt;Verify: + button → New Document → editor opens in browser. Open from second device → both cursors visible, edits sync in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: The 4-Model AI Proxy
&lt;/h2&gt;

&lt;p&gt;Runs at localhost:8000, routed to ai.yourdomain.com via Cloudflare Tunnel. Keys never reach the browser.&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/llm-proxy &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/llm-proxy
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi uvicorn httpx python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/llm-proxy/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste with your actual keys:&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=sk-your-openai-key
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key
GOOGLE_API_KEY=AIza-your-google-key
PERPLEXITY_API_KEY=pplx-your-perplexity-key
PROXY_AUTH_TOKEN=REPLACE_WITH_RANDOM_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate auth token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import secrets; print(secrets.token_urlsafe(32))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste output as PROXY_AUTH_TOKEN value. Save.&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;600 ~/llm-proxy/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full proxy application code is in the Part 5 operations file. Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: &lt;code&gt;{"status":"ok"}&lt;/code&gt;. Confirm at &lt;a href="https://ai.yourdomain.com/health" rel="noopener noreferrer"&gt;https://ai.yourdomain.com/health&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model allocation:&lt;/strong&gt; Claude for contracts/editorial. Perplexity for cited research. ChatGPT for general reasoning. Gemini for structured data. One portal, right model per task.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: OpenClaw — Full Install
&lt;/h2&gt;

&lt;p&gt;A chatbot answers and waits. An agent acts and completes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CVE-2026-25253 (CVSS 8.8, High) and the ClawJacked class were addressed in Part 1.&lt;/strong&gt; The security posture from Part 2 — localhost binding, Cloudflare tunnel auth, UFW port restriction — neutralizes both structurally. This install specifies OpenClaw 2026.1.29 or later (patched).&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;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; openclaw@latest
openclaw onboard &lt;span class="nt"&gt;--install-daemon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interactive flow: select AI model (Claude/ChatGPT), configure working dirs, connect messaging app (Slack/Discord/Telegram).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three mandatory configs before production:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set OpenClaw's internal spending cap (separate from provider caps).&lt;/li&gt;
&lt;li&gt;Restrict filesystem access — exclude dirs containing passwords, keys, financial records.&lt;/li&gt;
&lt;li&gt;No third-party skills without review — community extensions are unaudited.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: "Gateway: running." Test via messaging app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: CalDAV Calendar Sync
&lt;/h2&gt;

&lt;p&gt;iPhone: Settings → Calendar → Accounts → Add → Other → CalDAV. Server: &lt;a href="https://cloud.yourdomain.com/remote.php/dav" rel="noopener noreferrer"&gt;https://cloud.yourdomain.com/remote.php/dav&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Android: DAVx⁵ (free, Play Store). Same server + credentials.&lt;/p&gt;

&lt;p&gt;Mac/PC: Apple Calendar or Thunderbird — CalDAV natively supported.&lt;/p&gt;

&lt;p&gt;Test: create event in Nextcloud → appears on phone within minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Custom Domain Email
&lt;/h2&gt;

&lt;p&gt;Cloudflare Email Routing: Email → Email Routing → Enable. Forward &lt;a href="mailto:yourname@yourdomain.com"&gt;yourname@yourdomain.com&lt;/a&gt; to your existing inbox.&lt;/p&gt;

&lt;p&gt;Send-as config: Gmail → Settings → Accounts and Import → Add another address. Apple Mail → Preferences → Accounts → alias.&lt;/p&gt;

&lt;p&gt;DMARC: Cloudflare DNS → TXT record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: _dmarc
Content: v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify at mail-tester.com. Score ≥8 = reliable inbox delivery.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Supabase Nightly Backup
&lt;/h2&gt;

&lt;p&gt;Create project at supabase.com. Region: US East. Copy connection string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/.supabase-backup.conf
&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;SUPABASE_URI="postgresql://postgres.[project-id]:[password]@aws-0-us-east-1.pooler.supabase.com:6543/postgres"
LOCAL_DB_NAME="nextcloud_db"
LOCAL_DB_USER="nextcloud_user"
LOCAL_DB_CONTAINER="nextcloud_db_1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;600 ~/.supabase-backup.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/db-backup-to-supabase.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste (change myadmin if your username differs):&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;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nb"&gt;source&lt;/span&gt; /home/myadmin/.supabase-backup.conf
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DUMP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/home/myadmin/db-backups"&lt;/span&gt;
&lt;span class="nv"&gt;DUMP_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DUMP_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/nextcloud_db_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.sql"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DUMP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Backup started"&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DB_CONTAINER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; pg_dump &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DB_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DUMP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Local dump complete: &lt;/span&gt;&lt;span class="nv"&gt;$DUMP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUPABASE_URI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'(?&amp;lt;=:)[^@]+(?=@)'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
psql &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUPABASE_URI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"DROP SCHEMA IF EXISTS nextcloud_backup CASCADE; CREATE SCHEMA nextcloud_backup;"&lt;/span&gt;
psql &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUPABASE_URI&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;search_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nextcloud_backup &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DUMP_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Transfer to Supabase complete"&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DUMP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sql"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +14 &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Backup complete"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;700 ~/db-backup-to-supabase.sh
~/db-backup-to-supabase.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm in Supabase: Table Editor → nextcloud_backup schema exists.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 2 * * * /home/myadmin/db-backup-to-supabase.sh &amp;gt;&amp;gt; /home/myadmin/db-backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recovery (total server loss):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dump &lt;span class="s2"&gt;"your-supabase-uri"&lt;/span&gt; &lt;span class="nt"&gt;--schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nextcloud_backup &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/restore.sql
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; nextcloud_db_1 psql &lt;span class="nt"&gt;-U&lt;/span&gt; nextcloud_user nextcloud_db &amp;lt; /tmp/restore.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Three Rules of AI Use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. No PII in prompts.&lt;/strong&gt; Names, SSNs, financial accounts, medical data — none of it belongs in an AI prompt. Use initials or pseudonyms. CCPA and HIPAA don't distinguish accidental from intentional disclosure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. No credentials in chat.&lt;/strong&gt; Keys are in .env. Passwords are in Bitwarden. They never need to appear in a message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Review every output before it acts.&lt;/strong&gt; Claude writes excellent email. Read it first. OpenClaw organizes files as instructed. Check the result. The irreversible consequences of automated action belong to the human who gave the instruction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verification Checklist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: 3 containers Up.&lt;/p&gt;

&lt;p&gt;Browser: &lt;a href="https://cloud.yourdomain.com" rel="noopener noreferrer"&gt;https://cloud.yourdomain.com&lt;/a&gt; → Nextcloud loads. Open .docx → Collabora editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;{"status":"ok"}&lt;/code&gt; — also at &lt;a href="https://ai.yourdomain.com/health" rel="noopener noreferrer"&gt;https://ai.yourdomain.com/health&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Gateway: running." Message via app → response arrives.&lt;/p&gt;

&lt;p&gt;Calendar event in Nextcloud → phone. Email to custom domain → forwarding inbox. Reply → sender shows custom address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Active, 80+443 only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status fail2ban cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both active.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/db-backup-to-supabase.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completes. Supabase shows data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All twelve checked: system is fully operational for daily use.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Monthly Cost
&lt;/h2&gt;

&lt;p&gt;VPS: $12–$48. Domain: ~$1. Cloudflare: $0. Supabase: $0. Software: $0. AI: $15–$35 (moderate, 3 users).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total (3–8 person team): ~$35–$50/month.&lt;/strong&gt; Equivalent SaaS for 3 users: $240/month AI alone, plus storage, monitoring, remote desktop — $400+ total.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Part 4 Builds
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Apache Guacamole&lt;/strong&gt; — browser-based remote desktop. &lt;strong&gt;Prometheus + Grafana + Alertmanager&lt;/strong&gt; — real-time monitoring with email alerts. &lt;strong&gt;AES-256 encrypted weekly backups&lt;/strong&gt; — the third layer of defense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Part 4 is complete, the build is finished.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: Building Your Private AI Infrastructure
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Part 1 — Architecture Overview&lt;/td&gt;
&lt;td&gt;Stack, costs, security model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 2 — Zero-Trust Server&lt;/td&gt;
&lt;td&gt;Vultr, Cloudflare, UFW, fail2ban&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 3 — The Intelligence Layer (you are here)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Docker, Nextcloud, Collabora, AI proxy, OpenClaw&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 4 — Operations &amp;amp; Monitoring&lt;/td&gt;
&lt;td&gt;Guacamole, Prometheus, Grafana, encrypted backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 5 — The Operations Manual&lt;/td&gt;
&lt;td&gt;Maintenance, audits, cost optimization, runbook&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;All five parts are published and free.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Legal Disclaimer
&lt;/h3&gt;

&lt;p&gt;The information provided in this series is for educational and informational purposes only. It does not constitute legal, financial, tax, accounting, cybersecurity, or professional advice. All use is at the sole risk of the user. To the maximum extent permitted by applicable law, including the laws of the State of California (Cal. Civ. Code §§1668, 3513) and the State of New York (N.Y. GOL §5-323), the author disclaims all liability for any damages arising from use of this content. References to all third-party products are for informational purposes only. The author has no commercial relationship with any provider mentioned.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 4 is next. Questions, errors, environment-specific issues: comments. Every one gets read. Technical ones get detailed answers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;— Kusunoki&lt;/strong&gt;&lt;br&gt;
International Tax Specialist &amp;amp; Systems Builder&lt;br&gt;
Sapporo, Japan | &lt;a class="mentioned-user" href="https://dev.to/kusunoki"&gt;@kusunoki&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>selfhosted</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built a Zero-Trust Server With Only 2 Open Ports — Building Your Private AI Infrastructure [2/5]</title>
      <dc:creator>kusunoki</dc:creator>
      <pubDate>Wed, 08 Apr 2026 09:40:15 +0000</pubDate>
      <link>https://dev.to/kusunoki/i-built-a-zero-trust-server-with-only-2-open-ports-no-vpn-no-exposed-ssh-3l41</link>
      <guid>https://dev.to/kusunoki/i-built-a-zero-trust-server-with-only-2-open-ports-no-vpn-no-exposed-ssh-3l41</guid>
      <description>&lt;p&gt;Free series. All open-source. No DevOps background required. Estimated hands-on time: 45–90 minutes.&lt;/p&gt;

&lt;p&gt;You have set up a server before. You know what a terminal looks like. You have also watched someone paste a curl command from a random blog post and immediately regret it. This guide is not that. Every command in this part has a stated purpose, a predictable output, and a clean recovery path. If something breaks — and the instructions are written to prevent this — you log into Vultr, click Reinstall, and start fresh in under two minutes.&lt;br&gt;
What makes this part worth your time is not the individual steps — you could figure out each one alone. It is the specific combination of decisions, the order in which they are applied, and the security posture that results. By the end of this article, your server will have zero publicly accessible ports beyond two, an administrative interface that is invisible to port scanners, and five independent security layers verified and operational. That is not a weekend project you abandon. That is a foundation you build on.&lt;br&gt;
Let’s build it.&lt;/p&gt;

&lt;p&gt;What You Will Complete in This Part&lt;br&gt;
Five things will be true when this part is done:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Vultr VPS running Ubuntu 24.04 LTS — your isolated machine in a global data center, owned entirely by you.&lt;/li&gt;
&lt;li&gt;A custom domain pointing to Cloudflare’s network — the address your team uses to reach every service.&lt;/li&gt;
&lt;li&gt;Cloudflare Zero Trust deployed and active — DDoS mitigation, WAF, and authenticated access control, all at zero cost for up to 50 users.&lt;/li&gt;
&lt;li&gt;Server-level defenses configured — UFW, fail2ban, unattended-upgrades, and hardened sysctl parameters.&lt;/li&gt;
&lt;li&gt;Node.js 24 LTS installed and the OpenClaw foundation ready for Part 3.
Five security layers active and verified. The remaining three are completed in Part 3.
Read time: ~15 min. Hands-on: ~45–90 min. Prerequisites: Part 1 read. A credit card for Vultr (hourly billing, cancel anytime). An email address you control.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1: Provisioning Your Vultr Server&lt;br&gt;
Vultr is a global cloud provider with DCs across NA, EU, and APAC. You are provisioning a Cloud Compute instance — dedicated vCPU, dedicated RAM, dedicated SSD, isolated from other tenants.&lt;br&gt;
Navigate to vultr.com. Create an account. Vultr charges by the hour and bills monthly. No contract. Destroy the server and billing stops.&lt;br&gt;
Click Deploy → Cloud Compute — Shared CPU.&lt;br&gt;
Select a DC closest to your users (US: ATL/CHI/DAL/LA/MIA/NY/SEA).&lt;br&gt;
Server Image: Ubuntu 24.04 LTS. Canonical commits to security patches through April 2029.&lt;br&gt;
Server Size:&lt;br&gt;
Plan    vCPU    RAM SSD $/mo    Use case&lt;br&gt;
Starter 1   2 GB    55 GB   ~$12    Single user / evaluation&lt;br&gt;
Recommended 2   4 GB    80 GB   ~$24    3–8 users (used in this guide)&lt;br&gt;
Growth  4   8 GB    160 GB  ~$48    8–30 users&lt;/p&gt;

&lt;p&gt;Additional Features: enable IPv6 (free, needed for some Part 3 services). Hostname: anything meaningful (visible only in your dashboard).&lt;br&gt;
Root Password: 20+ characters, randomly generated, stored in a password manager. Bitwarden is free and recommended. If you don’t use a password manager yet, start now — you will generate several credentials during this build.&lt;br&gt;
Click Deploy Now. Server provisions in ~60 seconds. Note the IP address — you need it in Step 3.&lt;/p&gt;

&lt;p&gt;Step 2: Registering Your Domain&lt;br&gt;
Your domain is the address for every service in the stack:&lt;br&gt;
cloud.yourdomain.com   → Nextcloud ai.yourdomain.com      → AI proxy remote.yourdomain.com  → Guacamole grafana.yourdomain.com → Grafana ssh.yourdomain.com     → SSH (tunnel only)&lt;br&gt;
Cloudflare Registrar (cloudflare.com/products/registrar) is recommended — consolidates domain + security in one dashboard. .com domains ~$10.44/year. Other registrars work fine.&lt;br&gt;
Choose something short, professional, easy to say aloud. Complete the registration.&lt;/p&gt;

&lt;p&gt;Step 3: Deploying Cloudflare Zero Trust&lt;br&gt;
Cloudflare routes ~20% of global web traffic. The Zero Trust free tier (≤50 users) provides WAF, DDoS mitigation, and Access authentication — capabilities that cost tens of thousands/year commercially.&lt;br&gt;
Account Setup&lt;br&gt;
If you used Cloudflare Registrar, your account exists. Otherwise: dash.cloudflare.com → free account.&lt;br&gt;
Immediately enable 2FA. My Profile → Authentication → Two-Factor Authentication. This is not optional. A compromised Cloudflare account compromises your entire security layer.&lt;br&gt;
If using an external registrar: Add a Site → Free plan → update nameservers to the two Cloudflare provides. Propagation: minutes to 24 hours.&lt;br&gt;
Zero Trust Setup&lt;br&gt;
Click Zero Trust in the left panel. Choose a team name (e.g., mybase). Select the Free plan.&lt;br&gt;
Creating the Tunnel&lt;br&gt;
The Cloudflare Tunnel inverts the conventional model. Instead of opening ports for inbound connections (which creates attack surface), your server initiates an outbound connection to Cloudflare and maintains it. All inbound access routes through this tunnel. Your server’s IP is never directly exposed. Nothing for a port scanner to find.&lt;br&gt;
Zero Trust dashboard → Networks → Tunnels → Create a tunnel → Cloudflared → name it (e.g., mybase-tunnel). Select Debian/Ubuntu. Cloudflare displays a curl command. Copy it.&lt;br&gt;
Open your server console: Vultr dashboard → your server → Console. Log in as root (password won’t display as you type — intentional security behavior). Paste the Cloudflare command. ~30 seconds. Tunnel status shows Healthy.&lt;br&gt;
Tunnel Routing&lt;br&gt;
Public Hostname tab. Add these entries (replace yourdomain.com):&lt;br&gt;
Subdomain   Routes to   Service&lt;br&gt;
cloud.yourdomain.com    &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;   Nextcloud&lt;br&gt;
office.yourdomain.com   &lt;a href="http://localhost:9980" rel="noopener noreferrer"&gt;http://localhost:9980&lt;/a&gt;   Collabora Online&lt;br&gt;
ai.yourdomain.com   &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt;   AI proxy&lt;br&gt;
remote.yourdomain.com   &lt;a href="http://localhost:8888" rel="noopener noreferrer"&gt;http://localhost:8888&lt;/a&gt;   Guacamole&lt;br&gt;
grafana.yourdomain.com  &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;   Grafana&lt;br&gt;
ssh.yourdomain.com  ssh://localhost:50922   SSH admin (tunnel only)&lt;/p&gt;

&lt;p&gt;localhost = this server. The port number = which service. Traffic arrives through the tunnel and gets routed to the right process. The service never talks directly to the public internet.&lt;br&gt;
Access Policies&lt;br&gt;
Access → Applications. For each subdomain: Self-hosted Application. Authentication: One-time PIN (email OTP).&lt;br&gt;
Flow: user navigates to cloud.yourdomain.com → Cloudflare intercepts → email prompt → six-digit code sent → code entered → access granted for configurable session duration. No password to manage. No identity provider to configure. The email address is the credential.&lt;br&gt;
Under Include: add authorized email addresses. Changes take effect immediately. Adding a colleague: one email address added to one list.&lt;/p&gt;

&lt;p&gt;Step 4: Hardening the Server&lt;br&gt;
Cloudflare is the outer wall. The server needs independent defenses — defense in depth means no single layer’s failure compromises everything.&lt;br&gt;
Commands are executed one at a time. Paste, Enter, wait for prompt, proceed.&lt;br&gt;
Administrative User&lt;br&gt;
Root has unrestricted access. A typo operating as root can cause irreversible damage without confirmation. Create a dedicated admin user:&lt;br&gt;
adduser myadmin&lt;br&gt;
Set a strong 20+ char password (different from root, stored in password manager). Skip the Full Name prompts.&lt;br&gt;
usermod -aG sudo myadmin&lt;br&gt;
myadmin is a placeholder. Use whatever name you prefer throughout.&lt;br&gt;
UFW Firewall&lt;br&gt;
sudo apt update &amp;amp;&amp;amp; sudo apt install -y ufw sudo ufw allow 80/tcp comment "HTTP" sudo ufw allow 443/tcp comment "HTTPS" sudo ufw enable&lt;br&gt;
Confirm with y when prompted.&lt;br&gt;
Note what is absent: port 22 is not opened. SSH is routed through the Cloudflare Tunnel — no public SSH port exists. Two ports open. That is the entire external attack surface.&lt;br&gt;
fail2ban&lt;br&gt;
sudo apt install -y fail2ban sudo systemctl enable --now fail2ban&lt;br&gt;
Verify:&lt;br&gt;
sudo systemctl status fail2ban&lt;br&gt;
Expected: active (running).&lt;br&gt;
Automatic Security Updates&lt;br&gt;
sudo apt install -y unattended-upgrades apt-listchanges sudo dpkg-reconfigure -plow unattended-upgrades&lt;br&gt;
Select Yes. Security patches now apply automatically during off-hours.&lt;br&gt;
Kernel Network Hardening&lt;br&gt;
sudo nano /etc/sysctl.d/99-security.conf&lt;br&gt;
Paste:&lt;br&gt;
net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv4.conf.all.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv4.ip_forward = 0&lt;br&gt;
Save: Ctrl+O → Enter → Ctrl+X. Apply:&lt;br&gt;
sudo sysctl --system&lt;br&gt;
Note on ip_forward = 0: VPN setups require this enabled for packet routing. This architecture uses Cloudflare Tunnel for all external routing — IP forwarding is unnecessary and therefore disabled. Capability not needed is capability that cannot be exploited.&lt;br&gt;
Vultr Hardware Firewall&lt;br&gt;
Vultr dashboard → your server → Firewall. Create a firewall group:&lt;br&gt;
Accept TCP 80 from any Accept TCP 443 from any Drop all other inbound&lt;br&gt;
Apply to your server. You now have two independent firewalls enforcing the same two-port restriction: Vultr hardware + Ubuntu UFW. Both fronted by Cloudflare authentication.&lt;/p&gt;

&lt;p&gt;Step 5: Node.js Foundation&lt;br&gt;
OpenClaw (Part 3) runs on Node.js. Install now to avoid a dependency gap:&lt;br&gt;
curl -fsSL &lt;a href="https://deb.nodesource.com/setup_24.x" rel="noopener noreferrer"&gt;https://deb.nodesource.com/setup_24.x&lt;/a&gt; | sudo -E bash - sudo apt install -y nodejs node --version&lt;br&gt;
Expected output: v24.x.x. If error, re-run the first two commands.&lt;/p&gt;

&lt;p&gt;Verifying Your Security Stack&lt;br&gt;
Five of eight layers active. Verify:&lt;br&gt;
sudo ufw status verbose&lt;br&gt;
Expected: active, ports 80 + 443 only.&lt;br&gt;
sudo systemctl status fail2ban&lt;br&gt;
Expected: active (running).&lt;br&gt;
sudo systemctl status cloudflared&lt;br&gt;
Expected: active (running).&lt;br&gt;
node --version&lt;br&gt;
Expected: v24.x.x.&lt;br&gt;
If any check fails: re-run the relevant step. Most failures are a missed command or typo. If unresolvable, Vultr Reinstall → clean Ubuntu → restart from Step 4. Cloudflare config (Steps 2–3) is stored independently and doesn’t need redoing.&lt;/p&gt;

&lt;p&gt;The Security Posture You Have Built&lt;br&gt;
From the public internet: two open TCP ports (80/443), dual-firewalled (Vultr HW + UFW), both fronted by Cloudflare WAF + DDoS mitigation.&lt;br&gt;
SSH: no public port. Routed through Cloudflare Tunnel, accessible only after email OTP authentication.&lt;br&gt;
fail2ban: monitoring auth logs, auto-blocking repeated failures. unattended-upgrades: patching known CVEs during off-hours. Kernel hardening: active.&lt;br&gt;
This is not security bolted on after the fact. It is the foundation. Every application installed in Parts 3 and 4 inherits this posture. No application gets a public port. No admin interface is directly internet-accessible. Every access path routes through the authenticated tunnel.&lt;/p&gt;

&lt;p&gt;A Note on OpenClaw and CVE-2026-25253&lt;br&gt;
Part 1 addressed the vulnerability (CVSS 8.8, High) and the ClawJacked class in detail. The security posture built in this part is precisely what neutralizes them.&lt;br&gt;
The exploit requires an accessible WebSocket endpoint. Your server has no publicly accessible ports beyond 80/443, both behind Cloudflare Access. OpenClaw, when installed in Part 3, will bind to localhost and be accessible only through the authenticated tunnel. No credentials → no access → no exploit.&lt;br&gt;
Patch: OpenClaw 2026.1.29. Specified throughout this guide.&lt;/p&gt;

&lt;p&gt;What Part 3 Builds&lt;br&gt;
The foundation is complete. Part 3 installs the intelligence:&lt;br&gt;
Nextcloud — private file sync replacing Google Drive/Dropbox.&lt;br&gt;
Collabora Online — browser-based collaborative editing.&lt;br&gt;
Four-AI unified proxy — ChatGPT + Claude + Gemini + Perplexity through one authenticated interface. API keys never leave the server.&lt;br&gt;
OpenClaw — agentic AI butler with systemd sandboxing, API key isolation, and messaging integrations.&lt;br&gt;
When Part 3 is complete, the system is operational for daily use.&lt;/p&gt;

&lt;p&gt;Series: Building Your Private AI Infrastructure&lt;br&gt;
Part    What It Covers&lt;br&gt;
Part 1 — Architecture Overview    Stack, costs, security model, daily usage&lt;br&gt;
Part 2 — Zero-Trust Server (you are here) Vultr, Cloudflare, UFW, fail2ban, Node.js&lt;br&gt;
Part 3 — The Intelligence Layer   Docker, Nextcloud, Collabora, AI proxy, OpenClaw&lt;br&gt;
Part 4 — Operations &amp;amp; Monitoring  Guacamole, Prometheus, Grafana, encrypted backups&lt;br&gt;
Part 5 — The Operations Manual    Maintenance, audits, cost optimization, runbook&lt;/p&gt;

&lt;p&gt;All five parts are published and free. No paywall, no signup, no follow-up sequence.&lt;/p&gt;

&lt;p&gt;Legal Disclaimer&lt;br&gt;
The information provided in this series is for educational and informational purposes only. It does not constitute legal, financial, tax, accounting, cybersecurity, or professional advice of any kind. No professional relationship is formed by reading or acting on this content.&lt;br&gt;
All use of techniques, software, services, configurations, or commands described in this series is undertaken at the sole risk of the user. To the maximum extent permitted by applicable law, the author disclaims all liability for any damages arising from use of or reliance upon this content.&lt;br&gt;
This disclaimer is enforceable to the fullest extent permitted by applicable law, including the laws of the State of California (Cal. Civ. Code §§1668, 3513) and the State of New York (N.Y. GOL §5-323), and applicable federal law. References to all third-party products are for informational purposes only. The author has no commercial relationship with any provider mentioned.&lt;/p&gt;

&lt;p&gt;Part 3 is next. Questions, errors, or environment-specific issues belong in the comments. Every one gets read. The technical ones get detailed answers.&lt;/p&gt;

&lt;p&gt;— Kusunoki International Tax Specialist &amp;amp; Systems Builder Sapporo, Japan | @kusunoki---&lt;br&gt;
title: "I Built a Zero-Trust Server With Only 2 Open Ports — Building Your Private AI Infrastructure [2/5]"&lt;br&gt;
published: true&lt;br&gt;
tags: linux, security, selfhosted, tutorial&lt;/p&gt;
&lt;h2&gt;
  
  
  series: Building Your Private AI Infrastructure
&lt;/h2&gt;

&lt;p&gt;Free series. All open-source. No DevOps background required. Estimated hands-on time: 45–90 minutes.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;You have set up a server before.&lt;/strong&gt; You know what a terminal looks like. You have also watched someone paste a curl command from a random blog post and immediately regret it. This guide is not that. Every command in this part has a stated purpose, a predictable output, and a clean recovery path. If something breaks — and the instructions are written to prevent this — you log into Vultr, click Reinstall, and start fresh in under two minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes this part worth your time is not the individual steps — you could figure out each one alone.&lt;/strong&gt; It is the specific combination of decisions, the order in which they are applied, and the security posture that results. By the end of this article, your server will have zero publicly accessible ports beyond two, an administrative interface that is invisible to port scanners, and five independent security layers verified and operational. That is not a weekend project you abandon. That is a foundation you build on.&lt;/p&gt;

&lt;p&gt;Let's build it.&lt;/p&gt;


&lt;h2&gt;
  
  
  What You Will Complete in This Part
&lt;/h2&gt;

&lt;p&gt;Five things will be true when this part is done:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; A Vultr VPS running Ubuntu 24.04 LTS — your isolated machine in a global data center, owned entirely by you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; A custom domain pointing to Cloudflare's network — the address your team uses to reach every service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Cloudflare Zero Trust deployed and active — DDoS mitigation, WAF, and authenticated access control, all at zero cost for up to 50 users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Server-level defenses configured — UFW, fail2ban, unattended-upgrades, and hardened sysctl parameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; Node.js 24 LTS installed and the OpenClaw foundation ready for Part 3.&lt;/p&gt;

&lt;p&gt;Five security layers active and verified. The remaining three are completed in Part 3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read time:&lt;/strong&gt; ~15 min. &lt;strong&gt;Hands-on:&lt;/strong&gt; ~45–90 min. &lt;strong&gt;Prerequisites:&lt;/strong&gt; Part 1 read. A credit card for Vultr (hourly billing, cancel anytime). An email address you control.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: Provisioning Your Vultr Server
&lt;/h2&gt;

&lt;p&gt;Vultr is a global cloud provider with DCs across NA, EU, and APAC. You are provisioning a Cloud Compute instance — dedicated vCPU, dedicated RAM, dedicated SSD, isolated from other tenants.&lt;/p&gt;

&lt;p&gt;Navigate to vultr.com. Create an account. Vultr charges by the hour and bills monthly. No contract. Destroy the server and billing stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Click&lt;/strong&gt; Deploy → Cloud Compute — Shared CPU.&lt;/p&gt;

&lt;p&gt;Select a DC closest to your users (US: ATL/CHI/DAL/LA/MIA/NY/SEA).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server Image:&lt;/strong&gt; Ubuntu 24.04 LTS. Canonical commits to security patches through April 2029.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server Size:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;vCPU&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;SSD&lt;/th&gt;
&lt;th&gt;$/mo&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;55 GB&lt;/td&gt;
&lt;td&gt;~$12&lt;/td&gt;
&lt;td&gt;Single user / evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;td&gt;80 GB&lt;/td&gt;
&lt;td&gt;~$24&lt;/td&gt;
&lt;td&gt;3–8 users (used in this guide)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;160 GB&lt;/td&gt;
&lt;td&gt;~$48&lt;/td&gt;
&lt;td&gt;8–30 users&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Additional Features:&lt;/strong&gt; enable IPv6 (free, needed for some Part 3 services). &lt;strong&gt;Hostname:&lt;/strong&gt; anything meaningful (visible only in your dashboard).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root Password:&lt;/strong&gt; 20+ characters, randomly generated, stored in a password manager. Bitwarden is free and recommended. If you don't use a password manager yet, start now — you will generate several credentials during this build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Click Deploy Now.&lt;/strong&gt; Server provisions in ~60 seconds. Note the IP address — you need it in Step 3.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Registering Your Domain
&lt;/h2&gt;

&lt;p&gt;Your domain is the address for every service in the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cloud.yourdomain.com   → Nextcloud
ai.yourdomain.com      → AI proxy
remote.yourdomain.com  → Guacamole
grafana.yourdomain.com → Grafana
ssh.yourdomain.com     → SSH (tunnel only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudflare Registrar (cloudflare.com/products/registrar) is recommended — consolidates domain + security in one dashboard. .com domains ~$10.44/year. Other registrars work fine.&lt;/p&gt;

&lt;p&gt;Choose something short, professional, easy to say aloud. Complete the registration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Deploying Cloudflare Zero Trust
&lt;/h2&gt;

&lt;p&gt;Cloudflare routes ~20% of global web traffic. The Zero Trust free tier (≤50 users) provides WAF, DDoS mitigation, and Access authentication — capabilities that cost tens of thousands/year commercially.&lt;/p&gt;

&lt;h3&gt;
  
  
  Account Setup
&lt;/h3&gt;

&lt;p&gt;If you used Cloudflare Registrar, your account exists. Otherwise: dash.cloudflare.com → free account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Immediately enable 2FA.&lt;/strong&gt; My Profile → Authentication → Two-Factor Authentication. This is not optional. A compromised Cloudflare account compromises your entire security layer.&lt;/p&gt;

&lt;p&gt;If using an external registrar: Add a Site → Free plan → update nameservers to the two Cloudflare provides. Propagation: minutes to 24 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero Trust Setup
&lt;/h3&gt;

&lt;p&gt;Click Zero Trust in the left panel. Choose a team name (e.g., mybase). Select the Free plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Tunnel
&lt;/h3&gt;

&lt;p&gt;The Cloudflare Tunnel inverts the conventional model. Instead of opening ports for inbound connections (which creates attack surface), your server initiates an outbound connection to Cloudflare and maintains it. All inbound access routes through this tunnel. Your server's IP is never directly exposed. Nothing for a port scanner to find.&lt;/p&gt;

&lt;p&gt;Zero Trust dashboard → Networks → Tunnels → Create a tunnel → Cloudflared → name it (e.g., mybase-tunnel). Select Debian/Ubuntu. Cloudflare displays a curl command. Copy it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open your server console:&lt;/strong&gt; Vultr dashboard → your server → Console. Log in as root (password won't display as you type — intentional security behavior). Paste the Cloudflare command. ~30 seconds. Tunnel status shows Healthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tunnel Routing
&lt;/h3&gt;

&lt;p&gt;Public Hostname tab. Add these entries (replace yourdomain.com):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subdomain&lt;/th&gt;
&lt;th&gt;Routes to&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cloud.yourdomain.com&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Nextcloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;office.yourdomain.com&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:9980" rel="noopener noreferrer"&gt;http://localhost:9980&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Collabora Online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ai.yourdomain.com&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;AI proxy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;remote.yourdomain.com&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:8888" rel="noopener noreferrer"&gt;http://localhost:8888&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Guacamole&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;grafana.yourdomain.com&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ssh.yourdomain.com&lt;/td&gt;
&lt;td&gt;ssh://localhost:50922&lt;/td&gt;
&lt;td&gt;SSH admin (tunnel only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;localhost = this server. The port number = which service. Traffic arrives through the tunnel and gets routed to the right process. The service never talks directly to the public internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Policies
&lt;/h3&gt;

&lt;p&gt;Access → Applications. For each subdomain: Self-hosted Application. Authentication: One-time PIN (email OTP).&lt;/p&gt;

&lt;p&gt;Flow: user navigates to cloud.yourdomain.com → Cloudflare intercepts → email prompt → six-digit code sent → code entered → access granted for configurable session duration. No password to manage. No identity provider to configure. The email address is the credential.&lt;/p&gt;

&lt;p&gt;Under Include: add authorized email addresses. Changes take effect immediately. Adding a colleague: one email address added to one list.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Hardening the Server
&lt;/h2&gt;

&lt;p&gt;Cloudflare is the outer wall. The server needs independent defenses — defense in depth means no single layer's failure compromises everything.&lt;/p&gt;

&lt;p&gt;Commands are executed one at a time. Paste, Enter, wait for prompt, proceed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Administrative User
&lt;/h3&gt;

&lt;p&gt;Root has unrestricted access. A typo operating as root can cause irreversible damage without confirmation. Create a dedicated admin user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser myadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a strong 20+ char password (different from root, stored in password manager). Skip the Full Name prompts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;myadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;myadmin is a placeholder. Use whatever name you prefer throughout.&lt;/p&gt;

&lt;h3&gt;
  
  
  UFW Firewall
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ufw
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 80/tcp comment &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 443/tcp comment &lt;span class="s2"&gt;"HTTPS"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm with y when prompted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note what is absent: port 22 is not opened.&lt;/strong&gt; SSH is routed through the Cloudflare Tunnel — no public SSH port exists. Two ports open. That is the entire external attack surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  fail2ban
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; fail2ban
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: active (running).&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Security Updates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; unattended-upgrades apt-listchanges
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg-reconfigure &lt;span class="nt"&gt;-plow&lt;/span&gt; unattended-upgrades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select Yes. Security patches now apply automatically during off-hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kernel Network Hardening
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/sysctl.d/99-security.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.ip_forward = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save: Ctrl+O → Enter → Ctrl+X. Apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;--system&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note on ip_forward = 0: VPN setups require this enabled for packet routing. This architecture uses Cloudflare Tunnel for all external routing — IP forwarding is unnecessary and therefore disabled. Capability not needed is capability that cannot be exploited.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vultr Hardware Firewall
&lt;/h3&gt;

&lt;p&gt;Vultr dashboard → your server → Firewall. Create a firewall group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accept TCP 80 from any
Accept TCP 443 from any
Drop all other inbound
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply to your server. You now have two independent firewalls enforcing the same two-port restriction: Vultr hardware + Ubuntu UFW. Both fronted by Cloudflare authentication.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Node.js Foundation
&lt;/h2&gt;

&lt;p&gt;OpenClaw (Part 3) runs on Node.js. Install now to avoid a dependency gap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_24.x | &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; bash -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs
node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output: v24.x.x. If error, re-run the first two commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifying Your Security Stack
&lt;/h2&gt;

&lt;p&gt;Five of eight layers active. Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: active, ports 80 + 443 only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: active (running).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: active (running).&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;p&gt;Expected: v24.x.x.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If any check fails:&lt;/strong&gt; re-run the relevant step. Most failures are a missed command or typo. If unresolvable, Vultr Reinstall → clean Ubuntu → restart from Step 4. Cloudflare config (Steps 2–3) is stored independently and doesn't need redoing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Security Posture You Have Built
&lt;/h2&gt;

&lt;p&gt;From the public internet: two open TCP ports (80/443), dual-firewalled (Vultr HW + UFW), both fronted by Cloudflare WAF + DDoS mitigation.&lt;/p&gt;

&lt;p&gt;SSH: no public port. Routed through Cloudflare Tunnel, accessible only after email OTP authentication.&lt;/p&gt;

&lt;p&gt;fail2ban: monitoring auth logs, auto-blocking repeated failures. unattended-upgrades: patching known CVEs during off-hours. Kernel hardening: active.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is not security bolted on after the fact. It is the foundation. Every application installed in Parts 3 and 4 inherits this posture. No application gets a public port. No admin interface is directly internet-accessible. Every access path routes through the authenticated tunnel.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on OpenClaw and CVE-2026-25253
&lt;/h2&gt;

&lt;p&gt;Part 1 addressed the vulnerability (CVSS 8.8, High) and the ClawJacked class in detail. The security posture built in this part is precisely what neutralizes them.&lt;/p&gt;

&lt;p&gt;The exploit requires an accessible WebSocket endpoint. Your server has no publicly accessible ports beyond 80/443, both behind Cloudflare Access. OpenClaw, when installed in Part 3, will bind to localhost and be accessible only through the authenticated tunnel. No credentials → no access → no exploit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patch: OpenClaw 2026.1.29.&lt;/strong&gt; Specified throughout this guide.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Part 3 Builds
&lt;/h2&gt;

&lt;p&gt;The foundation is complete. Part 3 installs the intelligence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nextcloud&lt;/strong&gt; — private file sync replacing Google Drive/Dropbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collabora Online&lt;/strong&gt; — browser-based collaborative editing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four-AI unified proxy&lt;/strong&gt; — ChatGPT + Claude + Gemini + Perplexity through one authenticated interface. API keys never leave the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenClaw&lt;/strong&gt; — agentic AI butler with systemd sandboxing, API key isolation, and messaging integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Part 3 is complete, the system is operational for daily use.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Series: Building Your Private AI Infrastructure
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Part 1 — Architecture Overview&lt;/td&gt;
&lt;td&gt;Stack, costs, security model, daily usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 2 — Zero-Trust Server (you are here)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Vultr, Cloudflare, UFW, fail2ban, Node.js&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 3 — The Intelligence Layer&lt;/td&gt;
&lt;td&gt;Docker, Nextcloud, Collabora, AI proxy, OpenClaw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 4 — Operations &amp;amp; Monitoring&lt;/td&gt;
&lt;td&gt;Guacamole, Prometheus, Grafana, encrypted backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 5 — The Operations Manual&lt;/td&gt;
&lt;td&gt;Maintenance, audits, cost optimization, runbook&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;All five parts are published and free. No paywall, no signup, no follow-up sequence.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Legal Disclaimer
&lt;/h3&gt;

&lt;p&gt;The information provided in this series is for educational and informational purposes only. It does not constitute legal, financial, tax, accounting, cybersecurity, or professional advice. All use is at the sole risk of the user. To the maximum extent permitted by applicable law, including the laws of the State of California (Cal. Civ. Code §§1668, 3513) and the State of New York (N.Y. GOL §5-323), the author disclaims all liability for any damages arising from use of this content. References to all third-party products are for informational purposes only. The author has no commercial relationship with any provider mentioned.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 3 is next. Questions, errors, or environment-specific issues belong in the comments. Every one gets read. The technical ones get detailed answers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;— Kusunoki&lt;/strong&gt;&lt;br&gt;
International Tax Specialist &amp;amp; Systems Builder&lt;br&gt;
Sapporo, Japan | &lt;a class="mentioned-user" href="https://dev.to/kusunoki"&gt;@kusunoki&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>selfhosted</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Replaced $150/Month of SaaS With a $24 VPS and a Weekend — Building Your Private AI Infrastructure [1/5]</title>
      <dc:creator>kusunoki</dc:creator>
      <pubDate>Wed, 08 Apr 2026 09:33:09 +0000</pubDate>
      <link>https://dev.to/kusunoki/i-replaced-150month-saas-with-a-15-self-hosted-ai-stack-zero-trust-full-control-41ga</link>
      <guid>https://dev.to/kusunoki/i-replaced-150month-saas-with-a-15-self-hosted-ai-stack-zero-trust-full-control-41ga</guid>
      <description>&lt;p&gt;uploads.s3.amazonaws.com/uploads/articles/2fwd8amj0ejx1yn4ppmd.png)---&lt;/p&gt;

&lt;h2&gt;
  
  
  series: Building Your Private AI Infrastructure
&lt;/h2&gt;

&lt;p&gt;Free series. No DevOps background required. All open-source. Total cost: ~$15–50/month depending on team size.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;There is a question that every engineer eventually asks, and then spends years not answering.&lt;/strong&gt; You look at the SaaS invoices — $20 here for AI, $20 there for another AI, $18 for cloud storage, $15 for monitoring, $12 for a VPN you don't fully trust — and you think: I could build this. I could own this. I could run this on a single machine I control, behind a security architecture I designed, for a fraction of what I'm paying strangers to hold my data. You have the skills. You have had the thought. What you may not have had is the weekend to research every moving piece, make every configuration decision, and test each integration until it actually works. This guide is that weekend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is also the guide for a transition that is no longer optional.&lt;/strong&gt; The era of trusting your data, your clients' data, and your business operations to SaaS vendors whose terms change without notice, whose security you cannot audit, and whose pricing you cannot negotiate — that era is ending. Not because self-hosting became fashionable. Because the threat landscape changed. The FBI's IC3 documented $12.5 billion in cybercrime losses in 2023, with VPN compromise and stolen credentials as the primary vectors for ransomware targeting small businesses. The perimeter model that every SaaS VPN relies on is structurally broken. Zero-trust is not an upgrade. It is the replacement. And for the first time in the history of computing, you can build it yourself, on commodity hardware, with open-source software, for less than the cost of a single SaaS subscription.&lt;/p&gt;

&lt;p&gt;If you follow Parts 1 through 4 in order, you will finish with a production-grade, zero-trust, self-hosted AI environment running on a single $24 VPS — with eight security layers, four AI providers unified behind one portal, agentic automation, collaborative document editing, remote desktop access, live monitoring with alerting, and triple-redundant encrypted backups. Every command tested. Every decision explained. Every mistake I made flagged so you don't have to. The entire series is free.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who This Is For
&lt;/h2&gt;

&lt;p&gt;If you are a developer who has ever thought about building something like this for a non-technical client — a lawyer, an accountant, a small agency, a consultant — this guide is the deliverable you can hand them, fully documented, with every operational procedure written for someone who has never opened a terminal.&lt;/p&gt;

&lt;p&gt;If you are a technical founder or indie hacker who wants enterprise-grade infrastructure without an enterprise budget or an enterprise IT team, this is the blueprint.&lt;/p&gt;

&lt;p&gt;If you are a small business owner who is comfortable in a terminal but does not live there, this guide is written to be followed sequentially — every command, every configuration file, every verification step — without requiring you to fill in gaps from Stack Overflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The stack:&lt;/strong&gt; Ubuntu 24.04 LTS on Vultr, Cloudflare Zero Trust (free tier), Nextcloud, Collabora Online, a unified four-AI API proxy (ChatGPT + Claude + Gemini + Perplexity), OpenClaw for agentic automation, Apache Guacamole, Prometheus + Grafana + Alertmanager, and AES-256 encrypted backup to Supabase.&lt;/p&gt;

&lt;p&gt;Let's go through what you're actually building before we touch a single command.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Stack Does
&lt;/h2&gt;

&lt;p&gt;This is not an overview designed to impress you and then disappoint you in the implementation. Everything listed here is fully built out in Parts 2 through 4, with every command and configuration file provided.&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Private Cloud
&lt;/h3&gt;

&lt;p&gt;Nextcloud replaces Google Drive and Dropbox. Your files live on your server. They sync across devices. They have version history and a complete audit trail. No third party can access, index, or monetize your data. Nextcloud has over 4,000 contributors and is deployed by governments, universities, and enterprises across more than 50 countries. It is not a weekend experiment. It is production infrastructure used by organizations that cannot afford data leaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four AI Assistants Through One Secure Interface
&lt;/h3&gt;

&lt;p&gt;A self-hosted reverse proxy routes requests to ChatGPT, Claude, Gemini, and Perplexity through a single authenticated interface at ai.yourdomain.com. API keys are stored in an isolated environment file on the server and never reach the browser.&lt;/p&gt;

&lt;p&gt;The practical difference between the four matters in daily use. Claude handles nuanced writing, contractual analysis, and tasks requiring sustained register awareness. Perplexity provides source-cited real-time research. ChatGPT covers broad reasoning and coding tasks. Gemini integrates with Google Workspace data. One portal. Four models. Your credentials, secured on your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Agentic AI Butler
&lt;/h3&gt;

&lt;p&gt;OpenClaw is an open-source agentic AI framework that executes multi-step instructions autonomously — it does not answer and wait, it acts and completes. "Organize last quarter's client invoices by company name and move unpaid items to a dedicated folder" is not a prompt. It is a job order. When you return from a meeting, it will be done.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(There is a security section later in this article specifically about OpenClaw's disclosed vulnerabilities and why the architecture here neutralizes them structurally. If the headlines made you hesitate, read that section before deciding.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-Time Collaborative Document Editing
&lt;/h3&gt;

&lt;p&gt;Collabora Online is a self-hosted office suite — browser-based, simultaneous editing of Word and Excel files, functionally equivalent to Google Docs. One version. Always.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser-Based Remote Desktop
&lt;/h3&gt;

&lt;p&gt;Apache Guacamole provides HTML5 remote desktop access to any designated machine through a standard browser — no client software required on the connecting device. RDP, VNC, and SSH sessions are proxied through Guacamole, authenticated through Cloudflare Access, and rendered in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated Monitoring and Alerting
&lt;/h3&gt;

&lt;p&gt;Prometheus scrapes metrics — CPU, memory, disk, network, application health — and Grafana renders them in a live dashboard. Email alerts fire when any threshold is breached. You know about problems before your users do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Triple-Redundant Encrypted Backups
&lt;/h3&gt;

&lt;p&gt;Daily database dumps to Supabase. Weekly AES-256 encrypted full-system backups. Monthly off-site copies to local storage. The system backs itself up on three independent schedules without your intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Domain Email
&lt;/h3&gt;

&lt;p&gt;&lt;a href="mailto:yourname@yourdomain.com"&gt;yourname@yourdomain.com&lt;/a&gt;, configured with DKIM, SPF, and DMARC authentication records, forwarded through Cloudflare Email Routing to your existing inbox. AI-assisted drafting. Phishing resistance from proper authentication.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Costs
&lt;/h2&gt;

&lt;p&gt;I am going to give you every number, because the first thing that breaks trust in a guide like this is discovering a hidden cost in Part 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vultr VPS
&lt;/h3&gt;

&lt;p&gt;Vultr is a global infrastructure provider with data centers across North America, Europe, and Asia-Pacific. The server you provision is a Cloud Compute instance — dedicated vCPU, dedicated RAM, dedicated SSD, isolated from other tenants.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;vCPU&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;SSD&lt;/th&gt;
&lt;th&gt;$/mo&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2 GB&lt;/td&gt;
&lt;td&gt;55 GB&lt;/td&gt;
&lt;td&gt;~$12&lt;/td&gt;
&lt;td&gt;Single user / evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4 GB&lt;/td&gt;
&lt;td&gt;80 GB&lt;/td&gt;
&lt;td&gt;~$24&lt;/td&gt;
&lt;td&gt;3–8 users, full stack (used in this guide)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;160 GB&lt;/td&gt;
&lt;td&gt;~$48&lt;/td&gt;
&lt;td&gt;8–30 users, heavier workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Domain
&lt;/h3&gt;

&lt;p&gt;$10–$15/year through any registrar. Cloudflare Registrar is used in this guide because it unifies DNS and Zero Trust management in one dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Software
&lt;/h3&gt;

&lt;p&gt;Zero. Nextcloud, Collabora, Guacamole, OpenClaw, Prometheus, Grafana, fail2ban, Certbot — all open-source, all actively maintained, all free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Zero Trust
&lt;/h3&gt;

&lt;p&gt;Zero for up to 50 users. WAF, DDoS mitigation, Access authentication, and Tunnel — enterprise-grade capabilities that would cost tens of thousands per year commercially.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI API Costs — The Honest Part
&lt;/h3&gt;

&lt;p&gt;AI providers charge per token (roughly per three-quarters of a word). There is no monthly flat fee. There is no automatic spending cap. If you build this stack and forget to set limits, you will receive a bill that reflects exactly what you used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You will set hard monthly caps on every provider's dashboard before this stack handles a single production request.&lt;/strong&gt; With a $20 cap per provider ($80 total), realistic usage:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light&lt;/strong&gt; (5–15 interactions/day): $3–$8/mo. &lt;strong&gt;Moderate&lt;/strong&gt; (20–40/day): $10–$20/mo. &lt;strong&gt;Heavy&lt;/strong&gt; (50+/day with automated workflows): $25–$50/mo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Total Monthly Cost
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sole proprietor / Starter / Light AI&lt;/td&gt;
&lt;td&gt;~$18–$28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small team / Recommended / Moderate AI&lt;/td&gt;
&lt;td&gt;~$35–$45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Growing business / Growth plan&lt;/td&gt;
&lt;td&gt;~$64–$79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Equivalent SaaS stack (per user)&lt;/td&gt;
&lt;td&gt;$131–$175&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Your stack replaces the entire SaaS list at a fraction of the cost, and the data stays on hardware you control.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Security Architecture
&lt;/h2&gt;

&lt;p&gt;I want to explain the zero-trust architecture in detail — both because it is the most important design decision in the stack and because "zero-trust" has been co-opted by marketing to the point where the term alone communicates nothing. Here is what it actually means in this implementation.&lt;/p&gt;

&lt;p&gt;A VPN is a perimeter model. Once you are authenticated to the tunnel, you are inside. Every resource inside is reachable. One stolen credential opens the entire castle. The FBI's IC3 documented over $12.5 billion in cybercrime losses in 2023, with VPN vulnerabilities and credential theft as the most common initial access vectors for ransomware targeting SMBs.&lt;/p&gt;

&lt;p&gt;Zero-trust abandons the perimeter concept. Nothing inside or outside the network is trusted by default. Every access request is evaluated independently, every time.&lt;/p&gt;

&lt;p&gt;This stack implements zero-trust through eight independent layers, each of which would need to be defeated separately for an attacker to reach any sensitive data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: Cloudflare WAF + DDoS mitigation:&lt;/strong&gt; All inbound traffic filtered at Cloudflare's edge before reaching your server's IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Cloudflare Access (email OTP):&lt;/strong&gt; Every session requires a fresh six-digit code to a verified email address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Vultr hardware firewall:&lt;/strong&gt; Permits inbound only on ports 80 and 443. Enforced before Ubuntu boots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 4: UFW software firewall:&lt;/strong&gt; Independent OS-level enforcement of the same port restrictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 5: fail2ban:&lt;/strong&gt; Continuous auth log monitoring. Auto-blocks IPs after repeated failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 6: SSH via Cloudflare Tunnel (ED25519):&lt;/strong&gt; No public SSH port. Administrative access routed through authenticated tunnel only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 7: Application-level auth:&lt;/strong&gt; Each service maintains independent credentials. Compromising one grants no access to others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 8: systemd sandboxing + env isolation:&lt;/strong&gt; Each service runs as a dedicated low-privilege user. API keys exist only in process memory. No external interface can reach them.&lt;/p&gt;

&lt;p&gt;A traditional VPN is one wall. This is eight independent checkpoints. The architecture is consistent with NIST SP 800-207, Zero Trust Architecture, translated to the budget and operational reality of an SMB.&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenClaw Security: CVE-2026-25253 and the ClawJacked Class
&lt;/h2&gt;

&lt;p&gt;This section exists because the disclosure was real, the coverage was extensive, and anyone who read it has a legitimate question about why this guide uses OpenClaw at all. I want to answer that question with the precision it deserves, because the answer matters not just for OpenClaw but for every agentic AI framework that will follow it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CVE-2026-25253&lt;/strong&gt; (CVSS 8.8, High severity) allowed an attacker who could induce an authenticated user to visit a crafted URL to hijack an active OpenClaw session through its WebSocket connection, steal the auth token, and execute arbitrary commands on the host. A second class of vulnerabilities — &lt;em&gt;ClawJacked&lt;/em&gt; — included command injection and SSRF flaws in the image processing pipeline. China's Ministry of Industry and Information Technology issued a formal advisory. SecurityScorecard identified over &lt;strong&gt;42,000 internet-exposed instances&lt;/strong&gt;, of which approximately &lt;strong&gt;15,200 (~36%)&lt;/strong&gt; were immediately exploitable.&lt;/p&gt;

&lt;p&gt;All of that is accurate. Here is what the coverage did not distinguish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every one of those 42,000 vulnerable instances had OpenClaw reachable from the public internet.&lt;/strong&gt; In this architecture, it is not.&lt;/p&gt;

&lt;p&gt;The coverage created a narrative: OpenClaw is dangerous. That narrative is incomplete. OpenClaw &lt;em&gt;exposed directly to the internet without authentication&lt;/em&gt; is dangerous — exactly as dangerous as any powerful tool operated without safeguards. The question is not whether the tool has risk. Every tool with real capability has risk. The question is whether the architecture contains that risk. This one does.&lt;/p&gt;

&lt;p&gt;Security researchers at Snyk noted that even localhost-bound instances are technically reachable via CVE-2026-25253 because the victim's browser acts as the pivot — the WebSocket originates from the browser, not an external attacker. This is correct. The protection in this architecture does not rest on the localhost binding alone. It rests on what sits in front of it.&lt;/p&gt;

&lt;p&gt;OpenClaw is installed on your Vultr server, bound to 127.0.0.1, behind Cloudflare Access with mandatory email OTP. There is no public URL at which an unauthenticated visitor can reach OpenClaw's interface. Before any request arrives, it must pass through Cloudflare Access — verified email + valid time-limited code. An attacker without your email credentials cannot start the chain. An attacker who cannot start the chain cannot reach the WebSocket. An attacker who cannot reach the WebSocket cannot execute CVE-2026-25253.&lt;/p&gt;

&lt;p&gt;The ClawJacked SSRF vulnerability is addressed by the systemd sandboxing configuration in Part 3. OpenClaw runs as a dedicated user with no sudo, write permissions restricted to its working directory, and network egress constrained to specific API endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CVE-2026-25253 was patched in OpenClaw 2026.1.29.&lt;/strong&gt; This guide specifies 2026.1.29 or later throughout. The patch addresses the root vulnerability. The architecture addresses the class of vulnerabilities — public exposure of agentic AI interfaces — regardless of whether future disclosures emerge in the same codebase.&lt;/p&gt;

&lt;p&gt;The 42,000 exposed instances that made headlines were not running zero-trust architectures. They were running OpenClaw the way most people run new software: directly, on a machine with a public IP, without hardening. This guide exists specifically to prevent that.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Day Actually Looks Like Running This
&lt;/h2&gt;

&lt;p&gt;This is the part of technical writing that usually gets skipped, which is a mistake, because the gap between "architecturally sound" and "actually useful in daily practice" is where most self-hosted projects die.&lt;/p&gt;

&lt;p&gt;Early morning. Coffee shop. Wi-Fi you do not control or trust.&lt;/p&gt;

&lt;p&gt;You activate Cloudflare WARP. Every byte leaving your device is encrypted in a tunnel the coffee shop's network cannot inspect. You open Nextcloud — a colleague updated a proposal at 11 PM. You open it in Collabora and begin editing simultaneously. One document. One version.&lt;/p&gt;

&lt;p&gt;Three emails need replies. You type one instruction to the AI portal: draft professional responses — first declining a meeting, second confirming a timeline, third following up on an overdue invoice. Thirty seconds later, three drafts. You change four words in the third. Six minutes total.&lt;/p&gt;

&lt;p&gt;A client calls needing a file from your office workstation. You open Guacamole, authenticate, and your office desktop materializes in the browser. You locate the file, send it, close the connection. Your client assumes you're at your desk.&lt;/p&gt;

&lt;p&gt;End of day. Grafana dashboard: CPU normal, memory stable, disk 34%, two blocked IPs from fail2ban. Backup completed at 3 AM. Every indicator green.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That is what good infrastructure looks like in operation: invisible, quiet, and below the threshold of daily attention.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Own Machine Is Not Involved
&lt;/h2&gt;

&lt;p&gt;Everything you build lives on the remote VPS. Your laptop is the browser window through which you reach it. It is not part of the structure.&lt;/p&gt;

&lt;p&gt;If you make a configuration error, log into Vultr, click Reinstall, and the server returns to a clean Ubuntu state in under two minutes. Nothing on your laptop is affected. Nothing was ever at risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only local prerequisite is a browser.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Series
&lt;/h2&gt;

&lt;p&gt;This is Part 1 of five. Each part is free, ungated, and complete.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Part 1 — Architecture Overview (you are here)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Stack, costs, security model, daily usage&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 2 — Zero-Trust Server&lt;/td&gt;
&lt;td&gt;Vultr, Ubuntu, Cloudflare Tunnel, UFW, fail2ban, SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 3 — The Intelligence Layer&lt;/td&gt;
&lt;td&gt;Docker, Nextcloud, Collabora, AI proxy, OpenClaw, CalDAV, backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 4 — Operations &amp;amp; Monitoring&lt;/td&gt;
&lt;td&gt;Guacamole, Prometheus, Grafana, Alertmanager, encrypted backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 5 — The Operations Manual&lt;/td&gt;
&lt;td&gt;Maintenance, security audits, cost optimization, troubleshooting, runbook&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;All five parts are published and free. No paywall, no signup, no follow-up sequence.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  On AI and Judgment
&lt;/h2&gt;

&lt;p&gt;Every AI in this stack drafts, researches, summarizes, and executes on instruction. No AI in this stack makes decisions. You review the draft before you send it. You verify the organized files before you archive them. You consult qualified professionals before acting on any AI-generated legal, financial, or medical information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is not a disclaimer. It is a correct description of what these tools are.&lt;/strong&gt; They are extraordinarily capable instruments. The outcome depends entirely on the judgment directing them. That distinction matters more as the tools get better, not less.&lt;/p&gt;




&lt;h3&gt;
  
  
  Legal Disclaimer
&lt;/h3&gt;

&lt;p&gt;The information provided in this series is for educational and informational purposes only. It does not constitute legal, financial, tax, accounting, cybersecurity, or professional advice of any kind. No professional relationship is formed by reading or acting on this content.&lt;/p&gt;

&lt;p&gt;The author makes no representation or warranty, express or implied, as to the accuracy, completeness, currentness, fitness for a particular purpose, or non-infringement of any information contained herein. All cost estimates, technical configurations, and third-party service descriptions are based on publicly available information as of April 2026 and are subject to change without notice.&lt;/p&gt;

&lt;p&gt;All use of techniques, software, services, configurations, commands, or information described in this series is undertaken at the sole risk of the user. To the maximum extent permitted by applicable law, the author expressly disclaims all liability for any direct, indirect, incidental, special, consequential, exemplary, or punitive damages arising from use of or reliance upon this content.&lt;/p&gt;

&lt;p&gt;This disclaimer is intended to be enforceable to the fullest extent permitted by applicable law, including the laws of the State of California (Cal. Civ. Code §§1668, 3513) and the State of New York (N.Y. GOL §5-323), as well as applicable federal law of the United States. Nothing herein limits liability where prohibited by law, including for gross negligence, willful misconduct, or fraud.&lt;/p&gt;

&lt;p&gt;References to Vultr, Cloudflare, OpenAI, Anthropic, Google DeepMind, Perplexity AI, Nextcloud, Collabora Online, Apache Guacamole, OpenClaw, Prometheus, Grafana, and all other third-party products are for informational purposes only. The author has no commercial, sponsorship, affiliate, or compensated relationship with any provider mentioned. All trademarks are the property of their respective owners.&lt;/p&gt;

&lt;p&gt;Non-commercial sharing and attribution are permitted and encouraged. Commercial reproduction requires the author's explicit written consent.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Part 2 — provisioning the server and building the zero-trust layer — is next.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drop questions in the comments. I read every one and answer the technical ones in detail.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this is the kind of thing you build for clients or have been meaning to build for yourself, follow along. Parts 2 through 4 get into the actual commands.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;— Kusunoki&lt;/strong&gt;&lt;br&gt;
International Tax Specialist &amp;amp; Systems Builder&lt;br&gt;
Sapporo, Japan | &lt;a class="mentioned-user" href="https://dev.to/kusunoki"&gt;@kusunoki&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>cloud</category>
      <category>selfhosted</category>
    </item>
  </channel>
</rss>
