@yunuseon/kayra 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/agent/guard.sh +84 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yunuseon/kayra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "A self-hosted app platform and the CLI that runs it: Traefik, Authelia, a registry, rootless BuildKit and a small deployer per server, set up with OpenTofu and Ansible.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Yunus Öztürk",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# guard.sh — Claude Code's PreToolUse hook on Bash, in kayra and every fleet
|
|
4
|
+
# (.claude/settings.json): an AI agent may build, test and read freely, but a
|
|
5
|
+
# command that changes something real waits for the operator to approve it,
|
|
6
|
+
# in that moment.
|
|
7
|
+
#
|
|
8
|
+
# Reads the hook's JSON on stdin; prints a permissionDecision "ask" with the
|
|
9
|
+
# reason for:
|
|
10
|
+
# - kayra commands that change a server (up, provision, setup, deploy,
|
|
11
|
+
# apps, adopt, down, app purge|deploy|rollback, job) unless --dry-run or
|
|
12
|
+
# the server is a multipass one (its server.yml under the project)
|
|
13
|
+
# - ansible / ansible-playbook against an inventory, ansible-vault reading
|
|
14
|
+
# or writing a vault
|
|
15
|
+
# - merging a PR, API writes (gh api / tea api with PUT, PATCH, DELETE),
|
|
16
|
+
# deleting or editing a repository, publishing a release or a package
|
|
17
|
+
# - git push of main or a tag, a force push, deleting a remote branch
|
|
18
|
+
# - tofu / terraform apply, destroy, import, state changes
|
|
19
|
+
# - destructive commands over SSH (rm, docker rm/stop/prune/down, systemctl…)
|
|
20
|
+
# Anything else: no output, the normal permission flow.
|
|
21
|
+
#
|
|
22
|
+
# It reads the command's text, so it stops mistakes and instructions smuggled
|
|
23
|
+
# in through files or web pages, not a determined adversary: a script it does
|
|
24
|
+
# not read runs whatever it contains. Cases: ci/tests/guard/run.sh.
|
|
25
|
+
# =============================================================================
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
|
|
28
|
+
command="$(jq -r '.tool_input.command // empty' 2>/dev/null)"
|
|
29
|
+
[ -n "$command" ] || exit 0
|
|
30
|
+
project="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
31
|
+
|
|
32
|
+
ask() {
|
|
33
|
+
jq -n --arg reason "$1" \
|
|
34
|
+
'{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: ("guard: " + $reason)}}'
|
|
35
|
+
exit 0
|
|
36
|
+
}
|
|
37
|
+
has() { grep -qE -- "$1" <<< "$command"; }
|
|
38
|
+
|
|
39
|
+
# ── kayra changing a server ─────────────────────────────────────────────────
|
|
40
|
+
kayra='(^|[;&|(` ]|/)(kayra|kayra\.js|main\.ts|main\.js)'
|
|
41
|
+
verbs='(up|provision|setup|deploy|apps|adopt|down|hosts|job|app (purge|deploy|rollback))'
|
|
42
|
+
if has "${kayra} +(--[a-z-]+ +)*${verbs}( |$)" && ! has '--dry-run'; then
|
|
43
|
+
# The server: the word after the verb (after the app subcommand for app …).
|
|
44
|
+
server="$(grep -oE -- "${kayra} +(--[a-z-]+ +)*${verbs} +[a-z0-9][a-z0-9-]*" <<< "$command" | head -1 | awk '{print $NF}')"
|
|
45
|
+
target=""
|
|
46
|
+
[ -z "$server" ] || [ ! -f "${project}/servers/${server}/server.yml" ] \
|
|
47
|
+
|| target="$(sed -n 's/^target: *\([a-z]*\).*/\1/p' "${project}/servers/${server}/server.yml")"
|
|
48
|
+
[ "$target" = multipass ] || ask "kayra changes ${server:-a server}${target:+ (target ${target})}"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# ── Ansible and the vaults ──────────────────────────────────────────────────
|
|
52
|
+
has '(^|[;&|( ])ansible-vault +(decrypt|view|edit|encrypt|encrypt_string|rekey|create)( |$)' \
|
|
53
|
+
&& ask "ansible-vault opens or writes a vault"
|
|
54
|
+
has '(^|[;&|( ])ansible(-playbook)? .*(-i|--inventory)[ =]' && ask "ansible runs against an inventory"
|
|
55
|
+
|
|
56
|
+
# ── The forges ──────────────────────────────────────────────────────────────
|
|
57
|
+
has '(^|[;&|( ])(gh|tea) +(pr|pulls) +merge( |$)' && ask "a pull request is merged"
|
|
58
|
+
has '(^|[;&|( ])(gh|tea) +api .*(-X|--method)[ =]*(PUT|PATCH|DELETE)' && ask "a forge API write"
|
|
59
|
+
has '(^|[;&|( ])gh +repo +(delete|edit|rename|archive)( |$)' && ask "a repository is changed or deleted"
|
|
60
|
+
has '(^|[;&|( ])gh +release +(create|delete|edit|upload)( |$)' && ask "a release is published or changed"
|
|
61
|
+
has '(^|[;&|( ])npm +publish( |$)' && ! has 'npm +publish.*--dry-run' && ask "a package is published"
|
|
62
|
+
|
|
63
|
+
# ── git push of what others build on ────────────────────────────────────────
|
|
64
|
+
if has '(^|[;&|( ])git +push( |$)'; then
|
|
65
|
+
has 'git +push.*( |:)(main|master)( |$)' && ask "main is pushed"
|
|
66
|
+
has 'git +push.*( --tags| --follow-tags| refs/tags/| v[0-9])' && ask "a tag is pushed (a release)"
|
|
67
|
+
has 'git +push.*( -f( |$)| --force| \+)' && ask "a force push"
|
|
68
|
+
# A push that names no branch pushes the current one: main, if that is where we are.
|
|
69
|
+
case "$(git branch --show-current 2>/dev/null)" in
|
|
70
|
+
main|master) has 'git +push( +-[a-zA-Z-]+)*( +origin)?( *$|[;&|])' && ask "main is pushed (the current branch)" ;;
|
|
71
|
+
esac
|
|
72
|
+
has 'git +push.*( --delete| -d( |$)| :[^ ])' && ask "a remote branch or tag is deleted"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# ── OpenTofu / Terraform ────────────────────────────────────────────────────
|
|
76
|
+
has '(^|[;&|( ])(tofu|terraform) +(-chdir=[^ ]+ +)?(apply|destroy|import|state +(rm|mv|push|replace-provider)|taint|untaint|force-unlock)( |$)' \
|
|
77
|
+
&& ask "OpenTofu changes infrastructure or state"
|
|
78
|
+
|
|
79
|
+
# ── Destructive commands on a server ────────────────────────────────────────
|
|
80
|
+
if has '(^|[;&|( ])(kayra +ssh|ssh) '; then
|
|
81
|
+
has '(rm +-|docker +(volume|image|system|container|network) +(rm|prune)|docker +(rm|stop|kill|restart)( |$)|compose +(down|stop|rm|restart|kill)( |$)|systemctl +(stop|restart|disable|mask)|reboot|shutdown|apt(-get)? +(install|remove|purge)|ufw |iptables )' \
|
|
82
|
+
&& ask "a destructive command over SSH"
|
|
83
|
+
fi
|
|
84
|
+
exit 0
|