Files
homelab-ansible-lxc-meridian/README.md
T
2026-05-17 21:39:49 -04:00

3.4 KiB

homelab-ansible-lxc-meridian

Ansible config for the Meridian LXC (CTID 457 on pve01, 192.168.1.164).

What it is

Meridian is a local Anthropic API server backed by the Claude Code SDK. It translates /v1/messages calls into Claude Code SDK query() calls so any Anthropic-compatible client can run against Chuck's Claude Max subscription instead of paid API tokens.

Primary consumer: HAOS's built-in anthropic conversation integration (via a custom_component fork that adds CONF_BASE_URL).

Architecture

  • Debian 12 LXC, Node 22 from NodeSource apt repo
  • @rynfar/meridian installed globally via npm
  • systemd unit meridian.service running as user meridian, binding to 0.0.0.0:3456
  • OAuth credentials live at /opt/meridian/.claude/ (transferred manually after first deploy — see Bootstrap below)
  • No auth at the Meridian layer. LAN-only reachability is the entire security model — no Caddy public vhost, no Cloudflare tunnel.

Bootstrap

  1. Provision the LXC via homelab-terraform/lxc (terraform apply).
  2. Run the LXC bootstrap one-liner from feedback_lxc_bootstrap_user:
    ssh root@192.168.1.164 'apt-get update && apt-get install -y sudo && useradd -m -s /bin/bash cbalders && echo "cbalders ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/90-cbalders && chmod 440 /etc/sudoers.d/90-cbalders'
    
    (Plus authorized_keys for cbalders.)
  3. Local first deploy (Semaphore can't reach a fresh host):
    ./deploy.sh
    
    Expect: Node 22 installed, @rynfar/meridian installed, systemd unit deployed and enabled but not started (no creds yet — claude_creds.stat.exists gates the start task).
  4. On Chuck's Mac:
    npm i -g @anthropic-ai/claude-code
    claude login          # browser flow → ~/.claude/.credentials.json
    scp -r ~/.claude cbalders@192.168.1.164:/tmp/.claude-bootstrap
    
  5. On the LXC:
    sudo cp -r /tmp/.claude-bootstrap/. /opt/meridian/.claude/
    sudo chown -R meridian:meridian /opt/meridian/.claude/
    sudo systemctl start meridian
    
  6. Smoke from a LAN host:
    curl http://192.168.1.164:3456/v1/messages \
      -H 'Content-Type: application/json' \
      -H 'x-api-key: placeholder' \
      -H 'anthropic-version: 2023-06-01' \
      -d '{"model":"claude-sonnet-4-5","max_tokens":100,"messages":[{"role":"user","content":"hi"}]}'
    

Operations

  • Subsequent deploys: via Semaphore template "Meridian Deploy" (added to the sync-semaphore-state.py manifest).
  • Token refresh: handled automatically by the Claude Code SDK; if it ever fails, sudo -u meridian /usr/bin/meridian refresh-token from the LXC.
  • Restart after creds change: sudo systemctl restart meridian.
  • Logs: journalctl -u meridian -f.

Files

roles/meridian/        Node 22 install + npm i meridian + systemd unit
roles/node_exporter/   Prometheus exporter for fleet metrics
vars/main.yml          base packages, ssh keys, meridian config
site.yml               playbook entrypoint
inventory.ini          single host (192.168.1.164)
deploy.sh              wrapper for local first-run

Memory pointers

  • project_meridian — overall design, OAuth model, consumers
  • feedback_local_dns_only — DNS convention (no public CF for services)
  • feedback_lxc_bootstrap_user — root bootstrap pattern for fresh LXCs
  • feedback_fresh_host_bootstrap — Semaphore can't reach fresh hosts