// Self-host guide — how to run ClawAgen on your own infrastructure.
function SelfHost() {
  const shell = useShell();
  const tocItems = [
    { id: 'why', label: 'Why self-host' },
    { id: 'reqs', label: 'What you need' },
    { id: 'clone', label: '1. Clone + install' },
    { id: 'env', label: '2. Configure env + providers' },
    { id: 'local', label: '3. Run locally' },
    { id: 'tenant', label: '4. Seed a first tenant' },
    { id: 'deploy', label: '5. Deploy to a server' },
    { id: 'tls', label: '6. TLS + custom domain' },
    { id: 'ops', label: 'Day-2 ops' },
    { id: 'backup', label: 'Backups', level: 3 },
    { id: 'update', label: 'Updates', level: 3 },
    { id: 'uninstall', label: 'Uninstall' },
  ];

  return (
    <div className="app">
      <Topbar section="docs" theme={shell.theme} setTheme={shell.setTheme}
              onSearch={() => shell.setSearchOpen(true)}
              onMenuToggle={() => shell.setMobileMenuOpen(true)} />
      <div className="main">
        <Sidebar activeId="selfhost"
                 mobileOpen={shell.mobileMenuOpen}
                 onMobileClose={() => shell.setMobileMenuOpen(false)} />
        <article className="content">
          <div className="crumbs">
            <a href="index.html">Docs</a>
            <span className="sep">/</span>
            <a href="#">Deployment</a>
            <span className="sep">/</span>
            <span>Self-host</span>
          </div>

          <div className="eyebrow">Deployment · ~20 min</div>
          <h1 className="h1">Self-host <em>ClawAgen.</em></h1>
          <p className="lede">
            One VM, one Docker Compose stack, your data. ClawAgen runs on anything that can host
            Docker — your laptop, a $5 VPS, an Azure/AWS/GCP instance, or your own datacenter.
            This guide walks from <code>git clone</code> to a live tenant reply.
          </p>

          <h2 id="why" className="h2">Why self-host</h2>
          <ul>
            <li><strong>Sovereign data.</strong> Every tenant folder lives on your disk — no third party sees <code>MEMORY.md</code> or the agent's history.</li>
            <li><strong>Model choice.</strong> Point <code>providers.yaml</code> at Anthropic, OpenAI, Azure, DeepSeek, Qwen, Kimi, GLM, MiniMax, Bedrock, or any compatible endpoint; swap at any time.</li>
            <li><strong>No vendor lock-in.</strong> MIT-licensed; the whole stack is yours to fork, patch, and run.</li>
            <li><strong>Predictable cost.</strong> Compute is your VM bill. LLM calls are billed by your provider directly — no ClawAgen-side markup.</li>
          </ul>

          <h2 id="reqs" className="h2">What you need</h2>
          <ul>
            <li><strong>Host:</strong> Linux x86_64 with at least 2 vCPU + 4 GB RAM + 20 GB disk (more comfortable: 8 vCPU / 16 GB).</li>
            <li><strong>Runtime:</strong> Docker 24+ and Docker Compose v2. That's it.</li>
            <li><strong>Development:</strong> Bun 1.3.12+ (for local editing and running without containers).</li>
            <li><strong>Keys:</strong> one LLM API key for your chosen provider; a separate embeddings key (OpenAI <code>text-embedding-3-large</code> recommended).</li>
            <li><strong>Optional:</strong> a domain name + DNS, if you want TLS and a custom URL.</li>
          </ul>

          <h2 id="clone" className="h2">1. Clone + install</h2>
          <CodeBlock tabs={[{ label: 'shell', raw: `git clone https://github.com/carrickcheah/ai-agents.git\ncd ai-agents\nmake install   # runs bun install in agents/, ui/, channel-oauth/`,
            code: `<span class="tok-com">$</span> git clone https://github.com/carrickcheah/ai-agents.git
<span class="tok-com">$</span> <span class="tok-kw">cd</span> ai-agents
<span class="tok-com">$</span> make install   <span class="tok-com"># runs bun install in agents/, ui/, channel-oauth/</span>` }]} />

          <h2 id="env" className="h2">2. Configure env + providers</h2>
          <p>
            Copy the example env file and fill in your keys:
          </p>
          <CodeBlock tabs={[{ label: 'agents/.env', raw: `cp agents/.env.example agents/.env\n$EDITOR agents/.env\n\n# Required fields\nDATA_DIR=./data\nAZURE_OPENAI_API_KEY=sk-...         # or the key for your chosen provider\nAZURE_OPENAI_BASE_URL=https://...\nAZURE_OPENAI_MODEL=gpt-5.3-chat\nOPENAI_EMBEDDINGS_API_KEY=sk-...    # for text-embedding-3-large`,
            code: `<span class="tok-com">$</span> cp agents/.env.example agents/.env
<span class="tok-com">$</span> $EDITOR agents/.env

<span class="tok-com"># Required fields</span>
<span class="tok-var">DATA_DIR</span>=./data
<span class="tok-var">AZURE_OPENAI_API_KEY</span>=sk-...         <span class="tok-com"># or the key for your chosen provider</span>
<span class="tok-var">AZURE_OPENAI_BASE_URL</span>=https://...
<span class="tok-var">AZURE_OPENAI_MODEL</span>=gpt-5.3-chat
<span class="tok-var">OPENAI_EMBEDDINGS_API_KEY</span>=sk-...    <span class="tok-com"># for text-embedding-3-large</span>` }]} />
          <p>
            For non-Azure providers, edit{' '}
            <code>agents/config/providers.yaml</code> — the file already ships with commented
            templates for Anthropic, DeepSeek, Qwen, and AWS Bedrock. Uncomment your choice and
            set <code>defaults.primary</code> to the <code>provider/model</code> string.
          </p>

          <h2 id="local" className="h2">3. Run locally</h2>
          <p>Two flavors. Pick one:</p>
          <CodeBlock tabs={[
            { label: 'Bun (native, hot-reload)', raw: `make run   # agents → :8000, ui → :8080; Ctrl+C stops both`,
              code: `<span class="tok-com">$</span> make run   <span class="tok-com"># agents → :8000, ui → :8080; Ctrl+C stops both</span>` },
            { label: 'Docker Compose', raw: `make up         # full stack (agents + redis + ui + caddy) in containers\nmake logs       # tail\nmake down       # stop`,
              code: `<span class="tok-com">$</span> make up         <span class="tok-com"># full stack (agents + redis + ui + caddy) in containers</span>
<span class="tok-com">$</span> make logs       <span class="tok-com"># tail</span>
<span class="tok-com">$</span> make down       <span class="tok-com"># stop</span>` },
          ]} />

          <Callout type="note" title="Agents needs a sandbox image">
            The <code>bash</code> / <code>exec</code> tools run inside an ephemeral Docker sandbox.
            Build it once with <code>make sandbox-build</code> before the first run; subsequent
            runs reuse the image.
          </Callout>

          <h2 id="tenant" className="h2">4. Seed a first tenant</h2>
          <CodeBlock tabs={[{ label: 'shell', raw: `make seed-dev\n\n# Output:\n# → tenant 1 created at ./data/tenants/1/\n# → api key: ck_live_...\n# Keep the key; you'll need it to hit /api/admin.`,
            code: `<span class="tok-com">$</span> make seed-dev

<span class="tok-com"># Output:</span>
<span class="tok-com"># → tenant 1 created at ./data/tenants/1/</span>
<span class="tok-com"># → api key: ck_live_...</span>
<span class="tok-com"># Keep the key; you'll need it to hit /api/admin.</span>` }]} />
          <p>
            The seed script creates the tenant directory, writes default{' '}
            <code>IDENTITY.md</code>/<code>SOUL.md</code>/<code>MEMORY.md</code> templates, and
            mints a signed API key. First API call:
          </p>
          <CodeBlock tabs={[{ label: 'curl', raw: `curl -N http://localhost:8000/api/admin \\\n  -H "X-API-Key: $CLAWAGEN_KEY" \\\n  -H "Content-Type: application/json" \\\n  -d '{"message":"who are you?"}'`,
            code: `<span class="tok-com">$</span> curl -N http://localhost:8000/api/admin <span class="tok-pun">\\</span>
  -H <span class="tok-str">"X-API-Key: $CLAWAGEN_KEY"</span> <span class="tok-pun">\\</span>
  -H <span class="tok-str">"Content-Type: application/json"</span> <span class="tok-pun">\\</span>
  -d <span class="tok-str">'{"message":"who are you?"}'</span>` }]} />

          <h2 id="deploy" className="h2">5. Deploy to a server</h2>
          <p>
            The bundled <code>deploy</code> target rsyncs the repo to your host, builds the
            sandbox image, and starts the production compose file. Update the VM fields in the
            Makefile or override on the command line:
          </p>
          <CodeBlock tabs={[{ label: 'shell', raw: `# One-time: add the server's IP and your SSH key path\nVM_USER=yourname VM_IP=1.2.3.4 SSH_KEY=~/.ssh/id_ed25519 make deploy\n\n# Or edit the defaults in Makefile and run:\nmake deploy`,
            code: `<span class="tok-com"># One-time: add the server's IP and your SSH key path</span>
<span class="tok-com">$</span> <span class="tok-var">VM_USER</span>=yourname <span class="tok-var">VM_IP</span>=1.2.3.4 <span class="tok-var">SSH_KEY</span>=~/.ssh/id_ed25519 make deploy

<span class="tok-com"># Or edit the defaults in Makefile and run:</span>
<span class="tok-com">$</span> make deploy` }]} />
          <p>
            The target uses <code>docker-compose.azure.yml</code>. If you're not on Azure, copy it
            to <code>docker-compose.prod.yml</code>, adjust any cloud-specific volume mounts, and
            update the deploy target to reference your file.
          </p>

          <h2 id="tls" className="h2">6. TLS + custom domain</h2>
          <p>
            The prod compose includes a <strong>Caddy</strong> container that fetches Let's Encrypt
            certificates automatically. Point a DNS A record at your host's IP, then edit{' '}
            <code>infra/Caddyfile</code>:
          </p>
          <CodeBlock tabs={[{ label: 'infra/Caddyfile', raw: `api.yourdomain.com {\n  reverse_proxy agents:8000\n}\n\napp.yourdomain.com {\n  reverse_proxy ui:80\n}`,
            code: `api.yourdomain.com {
  reverse_proxy agents:<span class="tok-num">8000</span>
}

app.yourdomain.com {
  reverse_proxy ui:<span class="tok-num">80</span>
}` }]} />
          <p>
            Redeploy with <code>make deploy</code>. Caddy will obtain certificates on first
            request; subsequent requests get served over HTTPS with auto-renewal.
          </p>

          <h2 id="ops" className="h2">Day-2 ops</h2>

          <h3 id="backup" className="h3">Backups</h3>
          <p>
            <code>/data/tenants/</code> is the durability boundary — not <code>data.db</code>.
            Delete a SQLite file and the reindexer rebuilds it from the Markdown. A nightly tar of
            that directory is enough:
          </p>
          <CodeBlock tabs={[{ label: 'crontab', raw: `# Nightly at 02:00 — tar the tenants dir and upload\n0 2 * * * docker run --rm -v agents_data:/data \\\n  alpine tar czf - /data > /backups/tenants-$(date +\\%F).tgz\n\n# Optional: push to S3 / Azure Blob / R2`,
            code: `<span class="tok-com"># Nightly at 02:00 — tar the tenants dir and upload</span>
<span class="tok-num">0 2 * * *</span> docker run --rm -v agents_data:/data <span class="tok-pun">\\</span>
  alpine tar czf - /data &gt; /backups/tenants-$(date +<span class="tok-str">\\%F</span>).tgz

<span class="tok-com"># Optional: push to S3 / Azure Blob / R2</span>` }]} />

          <h3 id="update" className="h3">Updates</h3>
          <p>
            <code>git pull &amp;&amp; make deploy</code>. The compose rebuild keeps named volumes
            (<code>agents_data</code>, <code>redis_data</code>) so tenant files and sessions
            survive. Downtime is a few seconds while containers swap.
          </p>

          <h2 id="uninstall" className="h2">Uninstall</h2>
          <CodeBlock tabs={[{ label: 'shell', raw: `make down\ndocker volume rm ai-agents_agents_data ai-agents_redis_data\ndocker image rm ai-agents-sandbox:latest\nrm -rf ai-agents/`,
            code: `<span class="tok-com">$</span> make down
<span class="tok-com">$</span> docker volume rm ai-agents_agents_data ai-agents_redis_data
<span class="tok-com">$</span> docker image rm ai-agents-sandbox:latest
<span class="tok-com">$</span> rm -rf ai-agents/` }]} />
          <Callout type="warn" title="This deletes every tenant folder">
            Tenant data lives in the <code>agents_data</code> Docker volume. Running
            <code>docker volume rm</code> erases everything irreversibly. Back up first if you
            might want anything back.
          </Callout>

          <Feedback />
          <PageFoot
            prev={{ label: 'Quickstart', href: 'quickstart.html' }}
            next={{ label: 'API reference', href: 'api-reference.html' }}
          />
        </article>
        <TOC items={tocItems} />
      </div>
      <SearchOverlay open={shell.searchOpen} onClose={() => shell.setSearchOpen(false)} />
      <TweaksPanel visible={shell.tweaksVisible} theme={shell.theme} setTheme={shell.setTheme} />
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<SelfHost />);
