feat: k8s:// ClusterIP claim env + complete payload target_repo
Resolve llm-connect / actcore-api / edge-relay without port-forward. Include target_repo on FI and Binky complete payloads for ops UI artefacts.
This commit is contained in:
parent
6383944939
commit
94a15d67aa
7 changed files with 207 additions and 8 deletions
|
|
@ -6,16 +6,20 @@ set -euo pipefail
|
||||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
UNIT_DIR="${HOME}/.config/systemd/user"
|
UNIT_DIR="${HOME}/.config/systemd/user"
|
||||||
ENV_DIR="${HOME}/.config/rein-aharness"
|
ENV_DIR="${HOME}/.config/rein-aharness"
|
||||||
mkdir -p "${UNIT_DIR}" "${ENV_DIR}"
|
BIN_DIR="${HOME}/bin"
|
||||||
|
mkdir -p "${UNIT_DIR}" "${ENV_DIR}" "${BIN_DIR}"
|
||||||
|
|
||||||
cp -f "${ROOT}/deploy/systemd/rein-aharness-claim-loop.service" "${UNIT_DIR}/"
|
cp -f "${ROOT}/deploy/systemd/rein-aharness-claim-loop.service" "${UNIT_DIR}/"
|
||||||
|
# Durable env loader + k8s:// ClusterIP resolution (no port-forward)
|
||||||
|
install -m 755 "${ROOT}/deploy/scripts/rein-aharness-claim" "${BIN_DIR}/rein-aharness-claim"
|
||||||
|
|
||||||
if [[ ! -f "${ENV_DIR}/claim-loop.env" ]]; then
|
if [[ ! -f "${ENV_DIR}/claim-loop.env" ]]; then
|
||||||
cp "${ROOT}/deploy/systemd/claim-loop.env.example" "${ENV_DIR}/claim-loop.env"
|
cp "${ROOT}/deploy/systemd/claim-loop.env.example" "${ENV_DIR}/claim-loop.env"
|
||||||
chmod 600 "${ENV_DIR}/claim-loop.env"
|
chmod 600 "${ENV_DIR}/claim-loop.env"
|
||||||
echo "Created ${ENV_DIR}/claim-loop.env — set ACTIVITY_CORE_URL, token, REPO_MAP."
|
echo "Created ${ENV_DIR}/claim-loop.env — set ACTIVITY_CORE_WORKER_TOKEN and REPO_MAP paths."
|
||||||
else
|
else
|
||||||
echo "Keeping existing ${ENV_DIR}/claim-loop.env"
|
echo "Keeping existing ${ENV_DIR}/claim-loop.env"
|
||||||
|
echo " Tip: migrate URLs with ${ROOT}/deploy/scripts/refresh-claim-loop-k8s-urls.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure package entrypoint exists
|
# Ensure package entrypoint exists
|
||||||
|
|
@ -28,4 +32,5 @@ systemctl --user enable rein-aharness-claim-loop.service
|
||||||
echo "Enabled. Start when env is filled:"
|
echo "Enabled. Start when env is filled:"
|
||||||
echo " systemctl --user start rein-aharness-claim-loop.service"
|
echo " systemctl --user start rein-aharness-claim-loop.service"
|
||||||
echo " journalctl --user -u rein-aharness-claim-loop -f"
|
echo " journalctl --user -u rein-aharness-claim-loop -f"
|
||||||
echo "Host timers remain break-glass until T05 cutover (docs/ops-run-claim-loop.md)."
|
echo "llm-connect / actcore-api: use k8s:// URLs (see activity-core docs/llm-connect-host-access.md)."
|
||||||
|
echo "Do not rely on kubectl port-forward for production claim-loop."
|
||||||
|
|
|
||||||
59
deploy/scripts/refresh-claim-loop-k8s-urls.sh
Executable file
59
deploy/scripts/refresh-claim-loop-k8s-urls.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Rewrite claim-loop.env URL keys to live k8s:// or concrete ClusterIP forms.
|
||||||
|
# Usage:
|
||||||
|
# ./deploy/scripts/refresh-claim-loop-k8s-urls.sh # write k8s:// pseudo-URLs
|
||||||
|
# ./deploy/scripts/refresh-claim-loop-k8s-urls.sh --concrete # write http://ClusterIP:port
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ENV_FILE="${HOME}/.config/rein-aharness/claim-loop.env"
|
||||||
|
KUBECONFIG="${KUBECONFIG:-/etc/rancher/k3s/k3s.yaml}"
|
||||||
|
MODE=k8s
|
||||||
|
if [[ "${1:-}" == "--concrete" ]]; then
|
||||||
|
MODE=concrete
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||||
|
echo "missing ${ENV_FILE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_ip() {
|
||||||
|
local ns="$1" svc="$2"
|
||||||
|
kubectl --kubeconfig="${KUBECONFIG}" -n "${ns}" get svc "${svc}" \
|
||||||
|
-o jsonpath='{.spec.clusterIP}'
|
||||||
|
}
|
||||||
|
|
||||||
|
url_for() {
|
||||||
|
local ns="$1" svc="$2" port="$3"
|
||||||
|
if [[ "${MODE}" == "k8s" ]]; then
|
||||||
|
echo "k8s://${ns}/${svc}:${port}"
|
||||||
|
else
|
||||||
|
local ip
|
||||||
|
ip="$(resolve_ip "${ns}" "${svc}")"
|
||||||
|
echo "http://${ip}:${port}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTIVITY_CORE_URL="$(url_for activity-core actcore-api 8010)"
|
||||||
|
LLM_CONNECT_URL="$(url_for activity-core llm-connect 8080)"
|
||||||
|
STATE_HUB_URL="$(url_for activity-core actcore-statehub-edge-relay 8000)"
|
||||||
|
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
awk -v ac="${ACTIVITY_CORE_URL}" -v llm="${LLM_CONNECT_URL}" -v sh="${STATE_HUB_URL}" '
|
||||||
|
BEGIN { seen_ac=0; seen_llm=0; seen_sh=0 }
|
||||||
|
/^ACTIVITY_CORE_URL=/ { print "ACTIVITY_CORE_URL=" ac; seen_ac=1; next }
|
||||||
|
/^LLM_CONNECT_URL=/ { print "LLM_CONNECT_URL=" llm; seen_llm=1; next }
|
||||||
|
/^STATE_HUB_URL=/ { print "STATE_HUB_URL=" sh; seen_sh=1; next }
|
||||||
|
{ print }
|
||||||
|
END {
|
||||||
|
if (!seen_ac) print "ACTIVITY_CORE_URL=" ac
|
||||||
|
if (!seen_llm) print "LLM_CONNECT_URL=" llm
|
||||||
|
if (!seen_sh) print "STATE_HUB_URL=" sh
|
||||||
|
}
|
||||||
|
' "${ENV_FILE}" > "${tmp}"
|
||||||
|
chmod 600 "${tmp}"
|
||||||
|
mv "${tmp}" "${ENV_FILE}"
|
||||||
|
|
||||||
|
echo "Updated ${ENV_FILE} (${MODE}):"
|
||||||
|
grep -E '^(ACTIVITY_CORE_URL|LLM_CONNECT_URL|STATE_HUB_URL)=' "${ENV_FILE}"
|
||||||
110
deploy/scripts/rein-aharness-claim
Executable file
110
deploy/scripts/rein-aharness-claim
Executable file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Claim-loop launcher: load claim-loop.env and resolve k8s:// Service URLs.
|
||||||
|
|
||||||
|
Used as ~/bin/rein-aharness-claim on railiance01 so shell never sources JSON
|
||||||
|
values (AGENT_HARNESS_REPO_MAP). Supports durable ClusterIP access without
|
||||||
|
kubectl port-forward (ACTIVITY-WP-0027 / docs in activity-core).
|
||||||
|
|
||||||
|
Env file keys may use:
|
||||||
|
LLM_CONNECT_URL=k8s://activity-core/llm-connect:8080
|
||||||
|
ACTIVITY_CORE_URL=k8s://activity-core/actcore-api:8010
|
||||||
|
STATE_HUB_URL=k8s://activity-core/actcore-statehub-edge-relay:8000
|
||||||
|
|
||||||
|
Resolution uses: kubectl -n <ns> get svc <name> -o jsonpath={.spec.clusterIP}
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_K8S_URL = re.compile(
|
||||||
|
r"^k8s://(?P<ns>[a-z0-9-]+)/(?P<svc>[a-z0-9-]+)(?::(?P<port>\d+))?/?$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_env_file(path: Path) -> dict[str, str]:
|
||||||
|
out: dict[str, str] = {}
|
||||||
|
if not path.is_file():
|
||||||
|
return out
|
||||||
|
for line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
out[key.strip()] = value.strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_k8s_url(value: str) -> str:
|
||||||
|
m = _K8S_URL.match(value.strip())
|
||||||
|
if not m:
|
||||||
|
return value
|
||||||
|
ns = m.group("ns")
|
||||||
|
svc = m.group("svc")
|
||||||
|
port = m.group("port") or "80"
|
||||||
|
kubeconfig = os.environ.get("KUBECONFIG", "/etc/rancher/k3s/k3s.yaml")
|
||||||
|
cmd = [
|
||||||
|
"kubectl",
|
||||||
|
f"--kubeconfig={kubeconfig}",
|
||||||
|
"-n",
|
||||||
|
ns,
|
||||||
|
"get",
|
||||||
|
"svc",
|
||||||
|
svc,
|
||||||
|
"-o",
|
||||||
|
"jsonpath={.spec.clusterIP}",
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
ip = subprocess.check_output(cmd, text=True, timeout=30).strip()
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as exc:
|
||||||
|
print(
|
||||||
|
f"rein-aharness-claim: failed to resolve {value!r}: {exc}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
raise SystemExit(2) from exc
|
||||||
|
if not ip or ip == "None":
|
||||||
|
print(
|
||||||
|
f"rein-aharness-claim: empty ClusterIP for {ns}/{svc}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
raise SystemExit(2)
|
||||||
|
return f"http://{ip}:{port}"
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_env(env: dict[str, str]) -> dict[str, str]:
|
||||||
|
keys = (
|
||||||
|
"LLM_CONNECT_URL",
|
||||||
|
"ACTIVITY_CORE_URL",
|
||||||
|
"STATE_HUB_URL",
|
||||||
|
"STATEHUB_URL",
|
||||||
|
)
|
||||||
|
for key in keys:
|
||||||
|
if key in env and env[key].startswith("k8s://"):
|
||||||
|
env[key] = _resolve_k8s_url(env[key])
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
home = Path.home()
|
||||||
|
env = os.environ.copy()
|
||||||
|
# Default kubeconfig for k3s host users
|
||||||
|
env.setdefault("KUBECONFIG", "/etc/rancher/k3s/k3s.yaml")
|
||||||
|
file_env = _load_env_file(home / ".config/rein-aharness/claim-loop.env")
|
||||||
|
env.update(file_env)
|
||||||
|
env = _resolve_env(env)
|
||||||
|
|
||||||
|
rein = home / "rein-aharness" / ".venv" / "bin" / "rein-aharness"
|
||||||
|
if not rein.is_file():
|
||||||
|
# Fall back to PATH
|
||||||
|
cmd = ["rein-aharness", *sys.argv[1:]]
|
||||||
|
else:
|
||||||
|
cmd = [str(rein), *sys.argv[1:]]
|
||||||
|
raise SystemExit(subprocess.call(cmd, env=env))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# Copy to ~/.config/rein-aharness/claim-loop.env and chmod 600
|
# Copy to ~/.config/rein-aharness/claim-loop.env and chmod 600
|
||||||
|
|
||||||
ACTIVITY_CORE_URL=http://127.0.0.1:8010
|
# Prefer k8s:// pseudo-URLs — rein-aharness-claim resolves ClusterIP at start
|
||||||
|
# (no kubectl port-forward). See activity-core docs/llm-connect-host-access.md
|
||||||
|
ACTIVITY_CORE_URL=k8s://activity-core/actcore-api:8010
|
||||||
ACTIVITY_CORE_WORKER_TOKEN=
|
ACTIVITY_CORE_WORKER_TOKEN=
|
||||||
AGENT_HARNESS_WORKER_ID=rein-aharness@railiance01
|
AGENT_HARNESS_WORKER_ID=rein-aharness@railiance01
|
||||||
AGENT_HARNESS_OPS_LABELS=automated
|
AGENT_HARNESS_OPS_LABELS=automated
|
||||||
|
|
@ -9,6 +11,6 @@ AGENT_HARNESS_OPS_LEASE_SECONDS=900
|
||||||
AGENT_HARNESS_CLAIM_INTERVAL=30
|
AGENT_HARNESS_CLAIM_INTERVAL=30
|
||||||
AGENT_HARNESS_REPO_MAP={"freedom-intelligence":"/home/tegwick/freedom-intelligence","binky-control":"/home/tegwick/binky-control"}
|
AGENT_HARNESS_REPO_MAP={"freedom-intelligence":"/home/tegwick/freedom-intelligence","binky-control":"/home/tegwick/binky-control"}
|
||||||
AGENT_HARNESS_REPO_ROOTS=/home/tegwick:/home/tegwick/work
|
AGENT_HARNESS_REPO_ROOTS=/home/tegwick:/home/tegwick/work
|
||||||
# LLM / hub as required by approaches
|
LLM_CONNECT_URL=k8s://activity-core/llm-connect:8080
|
||||||
# LLM_CONNECT_URL=http://...
|
STATE_HUB_URL=k8s://activity-core/actcore-statehub-edge-relay:8000
|
||||||
# STATE_HUB_URL=http://...
|
KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,24 @@ systemctl --user enable --now rein-aharness-claim-loop.service
|
||||||
journalctl --user -u rein-aharness-claim-loop -f
|
journalctl --user -u rein-aharness-claim-loop -f
|
||||||
```
|
```
|
||||||
|
|
||||||
Port-forward if claim-loop runs on host and API is ClusterIP-only:
|
### Host access to cluster services (no port-forward)
|
||||||
|
|
||||||
|
On railiance01 (single-node k3s), set **k8s://** pseudo-URLs in
|
||||||
|
`claim-loop.env` (see `deploy/systemd/claim-loop.env.example`). The
|
||||||
|
`~/bin/rein-aharness-claim` wrapper resolves them to Service ClusterIPs at
|
||||||
|
start. Canon: activity-core `docs/llm-connect-host-access.md`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# migrate existing env
|
||||||
|
./deploy/scripts/refresh-claim-loop-k8s-urls.sh
|
||||||
|
# reinstall wrapper
|
||||||
|
./deploy/scripts/install-claim-loop-user.sh
|
||||||
|
systemctl --user restart rein-aharness-claim-loop.service
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deprecated:** long-lived `kubectl port-forward` to 127.0.0.1:8010 / :8080.
|
||||||
|
|
||||||
|
Legacy note (break-glass only) if ClusterIP routing is broken:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl -n activity-core port-forward svc/actcore-api 8010:8010
|
kubectl -n activity-core port-forward svc/actcore-api 8010:8010
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,7 @@ def _run_fi(target: Path, *, report_to_hub: bool, commit: bool) -> ApproachResul
|
||||||
"skipped_existing": r.skipped_existing,
|
"skipped_existing": r.skipped_existing,
|
||||||
"collection_candidates": r.collection_candidates,
|
"collection_candidates": r.collection_candidates,
|
||||||
"head_after": r.head_after,
|
"head_after": r.head_after,
|
||||||
|
"target_repo": "freedom-intelligence",
|
||||||
},
|
},
|
||||||
reason=r.reason,
|
reason=r.reason,
|
||||||
reopen=not r.ok and not r.skipped_existing,
|
reopen=not r.ok and not r.skipped_existing,
|
||||||
|
|
@ -253,6 +254,7 @@ def _run_brief_daily(
|
||||||
"committed": r.committed,
|
"committed": r.committed,
|
||||||
"skipped_existing": r.skipped_existing,
|
"skipped_existing": r.skipped_existing,
|
||||||
"head_after": r.head_after,
|
"head_after": r.head_after,
|
||||||
|
"target_repo": "binky-control",
|
||||||
},
|
},
|
||||||
reason=r.reason,
|
reason=r.reason,
|
||||||
reopen=not r.ok and not r.skipped_existing,
|
reopen=not r.ok and not r.skipped_existing,
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,12 @@ def process_one(
|
||||||
"approach": ar.approach,
|
"approach": ar.approach,
|
||||||
"ok": ar.ok,
|
"ok": ar.ok,
|
||||||
"reason": ar.reason,
|
"reason": ar.reason,
|
||||||
|
"target_repo": run.target_repo,
|
||||||
**(ar.result or {}),
|
**(ar.result or {}),
|
||||||
}
|
}
|
||||||
|
# Prefer approach-provided target_repo if set
|
||||||
|
if ar.result and ar.result.get("target_repo"):
|
||||||
|
payload["target_repo"] = ar.result["target_repo"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if ar.ok:
|
if ar.ok:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue