Butler 2.3.1: zuverlässige Runtime-Probes #2
4 changed files with 29 additions and 5 deletions
|
|
@ -4,7 +4,7 @@ Unified API proxy and infrastructure management for Homelab Pfannkuchen.
|
|||
|
||||
- **Base URL:** `http://10.4.1.116:8888`
|
||||
- **Authentication:** `Authorization: Bearer <BUTLER_TOKEN>`
|
||||
- **Version:** 2.3.0
|
||||
- **Version:** 2.3.1
|
||||
- **Interactive API documentation:** `/docs`
|
||||
|
||||
## Service proxy
|
||||
|
|
@ -97,6 +97,11 @@ Integration Compose definition: `tests/compose.integration.yaml` (binds only to
|
|||
|
||||
## Changelog
|
||||
|
||||
### 2.3.1 — 22.07.2026
|
||||
|
||||
- Use a writable runtime `known_hosts` file for SSH probes with read-only SSH mounts.
|
||||
- Correct the Semaphore backend port from the repurposed MCP-Dockhand port to port 3010.
|
||||
|
||||
### 2.3.0 — 22.07.2026
|
||||
|
||||
- Added `/overview`, a compact schema-versioned verdict for lightweight language models.
|
||||
|
|
|
|||
5
app.py
5
app.py
|
|
@ -11,7 +11,7 @@ from fastapi.responses import JSONResponse, RedirectResponse
|
|||
from contextlib import asynccontextmanager
|
||||
|
||||
log = logging.getLogger("butler")
|
||||
VERSION = "2.3.0"
|
||||
VERSION = "2.3.1"
|
||||
|
||||
API_DIR = os.environ.get("API_KEY_DIR", "/data/api")
|
||||
VAULT_CACHE_DIR = os.environ.get("VAULT_CACHE_DIR", "/data/vault-cache")
|
||||
|
|
@ -788,7 +788,8 @@ class VMCreate(BaseModel):
|
|||
|
||||
def _ssh(host, cmd, timeout=600):
|
||||
try:
|
||||
r = _sp.run(["ssh","-o","ConnectTimeout=10","-o","StrictHostKeyChecking=accept-new",host,cmd],
|
||||
r = _sp.run(["ssh","-o","ConnectTimeout=10","-o","StrictHostKeyChecking=accept-new",
|
||||
"-o","UserKnownHostsFile=/tmp/butler_known_hosts",host,cmd],
|
||||
capture_output=True, text=True, timeout=timeout)
|
||||
return r.returncode, r.stdout, r.stderr
|
||||
except _sp.TimeoutExpired:
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ services:
|
|||
health_path: "/api/healthz"
|
||||
|
||||
semaphore:
|
||||
url: "http://10.4.1.116:8090"
|
||||
url: "http://10.4.1.116:3010"
|
||||
auth: bearer
|
||||
key_file: semaphore
|
||||
vault_key: semaphore_token
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def test_health_exposes_current_version():
|
|||
with TestClient(app.app) as client:
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["version"] == app.VERSION == "2.3.0"
|
||||
assert response.json()["version"] == app.VERSION == "2.3.1"
|
||||
|
||||
|
||||
def test_invalid_log_target_is_rejected_before_ssh():
|
||||
|
|
@ -145,6 +145,24 @@ def test_backup_item_reports_age_and_severity():
|
|||
assert app._backup_item(1, "", "timeout")["state"] == "unknown"
|
||||
|
||||
|
||||
def test_ssh_uses_writable_runtime_known_hosts(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
class Result:
|
||||
returncode = 0
|
||||
stdout = "ok"
|
||||
stderr = ""
|
||||
|
||||
def fake_run(args, **_kwargs):
|
||||
captured["args"] = args
|
||||
return Result()
|
||||
|
||||
monkeypatch.setattr(app._sp, "run", fake_run)
|
||||
assert app._ssh("sascha@example", "true", timeout=1)[0] == 0
|
||||
joined = " ".join(captured["args"])
|
||||
assert "UserKnownHostsFile=/tmp/butler_known_hosts" in joined
|
||||
|
||||
|
||||
def test_ssh_timeout_is_normalized_instead_of_crashing_collection(monkeypatch):
|
||||
def timeout(*_args, **_kwargs):
|
||||
raise app._sp.TimeoutExpired(cmd=["ssh"], timeout=1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue