diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 5a971e8..43a63bb 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -275,11 +275,11 @@ curl ]; - # One-shot: configure tea with the agent's forge token if - # hive-c0re seeded one and tea hasn't been configured yet. - # Runs before the harness service so the first turn can already - # `tea repos create`. *Always* exits 0 — never fail a NixOS - # switch-to-configuration over a missing/temperamental forge. + # One-shot: write tea's config.yml from the seeded forge token so + # the agent can use `tea` without interactive prompts. Runs on + # every boot so a rotated token (hive-c0re remints on each agent + # rebuild) is always reflected. *Always* exits 0 — never fail a + # NixOS switch-to-configuration over a missing/temperamental forge. systemd.services.tea-login = { description = "configure tea CLI from hive-forge token (best-effort)"; wantedBy = [ "multi-user.target" ]; @@ -289,18 +289,18 @@ RemainAfterExit = true; }; path = [ - pkgs.tea + pkgs.curl + pkgs.python3 pkgs.coreutils ]; script = '' - # No `set -e`: any subshell failure here (tea exit, mkdir, - # missing forge) must not propagate. A failed unit aborts - # `nixos-container update`, which would block every rebuild. - CONFIG=/root/.config/tea/config.yml + # No `set -e`: any subshell failure must not propagate. + # A failed unit aborts `nixos-container update` which blocks rebuilds. + FORGE_URL=${lib.escapeShellArg config.hyperhive.forge.url} # Manager bind-mounts state at /state; sub-agents at # /agents//state. Glob both — each container only sees - # its own mount, so there's exactly one hit either way (or - # zero, when the forge hasn't been seeded yet). + # its own mount, so there is exactly one hit (or zero when + # the forge hasn't been seeded yet). TOKEN_FILE="" for f in /state/forge-token /agents/*/state/forge-token; do if [ -f "$f" ]; then @@ -309,22 +309,42 @@ fi done if [ -z "$TOKEN_FILE" ]; then - echo "tea-login: no forge-token (hive-forge not seeded); skipping" + echo "tea-login: no forge-token found; skipping" exit 0 fi - if [ -f "$CONFIG" ]; then - echo "tea-login: $CONFIG already present; skipping" + TOKEN=$(cat "$TOKEN_FILE") + # Resolve the agent username from the forge API. + USER=$(curl -sf --max-time 5 \ + -H "Authorization: token $TOKEN" \ + "$FORGE_URL/api/v1/user" \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('login',''))" \ + 2>/dev/null || true) + if [ -z "$USER" ]; then + echo "tea-login: could not resolve username from forge API; skipping" exit 0 fi + # tea reads config from $HOME/.config/tea/config.yml. + # Write it directly so we control default:true and always + # refresh a rotated token — no 'tea login add' interactive dance. + CONFIG="$HOME/.config/tea/config.yml" mkdir -p "$(dirname "$CONFIG")" || true - if ! tea login add \ - --name forge \ - --url ${lib.escapeShellArg config.hyperhive.forge.url} \ - --token "$(cat "$TOKEN_FILE")"; then - echo "tea-login: tea login add failed; continuing without tea" - exit 0 - fi - echo "tea-login: configured for ${config.hyperhive.forge.url} from $TOKEN_FILE" + cat > "$CONFIG" << EOF +logins: + - name: forge + url: $FORGE_URL + token: $TOKEN + default: true + ssh_host: "" + ssh_key: "" + insecure: false + ssh_agent: false + user: $USER +preferences: + editor: false + flag_defaults: + remote: "" +EOF + echo "tea-login: configured for $FORGE_URL as $USER" ''; };