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)"
|
||||
UNIT_DIR="${HOME}/.config/systemd/user"
|
||||
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}/"
|
||||
# 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
|
||||
cp "${ROOT}/deploy/systemd/claim-loop.env.example" "${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
|
||||
echo "Keeping existing ${ENV_DIR}/claim-loop.env"
|
||||
echo " Tip: migrate URLs with ${ROOT}/deploy/scripts/refresh-claim-loop-k8s-urls.sh"
|
||||
fi
|
||||
|
||||
# Ensure package entrypoint exists
|
||||
|
|
@ -28,4 +32,5 @@ systemctl --user enable rein-aharness-claim-loop.service
|
|||
echo "Enabled. Start when env is filled:"
|
||||
echo " systemctl --user start rein-aharness-claim-loop.service"
|
||||
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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue