initial scaffold: Meridian LXC (Node 22 + npm @rynfar/meridian + systemd)
Deploys @rynfar/meridian on a Debian 12 LXC, bound to 0.0.0.0:3456. OAuth credentials transferred manually after first deploy (claude login on Mac, scp ~/.claude to /opt/meridian/.claude). systemd unit is enabled but gated on credentials.json existence so the first deploy doesn't crash-loop. LXC has no auth layer — security model is LAN-only reachability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,87 @@
|
||||
# homelab-ansible-lxc-meridian
|
||||
|
||||
Meridian — local Anthropic API powered by Claude Max OAuth, for HAOS LLM integration
|
||||
Ansible config for the Meridian LXC (CTID 457 on pve01, `192.168.1.184`).
|
||||
|
||||
## What it is
|
||||
|
||||
[Meridian](https://github.com/rynfar/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.184 '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.184:/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.184: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.184)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user