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
|
# 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
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[defaults]
|
||||||
|
interpreter_python = /usr/bin/python3
|
||||||
|
deprecation_warnings = False
|
||||||
|
host_key_checking = False
|
||||||
|
result_format = yaml
|
||||||
|
callbacks_enabled = profile_tasks, timer
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# deploy.sh — Deploy Meridian LXC
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./deploy.sh # full deploy
|
||||||
|
# ./deploy.sh --tags meridian # meridian role only
|
||||||
|
# ./deploy.sh -v # verbose output
|
||||||
|
# ==============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HOST_IP="$(grep -E '^[0-9]' inventory.ini | head -1 | awk '{print $1}')"
|
||||||
|
HOST_USER="$(grep -o 'ansible_user=[^ ]*' inventory.ini | head -1 | cut -d= -f2)"
|
||||||
|
|
||||||
|
echo "==> Checking connectivity to ${HOST_USER}@${HOST_IP} ..."
|
||||||
|
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "${HOST_USER}@${HOST_IP}" true 2>/dev/null; then
|
||||||
|
echo " Cannot SSH to ${HOST_IP} — refreshing host key ..."
|
||||||
|
ssh-keygen -R "$HOST_IP" 2>/dev/null || true
|
||||||
|
ssh-keyscan -H "$HOST_IP" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Installing Ansible collections ..."
|
||||||
|
ansible-galaxy collection install -r requirements.yml --force 2>/dev/null
|
||||||
|
|
||||||
|
echo "==> Running deploy playbook ..."
|
||||||
|
ansible-playbook -i inventory.ini site.yml "$@"
|
||||||
|
|
||||||
|
echo "==> Verifying ..."
|
||||||
|
ssh "${HOST_USER}@${HOST_IP}" bash -s <<'VERIFY'
|
||||||
|
echo "Node: $(node --version 2>/dev/null || echo missing)"
|
||||||
|
echo "Meridian binary: $(which meridian 2>/dev/null || echo missing)"
|
||||||
|
echo "Service:"
|
||||||
|
systemctl is-enabled meridian 2>&1
|
||||||
|
systemctl is-active meridian 2>&1
|
||||||
|
if systemctl is-active --quiet meridian; then
|
||||||
|
curl -sf --max-time 3 http://127.0.0.1:3456/v1/messages -X POST -H 'Content-Type: application/json' -d '{}' >/dev/null 2>&1 && echo "API reachable on :3456" || echo "API on :3456 not responding (expected if OAuth creds missing)"
|
||||||
|
fi
|
||||||
|
VERIFY
|
||||||
|
|
||||||
|
echo "==> Done."
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
192.168.1.184 ansible_user=cbalders ansible_become=yes
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
collections:
|
||||||
|
- name: community.general
|
||||||
|
- name: ansible.posix
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
- name: reload systemd
|
||||||
|
systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
- name: restart meridian
|
||||||
|
systemd:
|
||||||
|
name: meridian
|
||||||
|
state: restarted
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
# ---------- System setup ----------
|
||||||
|
- name: Set timezone
|
||||||
|
copy:
|
||||||
|
content: "{{ timezone }}"
|
||||||
|
dest: /etc/timezone
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Link localtime
|
||||||
|
file:
|
||||||
|
src: "/usr/share/zoneinfo/{{ timezone }}"
|
||||||
|
dest: /etc/localtime
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Install base packages
|
||||||
|
apt:
|
||||||
|
name: "{{ packages }}"
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
|
||||||
|
- name: Ensure users exist
|
||||||
|
user:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
groups: "{{ item.groups }}"
|
||||||
|
shell: "{{ item.shell }}"
|
||||||
|
append: true
|
||||||
|
loop: "{{ users }}"
|
||||||
|
|
||||||
|
- name: Authorize SSH keys for cbalders
|
||||||
|
authorized_key:
|
||||||
|
user: cbalders
|
||||||
|
key: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
loop: "{{ ssh_authorized_keys }}"
|
||||||
|
|
||||||
|
- name: Passwordless sudo for cbalders
|
||||||
|
copy:
|
||||||
|
content: "cbalders ALL=(ALL) NOPASSWD:ALL\n"
|
||||||
|
dest: /etc/sudoers.d/90-cbalders
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0440'
|
||||||
|
|
||||||
|
# ---------- Node 22 (NodeSource apt repo) ----------
|
||||||
|
- name: Ensure /etc/apt/keyrings exists
|
||||||
|
file:
|
||||||
|
path: /etc/apt/keyrings
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Add NodeSource GPG key
|
||||||
|
get_url:
|
||||||
|
url: https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key
|
||||||
|
dest: /etc/apt/keyrings/nodesource.asc
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Add NodeSource apt repo
|
||||||
|
copy:
|
||||||
|
content: "deb [signed-by=/etc/apt/keyrings/nodesource.asc] https://deb.nodesource.com/node_{{ meridian_node_major }}.x nodistro main\n"
|
||||||
|
dest: /etc/apt/sources.list.d/nodesource.list
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
register: nodesource_repo
|
||||||
|
|
||||||
|
- name: apt update after NodeSource add
|
||||||
|
apt:
|
||||||
|
update_cache: true
|
||||||
|
when: nodesource_repo.changed
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
apt:
|
||||||
|
name: nodejs
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Verify Node major version
|
||||||
|
command: node --version
|
||||||
|
register: node_version
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Fail if Node major mismatch
|
||||||
|
fail:
|
||||||
|
msg: "Node {{ node_version.stdout }} installed; expected v{{ meridian_node_major }}.x"
|
||||||
|
when: not node_version.stdout.startswith("v" ~ meridian_node_major | string ~ ".")
|
||||||
|
|
||||||
|
# ---------- Meridian user + home ----------
|
||||||
|
- name: Ensure meridian system user
|
||||||
|
user:
|
||||||
|
name: "{{ meridian_user }}"
|
||||||
|
system: true
|
||||||
|
home: "{{ meridian_home }}"
|
||||||
|
shell: /usr/sbin/nologin
|
||||||
|
create_home: true
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Ensure meridian home perms
|
||||||
|
file:
|
||||||
|
path: "{{ meridian_home }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ meridian_user }}"
|
||||||
|
group: "{{ meridian_user }}"
|
||||||
|
mode: '0750'
|
||||||
|
|
||||||
|
- name: Ensure .claude credentials dir
|
||||||
|
file:
|
||||||
|
path: "{{ meridian_home }}/.claude"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ meridian_user }}"
|
||||||
|
group: "{{ meridian_user }}"
|
||||||
|
mode: '0700'
|
||||||
|
|
||||||
|
# ---------- Install Meridian via npm ----------
|
||||||
|
- name: Install @rynfar/meridian globally
|
||||||
|
npm:
|
||||||
|
name: "@rynfar/meridian"
|
||||||
|
global: true
|
||||||
|
state: latest
|
||||||
|
register: meridian_install
|
||||||
|
|
||||||
|
- name: Resolve meridian binary path
|
||||||
|
command: which meridian
|
||||||
|
register: meridian_bin
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# ---------- systemd unit ----------
|
||||||
|
- name: Deploy meridian systemd unit
|
||||||
|
template:
|
||||||
|
src: meridian.service.j2
|
||||||
|
dest: /etc/systemd/system/meridian.service
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
notify: reload systemd
|
||||||
|
|
||||||
|
- name: Flush handlers (reload systemd before enable)
|
||||||
|
meta: flush_handlers
|
||||||
|
|
||||||
|
- name: Enable meridian service
|
||||||
|
systemd:
|
||||||
|
name: meridian
|
||||||
|
enabled: true
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
# OAuth creds (~/.claude/) are scp'd in manually after first deploy.
|
||||||
|
# Start the service only if creds are present — otherwise the SDK would
|
||||||
|
# crash-loop on missing credentials.
|
||||||
|
- name: Check for Claude OAuth credentials
|
||||||
|
stat:
|
||||||
|
path: "{{ meridian_home }}/.claude/.credentials.json"
|
||||||
|
register: claude_creds
|
||||||
|
|
||||||
|
- name: Start meridian if credentials present
|
||||||
|
systemd:
|
||||||
|
name: meridian
|
||||||
|
state: started
|
||||||
|
when: claude_creds.stat.exists
|
||||||
|
|
||||||
|
- name: Bootstrap reminder
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
Meridian installed at {{ meridian_bin.stdout }}.
|
||||||
|
OAuth credentials not yet present at {{ meridian_home }}/.claude/.credentials.json.
|
||||||
|
Bootstrap on your Mac:
|
||||||
|
npm i -g @anthropic-ai/claude-code && claude login
|
||||||
|
Then transfer creds:
|
||||||
|
scp -r ~/.claude cbalders@{{ inventory_hostname }}:/tmp/.claude-bootstrap
|
||||||
|
On the LXC:
|
||||||
|
sudo cp -r /tmp/.claude-bootstrap/. {{ meridian_home }}/.claude/
|
||||||
|
sudo chown -R {{ meridian_user }}:{{ meridian_user }} {{ meridian_home }}/.claude/
|
||||||
|
sudo systemctl start meridian
|
||||||
|
when: not claude_creds.stat.exists
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Meridian (Anthropic API → Claude Code SDK bridge)
|
||||||
|
Documentation=https://github.com/rynfar/meridian
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ meridian_user }}
|
||||||
|
Group={{ meridian_user }}
|
||||||
|
WorkingDirectory={{ meridian_home }}
|
||||||
|
Environment=HOME={{ meridian_home }}
|
||||||
|
Environment=MERIDIAN_HOST={{ meridian_host }}
|
||||||
|
Environment=MERIDIAN_PORT={{ meridian_port }}
|
||||||
|
Environment=MERIDIAN_IDLE_TIMEOUT_SECONDS={{ meridian_idle_timeout_seconds }}
|
||||||
|
ExecStart={{ meridian_bin.stdout }}
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
- name: restart node_exporter
|
||||||
|
systemd:
|
||||||
|
name: node_exporter
|
||||||
|
state: restarted
|
||||||
|
daemon_reload: yes
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
- name: Check if node_exporter is installed
|
||||||
|
stat:
|
||||||
|
path: /usr/local/bin/node_exporter
|
||||||
|
register: ne_bin
|
||||||
|
|
||||||
|
- name: Set architecture
|
||||||
|
set_fact:
|
||||||
|
ne_arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"
|
||||||
|
|
||||||
|
- name: Get latest node_exporter version
|
||||||
|
uri:
|
||||||
|
url: https://api.github.com/repos/prometheus/node_exporter/releases/latest
|
||||||
|
return_content: yes
|
||||||
|
register: ne_release
|
||||||
|
when: not ne_bin.stat.exists
|
||||||
|
|
||||||
|
- name: Set node_exporter version
|
||||||
|
set_fact:
|
||||||
|
ne_version: "{{ ne_release.json.tag_name | regex_replace('^v', '') }}"
|
||||||
|
when: not ne_bin.stat.exists
|
||||||
|
|
||||||
|
- name: Download node_exporter
|
||||||
|
get_url:
|
||||||
|
url: "https://github.com/prometheus/node_exporter/releases/download/v{{ ne_version }}/node_exporter-{{ ne_version }}.linux-{{ ne_arch }}.tar.gz"
|
||||||
|
dest: /tmp/node_exporter.tar.gz
|
||||||
|
when: not ne_bin.stat.exists
|
||||||
|
|
||||||
|
- name: Extract node_exporter
|
||||||
|
unarchive:
|
||||||
|
src: /tmp/node_exporter.tar.gz
|
||||||
|
dest: /tmp/
|
||||||
|
remote_src: yes
|
||||||
|
when: not ne_bin.stat.exists
|
||||||
|
|
||||||
|
- name: Install node_exporter binary
|
||||||
|
copy:
|
||||||
|
src: "/tmp/node_exporter-{{ ne_version }}.linux-{{ ne_arch }}/node_exporter"
|
||||||
|
dest: /usr/local/bin/node_exporter
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0755'
|
||||||
|
remote_src: yes
|
||||||
|
when: not ne_bin.stat.exists
|
||||||
|
notify: restart node_exporter
|
||||||
|
|
||||||
|
- name: Create node_exporter user
|
||||||
|
user:
|
||||||
|
name: node_exporter
|
||||||
|
system: yes
|
||||||
|
shell: /usr/sbin/nologin
|
||||||
|
create_home: no
|
||||||
|
|
||||||
|
- name: Deploy node_exporter systemd service
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
[Unit]
|
||||||
|
Description=Prometheus Node Exporter
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=node_exporter
|
||||||
|
Group=node_exporter
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/local/bin/node_exporter --collector.textfile.directory=/var/lib/node_exporter/textfile
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
dest: /etc/systemd/system/node_exporter.service
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0644'
|
||||||
|
notify: restart node_exporter
|
||||||
|
|
||||||
|
- name: Create textfile collector directory
|
||||||
|
file:
|
||||||
|
path: /var/lib/node_exporter/textfile
|
||||||
|
state: directory
|
||||||
|
owner: node_exporter
|
||||||
|
group: node_exporter
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Deploy CPU temperature collector script
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
#!/bin/bash
|
||||||
|
TEMP=$(vcgencmd measure_temp 2>/dev/null | grep -oP '[0-9.]+')
|
||||||
|
if [ -n "$TEMP" ]; then
|
||||||
|
echo "# HELP node_cpu_temperature_celsius CPU temperature from vcgencmd"
|
||||||
|
echo "# TYPE node_cpu_temperature_celsius gauge"
|
||||||
|
echo "node_cpu_temperature_celsius $TEMP"
|
||||||
|
fi > /var/lib/node_exporter/textfile/cpu_temp.prom
|
||||||
|
dest: /usr/local/bin/collect-cpu-temp.sh
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Schedule CPU temperature collection (every minute)
|
||||||
|
cron:
|
||||||
|
name: "node_exporter cpu temp"
|
||||||
|
user: node_exporter
|
||||||
|
job: "/usr/local/bin/collect-cpu-temp.sh"
|
||||||
|
|
||||||
|
- name: Run initial temperature collection
|
||||||
|
command: /usr/local/bin/collect-cpu-temp.sh
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Enable and start node_exporter
|
||||||
|
systemd:
|
||||||
|
name: node_exporter
|
||||||
|
enabled: yes
|
||||||
|
state: started
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: Clean up download
|
||||||
|
file:
|
||||||
|
path: /tmp/node_exporter.tar.gz
|
||||||
|
state: absent
|
||||||
|
when: not ne_bin.stat.exists
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
# ==============================================================================
|
||||||
|
# Meridian LXC — Site Playbook
|
||||||
|
# ==============================================================================
|
||||||
|
# Local Anthropic API powered by Chuck's Claude Max OAuth subscription.
|
||||||
|
# Bridges the Claude Code SDK to /v1/messages so HAOS's anthropic conversation
|
||||||
|
# integration (and any Anthropic-compatible client) can use the Max subscription
|
||||||
|
# instead of paid API tokens.
|
||||||
|
#
|
||||||
|
# Security: Meridian has no auth layer of its own. LAN-only reachability is
|
||||||
|
# the entire security model — no Caddy public vhost, no Cloudflare tunnel.
|
||||||
|
# OAuth bootstrap is manual: `claude login` on Chuck's Mac, scp ~/.claude/ to
|
||||||
|
# /opt/meridian/.claude/ on the LXC, then `systemctl restart meridian`.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./deploy.sh # full deploy
|
||||||
|
# ./deploy.sh --tags meridian # meridian role only
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
- name: Deploy Meridian LXC
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
vars_files:
|
||||||
|
- vars/main.yml
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Deploy banner
|
||||||
|
debug:
|
||||||
|
msg: "===== {{ ansible_play_name }} → {{ inventory_hostname }} ({{ ansible_host | default(inventory_hostname) }}) ====="
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- meridian
|
||||||
|
- node_exporter
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
timezone: America/Toronto
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- apt-utils
|
||||||
|
- bash-completion
|
||||||
|
- ca-certificates
|
||||||
|
- curl
|
||||||
|
- git
|
||||||
|
- gnupg
|
||||||
|
- htop
|
||||||
|
- net-tools
|
||||||
|
- openssh-server
|
||||||
|
- python3
|
||||||
|
- python3-pip
|
||||||
|
- sudo
|
||||||
|
- vim
|
||||||
|
- wget
|
||||||
|
|
||||||
|
users:
|
||||||
|
- name: cbalders
|
||||||
|
groups: sudo
|
||||||
|
shell: /bin/bash
|
||||||
|
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINALaic1jpoP6t1urbZqJLI1eU5NeTVD9k8AAMAvOvvk OfficeMini"
|
||||||
|
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGzTHdCiQjhIHsGB8oMpyKtr9TZXrXeIRKwcwe698zMW Generated By Termius"
|
||||||
|
|
||||||
|
# Meridian
|
||||||
|
meridian_user: meridian
|
||||||
|
meridian_home: /opt/meridian
|
||||||
|
meridian_port: 3456
|
||||||
|
meridian_host: "0.0.0.0"
|
||||||
|
meridian_idle_timeout_seconds: 300
|
||||||
|
meridian_node_major: 22
|
||||||
Reference in New Issue
Block a user