Add backstage Ollama helper for diagnostics
This commit is contained in:
parent
acb6817c98
commit
8488c9523e
15
README.md
15
README.md
|
|
@ -853,6 +853,21 @@ other nodes can reach `OLLAMA_HOST=http://192.168.100.73:11434`:
|
|||
./jeannie website-ollama-listen
|
||||
```
|
||||
|
||||
`jeannie` also has a backstage local AI helper for doctor commands.
|
||||
It is not a separate CLI action; when `ai_gateway.enabled` is true in
|
||||
`homelab.yml`, doctor commands try the configured Ollama model and silently
|
||||
skip the helper if Ollama is down. The default lightweight model is:
|
||||
|
||||
```bash
|
||||
ollama pull qwen2.5:0.5b
|
||||
```
|
||||
|
||||
Disable it for low-CPU sessions with:
|
||||
|
||||
```bash
|
||||
LAB_BACKSTAGE_BRAIN_ENABLED=false ./jeannie doctor-edge
|
||||
```
|
||||
|
||||
The CV page has two client-side presentation modes:
|
||||
|
||||
- `Elegant`: dark, minimal, terminal-inspired styling with a square profile
|
||||
|
|
|
|||
|
|
@ -853,6 +853,21 @@ other nodes can reach `OLLAMA_HOST=http://192.168.100.73:11434`:
|
|||
./{{ main_script }} website-ollama-listen
|
||||
```
|
||||
|
||||
`{{ main_script }}` also has a backstage local AI helper for doctor commands.
|
||||
It is not a separate CLI action; when `ai_gateway.enabled` is true in
|
||||
`homelab.yml`, doctor commands try the configured Ollama model and silently
|
||||
skip the helper if Ollama is down. The default lightweight model is:
|
||||
|
||||
```bash
|
||||
ollama pull qwen2.5:0.5b
|
||||
```
|
||||
|
||||
Disable it for low-CPU sessions with:
|
||||
|
||||
```bash
|
||||
LAB_BACKSTAGE_BRAIN_ENABLED=false ./{{ main_script }} doctor-edge
|
||||
```
|
||||
|
||||
The CV page has two client-side presentation modes:
|
||||
|
||||
- `Elegant`: dark, minimal, terminal-inspired styling with a square profile
|
||||
|
|
|
|||
|
|
@ -117,3 +117,10 @@ pimox:
|
|||
worker_base_vmid: 9010
|
||||
default_worker_count: 1
|
||||
worker_name_prefix: pimox-worker
|
||||
|
||||
ai_gateway:
|
||||
provider: ollama
|
||||
enabled: true
|
||||
url: http://192.168.100.73:11434
|
||||
model: qwen2.5:0.5b
|
||||
timeout_seconds: 20
|
||||
|
|
|
|||
126
jeannie
126
jeannie
|
|
@ -60,6 +60,11 @@ mapping = {
|
|||
"services.local_registry.endpoint": "LAB_REGISTRY_ENDPOINT",
|
||||
"services.rpi_dns.pihole_web_port": "PIHOLE_WEB_PORT",
|
||||
"services.rpi_dns.uptime_kuma_port": "UPTIME_KUMA_PORT",
|
||||
"ai_gateway.provider": "LAB_AI_GATEWAY_PROVIDER",
|
||||
"ai_gateway.enabled": "LAB_BACKSTAGE_BRAIN_ENABLED",
|
||||
"ai_gateway.url": "LAB_AI_GATEWAY_URL",
|
||||
"ai_gateway.model": "LAB_AI_GATEWAY_MODEL",
|
||||
"ai_gateway.timeout_seconds": "LAB_AI_GATEWAY_TIMEOUT_SECONDS",
|
||||
"pimox.worker_base_vmid": "LAB_PIMOX_WORKER_BASE_VMID",
|
||||
"pimox.default_worker_count": "LAB_PIMOX_WORKER_COUNT",
|
||||
}
|
||||
|
|
@ -3822,6 +3827,89 @@ status_http() {
|
|||
fi
|
||||
}
|
||||
|
||||
backstage_brain_enabled() {
|
||||
local provider="${LAB_AI_GATEWAY_PROVIDER:-ollama}"
|
||||
local enabled="${LAB_BACKSTAGE_BRAIN_ENABLED:-false}"
|
||||
|
||||
[[ "${provider,,}" == "ollama" ]] && truthy "${enabled}"
|
||||
}
|
||||
|
||||
backstage_brain_note() {
|
||||
local topic="$1"
|
||||
local endpoint="${LAB_AI_GATEWAY_URL:-${LAB_OLLAMA_URL:-http://127.0.0.1:11434}}"
|
||||
local model="${LAB_AI_GATEWAY_MODEL:-qwen2.5:0.5b}"
|
||||
local timeout="${LAB_AI_GATEWAY_TIMEOUT_SECONDS:-20}"
|
||||
local context
|
||||
|
||||
if ! backstage_brain_enabled; then
|
||||
return 0
|
||||
fi
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
context="$(cat)"
|
||||
BACKSTAGE_CONTEXT="${context}" python3 - "${endpoint}" "${model}" "${timeout}" "${topic}" <<'PY' || true
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
endpoint, model, timeout, topic = sys.argv[1:5]
|
||||
context = os.environ.get("BACKSTAGE_CONTEXT", "").strip()
|
||||
try:
|
||||
timeout_seconds = int(timeout)
|
||||
except ValueError:
|
||||
timeout_seconds = 20
|
||||
|
||||
prompt = f"""You are the backstage helper for a personal homelab script named jeannie.
|
||||
Be concise and operational. Do not invent facts. Use only the supplied context.
|
||||
Return at most 5 bullets. Focus on likely cause, next command, and risk.
|
||||
|
||||
Topic: {topic}
|
||||
|
||||
Context:
|
||||
{context}
|
||||
"""
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"options": {
|
||||
"temperature": 0.1,
|
||||
"num_predict": 220,
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
tags_request = urllib.request.Request(f"{endpoint.rstrip('/')}/api/tags")
|
||||
with urllib.request.urlopen(tags_request, timeout=3) as response:
|
||||
tags = json.loads(response.read().decode("utf-8"))
|
||||
available = {item.get("name", "") for item in tags.get("models", [])}
|
||||
if model not in available and f"{model}:latest" not in available:
|
||||
print(f"\n== Backstage Notes ==\nOllama is reachable, but model '{model}' is not pulled. Run: ollama pull {model}")
|
||||
raise SystemExit(0)
|
||||
|
||||
request = urllib.request.Request(
|
||||
f"{endpoint.rstrip('/')}/api/generate",
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError):
|
||||
raise SystemExit(0)
|
||||
|
||||
note = (result.get("response") or "").strip()
|
||||
if note:
|
||||
print("\n== Backstage Notes ==")
|
||||
print(note)
|
||||
PY
|
||||
}
|
||||
|
||||
status_systemd_units() {
|
||||
local unit
|
||||
|
||||
|
|
@ -4104,6 +4192,16 @@ Next steps:
|
|||
- If only Gitea is unreachable, run ./jeannie doctor-gitea.
|
||||
- If both are unreachable from OCI, check Tailscale routes and bootstrap/edge.
|
||||
- Runbook: docs/runbooks/edge-failures.md
|
||||
EOF
|
||||
|
||||
backstage_brain_note "doctor-edge" <<EOF
|
||||
Doctor command: doctor-edge
|
||||
Public domain: ${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/
|
||||
Public Gitea URL: ${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}
|
||||
OCI edge SSH target: ${edge_user}@${edge_host}
|
||||
Traefik backend from edge: http://${traefik_ip}:80/
|
||||
Gitea backend from edge: http://${gitea_ts_ip}:${gitea_http_port}/
|
||||
Known runbook: docs/runbooks/edge-failures.md
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
@ -4140,6 +4238,16 @@ Next steps:
|
|||
- If local works but public /git/ fails, check Tailscale and bootstrap/edge.
|
||||
- If Git SSH fails, check the gitea remote and registered SSH key.
|
||||
- Runbook: docs/runbooks/gitea-failures.md
|
||||
EOF
|
||||
|
||||
backstage_brain_note "doctor-gitea" <<EOF
|
||||
Doctor command: doctor-gitea
|
||||
Gitea local URL: http://127.0.0.1:${gitea_http_port}/
|
||||
Gitea LAN host: ${gitea_host}
|
||||
Gitea Tailscale backend: http://${gitea_ts_ip}:${gitea_http_port}/
|
||||
Gitea SSH port: ${gitea_ssh_port}
|
||||
Gitea public URL: ${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}
|
||||
Known runbook: docs/runbooks/gitea-failures.md
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
@ -4155,6 +4263,15 @@ Next steps:
|
|||
- If Pi-hole works but Unbound fails, DNS should still resolve through configured public fallbacks.
|
||||
- If Pi-hole DNS fails, clients using the RPi as DNS will be affected.
|
||||
- Reapply the stack with ./jeannie rpi-services.
|
||||
EOF
|
||||
|
||||
backstage_brain_note "doctor-rpi" <<EOF
|
||||
Doctor command: doctor-rpi
|
||||
RPi LAN host: ${LAB_RPI_HOST:-${LAB_RASPBERRY_HOST:-192.168.100.89}}
|
||||
RPi Tailscale IP: ${LAB_RPI_TAILSCALE_IP:-unknown}
|
||||
Docker NVMe root: ${LAB_RPI_DOCKER_NVME_ROOT:-/nvme-storage/docker}
|
||||
Docker fallback root: ${LAB_RPI_DOCKER_FALLBACK_ROOT:-/var/lib/docker}
|
||||
Services: Pi-hole, Unbound, Uptime Kuma
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
@ -4182,6 +4299,15 @@ Next steps:
|
|||
- If Pimox workers are stopped, start them with ./jeannie start-cluster.
|
||||
- If kubelet versions drift, run ./jeannie doctor-versions.
|
||||
- Runbook: docs/runbooks/cluster-stop-start-failures.md
|
||||
EOF
|
||||
|
||||
backstage_brain_note "doctor-cluster" <<EOF
|
||||
Doctor command: doctor-cluster
|
||||
Kubeconfig path: ${KUBECONFIG_PATH}
|
||||
Traefik LoadBalancer IP: ${LAB_TRAEFIK_LB_IP:-192.168.100.240}
|
||||
Pimox host: ${LAB_PIMOX_USER:-${TF_VAR_pimox_user:-jv}}@${LAB_PIMOX_HOST:-${TF_VAR_pimox_host:-192.168.100.80}}
|
||||
Worker storage: ${LAB_PIMOX_WORKER_STORAGE:-${TF_VAR_pimox_worker_storage:-opi5_ssd}}
|
||||
Known runbook: docs/runbooks/cluster-stop-start-failures.md
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue