diff --git a/scripts/t3-provision-users.sh b/scripts/t3-provision-users.sh index 33edf3fd..27a1beda 100644 --- a/scripts/t3-provision-users.sh +++ b/scripts/t3-provision-users.sh @@ -95,6 +95,26 @@ EOF log "wrote OIDC kubeconfig -> $user:~/.kube/config" } +# Share the admin's Claude subscription with a non-admin: inject CLAUDE_CODE_OAUTH_TOKEN +# (the staged long-lived token) into their t3-serve env — ONLY if they have neither their +# own ~/.claude/.credentials.json (own login) nor an existing token. Never clobbers. The +# agent picks it up when its t3-serve@ instance (re)starts. +install_user_claude_token() { + local user="$1" home envf tok + local token_file="${CLAUDE_TOKEN_FILE:-/etc/t3-serve/claude-oauth-token}" + home="$(getent passwd "$user" | cut -d: -f6)" + [[ -z "$home" ]] && return 0 + [[ -f "$home/.claude/.credentials.json" ]] && return 0 # has own login -> leave it + [[ -r "$token_file" ]] || return 0 + envf="${ENVDIR:-/etc/t3-serve}/$user.env" + grep -q '^CLAUDE_CODE_OAUTH_TOKEN=' "$envf" 2>/dev/null && return 0 # already shared + if [[ "$DRY_RUN" == 1 ]]; then echo "[dry-run] share Claude token -> $envf"; return 0; fi + tok="$(cat "$token_file")" + printf 'CLAUDE_CODE_OAUTH_TOKEN=%s\n' "$tok" >> "$envf" + chmod 600 "$envf" + log "shared Claude token -> $user (t3-serve env; restart needed to take effect)" +} + [[ $EUID -eq 0 ]] || { echo "t3-provision-users: must run as root" >&2; exit 1; } for bin in python3 jq; do command -v "$bin" >/dev/null || { echo "missing $bin" >&2; exit 1; }; done [[ -f "$ROSTER" && -f "$ENGINE" ]] || { echo "roster/engine not under $WORKSTATION_DIR" >&2; exit 1; } @@ -144,9 +164,10 @@ while IFS=$'\t' read -r os_user tier shell groups_csv; do log "add $os_user -> group $g"; run gpasswd -a "$os_user" "$g" >/dev/null done fi - if [[ "$tier" != admin ]]; then # non-admins: locked ~/code clone + OIDC kubeconfig + if [[ "$tier" != admin ]]; then # non-admins: locked clone + kubeconfig + shared Claude token install_locked_clone "$os_user" install_user_kubeconfig "$os_user" + install_user_claude_token "$os_user" fi done < <(jq -r '.accounts[] | [.os_user, .tier, .shell, (.groups|join(","))] | @tsv' "$desired_file") @@ -159,6 +180,10 @@ while IFS=$'\t' read -r os_user port; do id "$os_user" >/dev/null 2>&1 && run systemctl enable --now "t3-serve@$os_user.service" >/dev/null 2>&1 || true done < <(jq -r '.ports | to_entries[] | [.key, .value] | @tsv' "$desired_file") +# 5b) machine-wide (once, not per-user): keep the t3 nightly auto-updater enabled so it +# self-heals hourly — a `disabled` timer silently freezes every instance on an old build. +run systemctl enable --now t3-autoupdate.timer >/dev/null 2>&1 || true + # 6) regenerate /etc/ttyd-user-map + dispatch.json from the desired state (SSoT: # a roster entry removed here DISAPPEARS, which is what the offboarding cut relies on) if [[ "$DRY_RUN" == 1 ]]; then diff --git a/scripts/workstation/setup-devvm.sh b/scripts/workstation/setup-devvm.sh index 9e0284b6..f929b30a 100755 --- a/scripts/workstation/setup-devvm.sh +++ b/scripts/workstation/setup-devvm.sh @@ -77,4 +77,21 @@ if [[ -d "$ADMIN_CODE" ]]; then log "hardened $ADMIN_CODE (o-rx — not world-readable)" fi +# 8) stage the shared Claude subscription OAuth token (long-lived sk-ant-oat01) to a +# root-readable file the provisioner injects into non-admins' t3-serve env, so they +# share the admin's Claude subscription (only those without their own ~/.claude login). +if command -v vault >/dev/null; then + export VAULT_ADDR="${VAULT_ADDR:-https://vault.viktorbarzin.me}" + # setup-devvm runs as root (no ~/.vault-token); borrow the admin's token to read Vault. + if [[ -z "${VAULT_TOKEN:-}" && -r /home/wizard/.vault-token ]]; then + VAULT_TOKEN="$(cat /home/wizard/.vault-token)"; export VAULT_TOKEN + fi + if claude_tok="$(vault kv get -field=claude_oauth_token secret/workstation 2>/dev/null)"; then + install -m 0600 /dev/stdin /etc/t3-serve/claude-oauth-token <<<"$claude_tok" + log "staged /etc/t3-serve/claude-oauth-token (shared Claude subscription)" + else + log "WARN: secret/workstation claude_oauth_token absent -> non-admins won't share Claude auth" + fi +fi + log "OK (idempotent)"