@cyanautomation/kaseki-agent 1.36.10 → 1.37.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/README.md CHANGED
@@ -604,6 +604,14 @@ ssh pi@192.168.1.100 'tmp=$(mktemp) && curl -fsSL https://raw.githubusercontent.
604
604
  ssh pi@192.168.1.100 '/agents/kaseki-agent/scripts/kaseki-setup-host.sh --fix'
605
605
  ```
606
606
 
607
+ If checkout freshness fails, remediation is now specific to the failure mode:
608
+
609
+ - `.git` access failures (for example: `permission denied`, `not a git repository`,
610
+ or inaccessible `.git`) keep permission-focused remediation.
611
+ - UID/GID impersonation failures (for example: unknown user/group or `sudo`/`runuser`
612
+ invocation errors) return remediation that asks you to configure a valid way to
613
+ execute as UID:GID `10000:10000` (or provide passwd/group mappings), then rerun setup.
614
+
607
615
  #### Local Activation (No SSH)
608
616
 
609
617
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanautomation/kaseki-agent",
3
- "version": "1.36.10",
3
+ "version": "1.37.0",
4
4
  "description": "Admin/helper/doctor toolbox and local API client for Kaseki diagnostics, setup, and API-backed coding-agent task workflows",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -100,6 +100,37 @@ check_writable() {
100
100
  fi
101
101
  }
102
102
 
103
+ fix_checkout_permissions_if_exists() {
104
+ # If checkout directory exists with potentially problematic ownership,
105
+ # try to fix it to match container UID:GID (only when --fix is set)
106
+ if [ "$KASEKI_FIX" != "1" ]; then
107
+ return 0
108
+ fi
109
+ if [ ! -d "$KASEKI_CHECKOUT_DIR" ]; then
110
+ return 0
111
+ fi
112
+ # Attempt to fix ownership to container UID:GID for consistency
113
+ run_privileged chown -R "$KASEKI_CONTAINER_UID:$KASEKI_CONTAINER_GID" "$KASEKI_CHECKOUT_DIR" 2>/dev/null || true
114
+ }
115
+
116
+ resolve_uid_to_name() {
117
+ local uid="$1"
118
+ if ! command -v getent >/dev/null 2>&1; then
119
+ printf ''
120
+ return 0
121
+ fi
122
+ getent passwd "$uid" 2>/dev/null | awk -F: 'NR==1 && NF>=6 {print $1}'
123
+ }
124
+
125
+ resolve_gid_to_name() {
126
+ local gid="$1"
127
+ if ! command -v getent >/dev/null 2>&1; then
128
+ printf ''
129
+ return 0
130
+ fi
131
+ getent group "$gid" 2>/dev/null | awk -F: 'NR==1 && NF>=4 {print $1}'
132
+ }
133
+
103
134
  normalize_secrets_dir() {
104
135
  local secrets_dir="$1"
105
136
  if [ ! -d "$secrets_dir" ]; then
@@ -133,12 +164,20 @@ run_checkout_freshness_probe() {
133
164
  local probe_status="skipped"
134
165
  local probe_detail="Checkout freshness probe skipped because setup did not run bootstrap."
135
166
  local probe_remediation=""
167
+ local stderr_file=""
168
+
169
+ cleanup_probe_stderr_file() {
170
+ if [ -n "$stderr_file" ]; then
171
+ rm -f "$stderr_file"
172
+ fi
173
+ }
136
174
 
137
175
  if [ ! -d "$checkout_dir" ]; then
138
176
  probe_status="failed"
139
177
  probe_detail="Checkout freshness probe failed: expected checkout path ${checkout_dir} does not exist."
140
178
  probe_remediation="Fix ownership/permissions so ${checkout_dir} and ${checkout_dir}/.git are readable by UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID}."
141
179
  printf '%s|%s|%s\n' "$probe_status" "$probe_detail" "$probe_remediation"
180
+ cleanup_probe_stderr_file
142
181
  return 0
143
182
  fi
144
183
 
@@ -147,23 +186,33 @@ run_checkout_freshness_probe() {
147
186
  probe_detail="Checkout freshness probe failed: ${checkout_dir}/.git is missing or inaccessible."
148
187
  probe_remediation="Fix ownership/permissions so ${checkout_dir} and ${checkout_dir}/.git are readable by UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID}."
149
188
  printf '%s|%s|%s\n' "$probe_status" "$probe_detail" "$probe_remediation"
189
+ cleanup_probe_stderr_file
150
190
  return 0
151
191
  fi
152
192
 
153
- local stderr_file
154
193
  stderr_file="$(mktemp)"
155
-
156
- # Ensure cleanup on exit
157
- trap 'rm -f "$stderr_file"' EXIT INT TERM
158
-
194
+
159
195
  local probe_command=(git -C "$checkout_dir" rev-parse HEAD)
160
196
 
197
+ local resolved_user_name=""
198
+ local resolved_group_name=""
199
+ resolved_user_name="$(resolve_uid_to_name "$KASEKI_CONTAINER_UID")"
200
+ resolved_group_name="$(resolve_gid_to_name "$KASEKI_CONTAINER_GID")"
201
+
161
202
  if [ "$(id -u)" -eq "$KASEKI_CONTAINER_UID" ] && [ "$(id -g)" -eq "$KASEKI_CONTAINER_GID" ]; then
162
203
  "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
163
- elif command -v sudo >/dev/null 2>&1; then
164
- sudo -u "#${KASEKI_CONTAINER_UID}" -g "#${KASEKI_CONTAINER_GID}" -- "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
165
- elif command -v runuser >/dev/null 2>&1 && [ "$(id -u)" -eq 0 ]; then
166
- runuser -u "#${KASEKI_CONTAINER_UID}" -- "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
204
+ elif [ "$(id -u)" -eq 0 ] && command -v setpriv >/dev/null 2>&1; then
205
+ setpriv --reuid "$KASEKI_CONTAINER_UID" --regid "$KASEKI_CONTAINER_GID" --clear-groups -- "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
206
+ elif [ "$(id -u)" -eq 0 ] && command -v runuser >/dev/null 2>&1 && [ -n "$resolved_user_name" ] && [ -n "$resolved_group_name" ]; then
207
+ runuser -u "$resolved_user_name" -g "$resolved_group_name" -- "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
208
+ elif [ "$(id -u)" -eq 0 ] && command -v sudo >/dev/null 2>&1; then
209
+ if [ -n "$resolved_user_name" ] && [ -n "$resolved_group_name" ]; then
210
+ sudo -u "$resolved_user_name" -g "$resolved_group_name" -- "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
211
+ elif [ -n "$resolved_user_name" ]; then
212
+ sudo -u "$resolved_user_name" -- "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
213
+ else
214
+ sudo -u "#${KASEKI_CONTAINER_UID}" -g "#${KASEKI_CONTAINER_GID}" -- "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
215
+ fi
167
216
  else
168
217
  "${probe_command[@]}" >/dev/null 2>"$stderr_file" || true
169
218
  fi
@@ -172,13 +221,18 @@ run_checkout_freshness_probe() {
172
221
  probe_status="failed"
173
222
  local stderr_tail
174
223
  stderr_tail="$(tail -n 1 "$stderr_file" | tr -d '\r')"
175
- probe_detail="Checkout freshness probe failed when running git metadata access as UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID}: ${stderr_tail}"
176
- probe_remediation="Fix ownership/permissions so ${checkout_dir} and ${checkout_dir}/.git are readable by UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID}."
224
+ if printf '%s' "$stderr_tail" | grep -Eiq 'unknown user|unknown group|no passwd entry|user .* does not exist|group .* does not exist|sudo: .*unknown|sudo: .*invalid|runuser: .*does not exist|runuser: user .* does not exist|runuser: group .* does not exist|unable to initialize policy plugin|unable to set user context'; then
225
+ probe_detail="Checkout freshness probe failed: probe could not impersonate UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID} due to host user/group mapping or privilege-tool configuration issue: ${stderr_tail}"
226
+ probe_remediation="Configure a valid host method to run commands as UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID} (or ensure passwd/group mappings exist for that UID/GID), then rerun ./scripts/kaseki-setup-host.sh --fix."
227
+ else
228
+ probe_detail="Checkout freshness probe failed when running git metadata access as UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID}: ${stderr_tail}"
229
+ probe_remediation="Fix ownership/permissions so ${checkout_dir} and ${checkout_dir}/.git are readable by UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID}."
230
+ fi
177
231
  else
178
232
  probe_status="ok"
179
233
  probe_detail="Checkout freshness probe passed for ${checkout_dir} as UID:GID ${KASEKI_CONTAINER_UID}:${KASEKI_CONTAINER_GID}."
180
234
  fi
181
- rm -f "$stderr_file"
235
+ cleanup_probe_stderr_file
182
236
 
183
237
  printf '%s|%s|%s\n' "$probe_status" "$probe_detail" "$probe_remediation"
184
238
  }
@@ -288,6 +342,94 @@ bootstrap_checkout_if_possible() {
288
342
  return 1
289
343
  }
290
344
 
345
+ ensure_git_safe_directory() {
346
+ if ! command -v git >/dev/null 2>&1; then
347
+ printf 'warning: git is unavailable; skipping safe.directory preflight for %s\n' "$KASEKI_CHECKOUT_DIR"
348
+ return 0
349
+ fi
350
+
351
+ if [ ! -d "$KASEKI_CHECKOUT_DIR/.git" ]; then
352
+ printf 'warning: %s/.git is missing; skipping safe.directory preflight\n' "$KASEKI_CHECKOUT_DIR"
353
+ return 0
354
+ fi
355
+
356
+ local status_code=0
357
+
358
+ # Configure safe.directory for current context (usually root when run via sudo)
359
+ local existing_safe_dirs
360
+ existing_safe_dirs="$(git config --global --get-all safe.directory 2>/dev/null || true)"
361
+ if printf '%s\n' "$existing_safe_dirs" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
362
+ printf 'ok: git safe.directory already present for current user context\n'
363
+ else
364
+ if git config --global --add safe.directory "$KASEKI_CHECKOUT_DIR" >/dev/null 2>&1; then
365
+ printf 'ok: configured git safe.directory for current user context\n'
366
+ else
367
+ printf 'warning: failed to configure git safe.directory for current user context\n'
368
+ status_code=1
369
+ fi
370
+ fi
371
+
372
+ # If running via sudo, also configure safe.directory for the invoking user
373
+ if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
374
+ local invoking_user_config
375
+ invoking_user_config="$(sudo -u "$SUDO_USER" git config --global --get-all safe.directory 2>/dev/null || true)"
376
+ if printf '%s\n' "$invoking_user_config" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
377
+ printf 'ok: git safe.directory already present for invoking user (%s)\n' "$SUDO_USER"
378
+ else
379
+ if sudo -u "$SUDO_USER" git config --global --add safe.directory "$KASEKI_CHECKOUT_DIR" >/dev/null 2>&1; then
380
+ printf 'ok: configured git safe.directory for invoking user (%s)\n' "$SUDO_USER"
381
+ else
382
+ printf 'warning: failed to configure git safe.directory for invoking user (%s)\n' "$SUDO_USER"
383
+ status_code=1
384
+ fi
385
+ fi
386
+ fi
387
+
388
+ if [ "$status_code" -ne 0 ]; then
389
+ printf 'remediation: if you see dubious ownership errors, try manually running as the invoking user:\n'
390
+ if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
391
+ printf ' sudo -u %s git config --global --add safe.directory "%s"\n' "$SUDO_USER" "$KASEKI_CHECKOUT_DIR"
392
+ fi
393
+ printf ' And as root: git config --global --add safe.directory "%s"\n' "$KASEKI_CHECKOUT_DIR"
394
+ fi
395
+
396
+ return 0
397
+ }
398
+
399
+ verify_git_safe_directory() {
400
+ # Verify that safe.directory is actually configured before bootstrap
401
+ if ! command -v git >/dev/null 2>&1; then
402
+ return 0
403
+ fi
404
+
405
+ if [ ! -d "$KASEKI_CHECKOUT_DIR/.git" ]; then
406
+ return 0
407
+ fi
408
+
409
+ local existing_safe_dirs
410
+ existing_safe_dirs="$(git config --global --get-all safe.directory 2>/dev/null || true)"
411
+ if printf '%s\n' "$existing_safe_dirs" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
412
+ return 0
413
+ fi
414
+
415
+ # Not configured in current context; check if sudo user context has it
416
+ if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
417
+ local invoking_user_config
418
+ invoking_user_config="$(sudo -u "$SUDO_USER" git config --global --get-all safe.directory 2>/dev/null || true)"
419
+ if printf '%s\n' "$invoking_user_config" | grep -Fxq "$KASEKI_CHECKOUT_DIR"; then
420
+ return 0
421
+ fi
422
+ fi
423
+
424
+ # Safe.directory not configured anywhere; warn but don't fail (bootstrap might still work)
425
+ printf 'warning: git safe.directory not found for checkout. If bootstrap fails with "dubious ownership", run:\n'
426
+ if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
427
+ printf ' sudo -u %s git config --global --add safe.directory "%s"\n' "$SUDO_USER" "$KASEKI_CHECKOUT_DIR"
428
+ fi
429
+ printf ' git config --global --add safe.directory "%s"\n' "$KASEKI_CHECKOUT_DIR"
430
+ return 0
431
+ }
432
+
291
433
  recreate_api_if_requested() {
292
434
  if [ "$KASEKI_RECREATE_API" != "1" ]; then
293
435
  return 0
@@ -322,6 +464,9 @@ check_writable "$KASEKI_ROOT/kaseki-results" || status=1
322
464
  printf 'using host secrets directory: %s\n' "$KASEKI_HOST_SECRETS_DIR"
323
465
  normalize_secrets_dir "$KASEKI_HOST_SECRETS_DIR"
324
466
 
467
+ fix_checkout_permissions_if_exists
468
+ ensure_git_safe_directory
469
+ verify_git_safe_directory
325
470
  bootstrap_checkout_if_possible || status=$?
326
471
 
327
472
  probe_payload="$(run_checkout_freshness_probe "$KASEKI_CHECKOUT_DIR")"
@@ -347,7 +492,19 @@ fi
347
492
  recreate_api_if_requested || status=$?
348
493
 
349
494
  if [ "$status" -ne 0 ]; then
350
- printf 'kaseki host setup incomplete. Re-run with --fix to create directories and bootstrap when possible.\n' >&2
495
+ printf 'kaseki host setup incomplete. Details above. Common remediation steps:\n' >&2
496
+ printf '\n' >&2
497
+ printf '1. Ensure git safe.directory is configured:\n' >&2
498
+ if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
499
+ printf ' sudo -u %s git config --global --add safe.directory "%s"\n' "$SUDO_USER" "$KASEKI_CHECKOUT_DIR" >&2
500
+ fi
501
+ printf ' git config --global --add safe.directory "%s"\n' "$KASEKI_CHECKOUT_DIR" >&2
502
+ printf '\n' >&2
503
+ printf '2. Fix directory permissions/ownership:\n' >&2
504
+ printf ' sudo chown -R %d:%d "%s"\n' "$KASEKI_CONTAINER_UID" "$KASEKI_CONTAINER_GID" "$KASEKI_ROOT" >&2
505
+ printf '\n' >&2
506
+ printf '3. Retry setup:\n' >&2
507
+ printf ' sudo kaseki-agent host setup --fix\n' >&2
351
508
  fi
352
509
 
353
510
  exit "$status"