hermes: stop provisioning luna a working copy, fix her git/tea access

Three fixes to how luna's git/tea credentials are set up on mars, all found
against the running instance on 2026-08-23.

Drop the host-side clone. hermes-agent-prepare-dirs used to clone this repo
into ${hermesHome}/workspace/homelab, but nothing ever told luna at runtime
that it was there — she self-manages config/profiles/memories, so a path
baked into this file never reached her. She searched /opt/data/homelab and
/workspace, found neither, and concluded she had no repo at all. The
credentials are what actually grant access; any checkout is hers to make
anywhere inside HERMES_WRITE_SAFE_ROOT. The stale directory left by the old
version is deliberately not cleaned up, just unmanaged from here on.

Point credential.helper at the CONTAINER's path. It was written as the host
path (${hermesHome}/.git-credentials), which does not exist inside the
container where git actually reads the config — broken this way from 3c1f3e5
until now. Nothing host-side consumes those credentials any more, so the
container's view is the only one that has to be right; added `containerHome`
to make the distinction explicit at the point of use.

Chown what the oneshot writes. The image's cont-init only chowns the top
level of hermesHome and its own state — it does not recurse into the
root-owned 0600 files this unit drops there (.git-credentials, and tea's
config.yml, which tea also writes 0600), even though it runs afterwards. The
symptom was not an error but an absence: git reported no credential helper
and tea no login. Uses `if` rather than `[ -d x ] && chown` because under
`set -e` a false test on the left of an && list aborts the unit.

gitea.nix carries the matching comment updates: the luna provisioning unit is
server-side only, and her token needs write:repository,write:issue,read:user.
write:issue is the one that is easy to miss — a pull request IS an issue in
gitea's data model, so /pulls endpoints gate on the issue scope category and
`tea pr create` fails with write:repository alone even though clone, fetch
and push all work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S94o42aQ8VkBmEWvDem5xa
This commit is contained in:
2026-08-23 06:30:28 +02:00
co-authored by Claude Opus 5
parent 2d9be98df7
commit 50f83971de
2 changed files with 78 additions and 32 deletions
+18 -6
View File
@@ -220,19 +220,31 @@ in
# - required_approvals=1 + enable_approvals_whitelist(darman only):
# an approval has to come from darman specifically, not luna
# rubber-stamping her own PR from a second identity.
# This is provisioning parity with ci-bot only (account + collaborator +
# branch protection) — it does NOT wire a token into mars/hermes-agent.nix
# yet; that's a separate step once luna actually has git tooling to call.
# This covers the SERVER side only (account + collaborator + branch
# protection). The client side — git/tea inside the hermes-agent container,
# and the token below — lives in hosts/mars/hermes-agent.nix.
#
# luna's own push token (used by whatever git tooling gets wired into
# hermes-agent.nix later) is generated once, the same way ci-bot's was:
# luna's own push token is generated once, the same way ci-bot's was:
# su gitea -s /bin/sh -c \
# 'GITEA_WORK_DIR=/mnt/data/AppData/gitea gitea admin user generate-access-token \
# --username luna --scopes write:repository'
# --username luna --scopes write:repository,write:issue,read:user'
# then stored as a secret (e.g. secrets/mars.yaml's gitea_luna_token) —
# NOT pushed into gitea itself as an Actions secret like ci-bot's is,
# since luna isn't a CI workflow running inside gitea, she's an external
# agent calling out to it.
#
# **write:issue is NOT optional and is easy to miss**: this token started
# life as `write:repository` alone, which clones, fetches and pushes
# branches perfectly well — so everything looks fine right up until the
# first `tea pr create`, which gitea rejects with
# token scope=write:repository,read:user required=read:issue
# A pull request IS an issue in gitea's data model, so every /pulls
# endpoint is gated on the *issue* scope category, not the repository one.
# write:issue covers it (in gitea's scope model write:X implies read:X);
# read:issue alone would satisfy the GET half and then fail the POST that
# actually opens the PR. The error names read:issue only because that's
# the first check tea trips on. Rotating the token is free — the prepare
# oneshot on mars does delete-then-add for the tea login on every start.
systemd.services.gitea-luna-provision = {
description = "Provision luna (Hermes Agent) gitea account + PR-tier repo access";
after = [ "gitea.service" ];