← Index
Live Self-initiated · production infrastructure 2026

Architected and deployed Claude as a persistent, API-callable agentic runtime on a hardened private VPS, with loopback HTTP API integration, bidirectional vault sync, and a multi-agent fleet model

Deployed ClaudeClaw OS (earlyaidopters/claudeclaw-os v1.1.0) on a Hostinger KVM 2 VPS running Ubuntu, turning Claude Code CLI into a persistent background service reachable via Telegram and a local dashboard API. Designed the security cage, secrets management, vault sync architecture, and the reverse HTTP channel that lets other systems (GLOS, cron jobs) programmatically hand facts to the agent and receive Donna-voiced responses — without the calling system ever touching a public LLM API.

v1.1.0ClaudeClaw OS version deployed
~26 minTime from VPS shell to live Telegram bot (first run)
21 filesAgent output files confirmed synced Mac-side on first verified reverse-channel run (athena/ + jade/)
$0 marginal per-call costInference cost model (Claude Max subscription OAuth, not API billing)

The brief

I wanted Claude running as a persistent, always-available agent I could reach from my phone, from cron jobs, and from other applications via an HTTP API — not as a one-off subprocess but as a service that accumulates memory and context across sessions. The secondary goal was to expose a loopback API so my GLOS personal OS app could hand facts (workout completions, daily log events) to the agent and get Donna-voiced responses back without GLOS itself holding any LLM credentials.

What I built

A production agentic runtime on a private Linux VPS, composed of:

  • A persistent Telegram bot (DonnaClaw / @dclaw333_bot) backed by the real Claude Code CLI spawned headless via the @anthropic-ai/claude-agent-sdk, session-resumed per chat via SQLite so context persists across messages
  • A local dashboard and loopback HTTP API at http://127.0.0.1:3141 (token-authenticated /api/chat/send) reachable only through an SSH tunnel — never exposed to the public internet
  • A multi-agent fleet model: Donna as prime orchestrator, plus specialist agents (Athena/researcher, Jade/content, Karen/librarian), each with its own Telegram bot, its own CLAUDE.md personality, and a model tier assigned per role (Haiku for lowest-blast-radius tasks, Sonnet as the default, manual Opus escalation for synthesis)
  • Bidirectional vault sync via Syncthing so agents read my full Obsidian markdown vault on the VPS and write output back to my Mac without the two sides ever clobbering each other
  • A GLOS integration script (scripts/hermes-jefit-cron.sh) that POSTs to the GLOS Next.js API to pull new workout data, then calls the ClaudeClaw loopback API to have Donna deliver the congratulations in her own voice via Telegram — GLOS never touches an LLM API directly

How it’s built

Provisioning and networking. Hostinger KVM 2 (true VM, 2 vCPU / 8 GB RAM / Ubuntu LTS). All admin and sync traffic runs over Tailscale (WireGuard mesh), never the public IP. UFW is default-deny inbound with port rules scoped to the tailscale0 interface only — the dashboard port, the Syncthing sync port (41245), and SSH are all Tailscale-only. A non-root claudeclaw OS user with no sudo runs the entire agent fleet; a compromise is boxed to that account.

Service supervision. ClaudeClaw runs as a systemd per-user service with Restart=always and loginctl enable-linger so it survives logout and reboots. An earlier deployment ran the process bare with no supervisor, which produced a silent 40-minute outage. Systemd was the fix.

Security cage (two layers, both below the agent). The load-bearing wall is Linux file permissions: every credential-bearing file is owned by root at chmod 600. The claudeclaw user physically cannot read them — kernel-enforced, not AI-policy-enforced. The second layer is a root-owned /etc/claude-code/managed-settings.json (also chmod 600, root:root) with a deny list covering .env, .ssh/**, .aws/**, and Bash(env) / Bash(printenv). This file loads at highest precedence in Claude Code and applies even in bypassPermissions mode — the per-user settings file gets skipped in some ClaudeClaw internal flows (memory ingest, the internal router that passes settingSources: []), so root-owned managed-settings is the only airtight gate. Deny rules are scoped to secrets only to avoid breaking legitimate agent work.

Egress visibility. Before hardening outbound traffic I ran a passive DNS logger: tcpdump on port 53, launched as a transient unit via systemd-run, writing to /var/log/egress-domains.log for ~14 hours to capture every domain the agent actually contacts. That yields a real allowlist rather than a guess.

Runtime secrets. No secrets in plaintext on disk. Proton Pass CLI (pass-cli v2.1.2) fetches credentials on demand using a short-lived read-only PAT (7-day expiry, stored at a root-only path outside all sync and git paths). Every fetch requires a human-readable reason string that gets logged — an audit tripwire. The PAT is stored at /etc/proton-pass/agent.pat (chmod 600); values never land in the vault or in environment variables.

Vault sync. Two one-directional Syncthing folders, not a two-way sync: the Mac vault (~/vault) is Send Only; the VPS copy (/home/claudeclaw/vault) is Receive Only with staggered versioning. Agent output flows back through a second folder (/home/claudeclaw/vault/inbox Send Only → Mac ~/vault/inbox/from-claudeclaw Receive Only), with a whitelist .stignore that passes only the three active agent subdirectories. Transport is Tailscale IPv6 (the VPS has dead IPv4 egress on Tailscale — a gotcha that cost a night of debugging); relay, global discovery, and NAT traversal are all off so the sync uses only the known-good private path.

Dashboard access. The ClaudeClaw web UI (3D activity view, kanban, chat, usage) binds to localhost:3141. Rather than trying to publish it through Tailscale Serve (which would have required HTTPS and MagicDNS resolution, both unavailable on the plan), I reach it via a plain SSH tunnel over port 22 with a one-click launcher on the Mac. Zero additional attack surface.

Loopback API integration. scripts/hermes-jefit-cron.sh runs on the VPS (the only machine that can reach 127.0.0.1:3141). It POSTs to the GLOS /api/jefit/sync endpoint with a Bearer token to pull new workout data, then pipes the result through a Python prompt builder and POSTs to /api/chat/send?token=... on the ClaudeClaw dashboard API. If new workouts landed, Donna fires a congratulations message to Telegram in her own voice. GLOS never holds an Anthropic API key. The cron runs every two hours between 06:30 and 22:30 Manila time.

Model routing. ClaudeClaw’s SMART_ROUTING is down-only (simple tasks → Haiku) and is bypassed for any agent with an explicit default model pinned in agent.yaml. Sonnet is the default for specialist agents; Opus is a manual /model opus override for nuclear synthesis runs, then back. The obsidian: block in agent.yaml is context-injection only — it is not filesystem enforcement. Real write-scope restriction comes from the root-owned deny list and OS permissions, not the YAML.

Multi-agent sequencing. Agents were activated in blast-radius order: Karen (librarian, Haiku, vault-read only, lowest risk) first, then research and content agents, outbound-capable agents last. Non-overlapping trigger phrases, not isolated toolsets, provide separation within the shared ~/.claude/skills/ pool.

Why it matters

This proves that Claude can be deployed as programmable infrastructure — not just a chat widget. Any system on the same private network can POST facts to the agent and get a contextually aware, personality-consistent response back, without holding LLM credentials. The security model treats the agent as an untrusted process running on a trusted machine: permissions enforced below the agent where it cannot argue with them, secrets never in plaintext, write scope bounded at the OS level. The public install guide authored from this deployment generalizes the entire pattern for other builders.

AI infrastructureagentic AIself-hostedVPSLinux hardeningClaude

Want something like this?

That's the kind of thing I build. Tell me about yours.