t3-provision-users: vendor agent skills + per-user install_skills (emo)
All checks were successful
ci/woodpecker/push/default Pipeline was successful
All checks were successful
ci/woodpecker/push/default Pipeline was successful
Make the admin's Claude Code agent skills available to the `emo` devvm user. Viktor asked to install Matt Pocock's skills for emo, starting with grill-me but covering the full set the admin already uses. The `npx skills` upstream has drifted off that set (diagnose -> diagnosing-bugs and write-a-skill -> writing-great-skills were renamed; caveman + zoom-out are no longer published), so reproducing it via npx is impossible and would also spray ~70 agent dirs into the user's home + add a GitHub-clone + unpinned-CLI dependency to the hourly root reconcile. Instead vendor a point-in-time snapshot of the 16 skills (scripts/workstation/claude-skills/) and copy them per-user, mirroring install_memory: install_skills() copies each skill into ~/.agents/skills/<name> (owned by the user) and symlinks ~/.claude/skills/<name> -> ../../.agents/skills/<name>. if-absent, additive, best-effort, scoped to the SKILL_USERS allowlist (emo). find-skills is from vercel-labs/skills (not Matt Pocock) but included since it is part of the admin's current set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
59f2beda21
commit
987fdd16db
43 changed files with 2692 additions and 0 deletions
|
|
@ -29,6 +29,9 @@ REPO_REMOTE_BASE="${REPO_REMOTE_BASE:-https://forgejo.viktorbarzin.me/viktor}"
|
|||
# Per-user OIDC kubeconfig (kubelogin/PKCE; cluster server+CA copied from the admin kubeconfig).
|
||||
OIDC_ISSUER="${OIDC_ISSUER:-https://authentik.viktorbarzin.me/application/o/kubernetes/}"
|
||||
ADMIN_KUBECONFIG="${ADMIN_KUBECONFIG:-/home/wizard/.kube/config}"
|
||||
# OS users (space-separated) that receive the vendored agent skills (scripts/workstation/claude-skills).
|
||||
# Allowlist: install_skills no-ops for anyone not listed. Extend here to roll out to more users.
|
||||
SKILL_USERS="${SKILL_USERS:-emo}"
|
||||
|
||||
log() { echo "[t3-provision] $*"; }
|
||||
run() { if [[ "$DRY_RUN" == 1 ]]; then echo "[dry-run] $*"; else "$@"; fi; }
|
||||
|
|
@ -431,6 +434,49 @@ install_memory() {
|
|||
return 0 # best-effort tail must never return non-zero, else set -euo pipefail aborts the whole reconcile
|
||||
}
|
||||
|
||||
# Per-user agent skills, vendored from the in-repo snapshot ($WORKSTATION_DIR/claude-skills) — the
|
||||
# `npx skills` upstream drifted off this exact set, so we reproduce it offline + deterministically.
|
||||
# if-absent + ADDITIVE: copies a skill dir into ~/.agents/skills/<name> (owned by the user) and
|
||||
# symlinks ~/.claude/skills/<name> -> ../../.agents/skills/<name> (the layout `skills add -g`
|
||||
# produces; Claude Code reads ~/.claude/skills/). Scoped to SKILL_USERS; never clobbers an existing
|
||||
# skill. Best-effort tail: must return 0 or set -euo pipefail aborts the whole reconcile.
|
||||
install_skills() {
|
||||
local user="$1" home
|
||||
home="$(getent passwd "$user" | cut -d: -f6)"
|
||||
[[ -n "$home" && -d "$home" ]] || return 0
|
||||
case " $SKILL_USERS " in *" $user "*) ;; *) return 0 ;; esac
|
||||
local src_root="$WORKSTATION_DIR/claude-skills"
|
||||
[[ -d "$src_root" ]] || { log "WARN: $src_root missing -> skip skills for $user"; return 0; }
|
||||
|
||||
if [[ "$DRY_RUN" == 1 ]]; then
|
||||
local d names=""
|
||||
for d in "$src_root"/*/; do [[ -d "$d" ]] && names+="$(basename "$d") "; done
|
||||
echo "[dry-run] vendor skills if-absent -> $user: ${names}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local agents_dir="$home/.agents/skills" claude_dir="$home/.claude/skills"
|
||||
install -d -o "$user" -g "$user" -m 0755 "$agents_dir" "$claude_dir"
|
||||
|
||||
local skill name dst n=0
|
||||
for skill in "$src_root"/*/; do
|
||||
[[ -d "$skill" ]] || continue
|
||||
name="$(basename "$skill")"
|
||||
dst="$agents_dir/$name"
|
||||
[[ -e "$dst" || -L "$claude_dir/$name" ]] && continue # if-absent: already installed
|
||||
if cp -a "$src_root/$name" "$dst"; then
|
||||
chown -R "$user:$user" "$dst"
|
||||
ln -sfn "../../.agents/skills/$name" "$claude_dir/$name"
|
||||
chown -h "$user:$user" "$claude_dir/$name"
|
||||
n=$((n+1))
|
||||
else
|
||||
log "WARN: copy skill $name -> $user failed"
|
||||
fi
|
||||
done
|
||||
if [[ "$n" -gt 0 ]]; then log "vendored $n skill(s) -> $user"; fi
|
||||
return 0 # best-effort tail must never return non-zero, else set -euo pipefail aborts the reconcile
|
||||
}
|
||||
|
||||
[[ $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; }
|
||||
|
|
@ -574,6 +620,13 @@ while IFS=$'\t' read -r os_user; do
|
|||
install_memory "$os_user"
|
||||
done < <(jq -r '.accounts[].os_user' "$desired_file")
|
||||
|
||||
# 5e) per-user agent skills (SKILL_USERS allowlist only): vendored snapshot -> ~/.agents/skills
|
||||
# + ~/.claude/skills symlinks. if-absent + additive; best-effort (never aborts the reconcile).
|
||||
while IFS=$'\t' read -r os_user; do
|
||||
id "$os_user" >/dev/null 2>&1 || continue
|
||||
install_skills "$os_user"
|
||||
done < <(jq -r '.accounts[].os_user' "$desired_file")
|
||||
|
||||
# 5b) machine-wide (once, not per-user): keep the t3 gated nightly TRACKER timer enabled (it
|
||||
# follows t3@nightly daily, gated; see t3-autoupdate.sh / docs/runbooks/t3-version-bump.md).
|
||||
# NEVER --now: the tracker installs a NEW build + migrates DBs + restarts serves, so firing
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue