@agentshouse/kit 0.1.0-alpha.37 → 0.1.0-alpha.38

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
@@ -13,13 +13,13 @@ The Skill set Kit installs is Matt Pocock's
13
13
  Linux (`amd64`, `arm64`) or macOS (Apple silicon, Intel):
14
14
 
15
15
  ```sh
16
- bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.37/connect-linux.sh)
16
+ bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-linux.sh)
17
17
  ```
18
18
 
19
19
  Windows 11 (`amd64`), in PowerShell:
20
20
 
21
21
  ```powershell
22
- & ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.37/connect-windows.ps1)))
22
+ & ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-windows.ps1)))
23
23
  ```
24
24
 
25
25
  The command sets up Docker if it is missing, creates `~/.house-kit` (Kit home)
@@ -21,6 +21,15 @@ RUNTIME='Docker Engine'
21
21
  OPENER=xdg-open
22
22
  NETWORK=(--network host)
23
23
  PUBLISHED=()
24
+ AVAILABILITY=intermittent
25
+ NATIVE=0
26
+ NATIVE_RELEASE=26.04
27
+ NATIVE_ARCHITECTURE=amd64
28
+ NATIVE_PACKAGES=(git tmux)
29
+ NATIVE_PREFIX=/opt/house-kit
30
+ NATIVE_SERVICE=house-kit.service
31
+ NATIVE_UNIT=/etc/systemd/system/house-kit.service
32
+ NATIVE_WORKSPACE=/agents/house
24
33
 
25
34
  refuse() {
26
35
  printf 'kit_bootstrap_refused: %s\n' "$1" >&2
@@ -32,13 +41,25 @@ bind_mount() {
32
41
  printf 'type=bind,"src=%s",dst=%s' "$source" "$2"
33
42
  }
34
43
 
44
+ native_kit() {
45
+ PATH="$NATIVE_PREFIX/bin:$PATH" HOUSE_KIT_HOME="$KIT_HOME" "$NATIVE_PREFIX/bin/node" "$NATIVE_PREFIX/bin/$1" "${@:2}"
46
+ }
47
+
35
48
  login_kit() {
36
49
  if [[ " $* " == *' --manual '* ]]; then
37
- "${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login "$@"
50
+ if ((NATIVE)); then
51
+ native_kit kit login "$@"
52
+ else
53
+ "${DOCKER_RUN[@]:0:2}" -i "${DOCKER_RUN[@]:2}" login "$@"
54
+ fi
38
55
  return
39
56
  fi
40
57
  command -v "$OPENER" >/dev/null || refuse 'no host browser opener; rerun with --manual'
41
- "${DOCKER_RUN[@]:0:2}" ${PUBLISHED[@]+"${PUBLISHED[@]}"} "${DOCKER_RUN[@]:2}" login "$@" --host-browser | while IFS= read -r line; do
58
+ if ((NATIVE)); then
59
+ native_kit kit login "$@" --host-browser
60
+ else
61
+ "${DOCKER_RUN[@]:0:2}" ${PUBLISHED[@]+"${PUBLISHED[@]}"} "${DOCKER_RUN[@]:2}" login "$@" --host-browser
62
+ fi | while IFS= read -r line; do
42
63
  if [[ "$line" == 'Open '* ]]; then
43
64
  printf 'Opening House Login in the host browser.\n'
44
65
  "$OPENER" "${line#Open }" >/dev/null 2>&1 || refuse 'host browser did not open; rerun with --manual'
@@ -169,6 +190,204 @@ establish_docker_engine() {
169
190
  docker_answers || refuse "Docker Engine does not answer: $(docker_cause)"
170
191
  }
171
192
 
193
+ check_native_host() {
194
+ local distribution='' release='' described=macOS
195
+ if [[ "$RUNTIME" == 'Docker Engine' ]]; then
196
+ described=Linux
197
+ if [[ -r /etc/os-release ]]; then
198
+ distribution=$(. /etc/os-release && printf '%s' "${ID:-}")
199
+ release=$(. /etc/os-release && printf '%s' "${VERSION_ID:-}")
200
+ described=$(. /etc/os-release && printf '%s' "${PRETTY_NAME:-$described}")
201
+ fi
202
+ fi
203
+ [[ "$distribution" == ubuntu && "$release" == "$NATIVE_RELEASE" && "$ARCH" == "$NATIVE_ARCHITECTURE" ]] ||
204
+ refuse "--linux runs only on Ubuntu $NATIVE_RELEASE LTS on $NATIVE_ARCHITECTURE, not $described on $ARCH"
205
+ }
206
+
207
+
208
+ establish_native_packages() {
209
+ local present
210
+ present=$(installed_packages "${NATIVE_PACKAGES[@]}" | wc -l)
211
+ ((present == ${#NATIVE_PACKAGES[@]})) && return
212
+ printf 'Installing git and tmux from Ubuntu'\''s apt repository; sudo may ask for your password.\n'
213
+ elevated apt-get update -qq >/dev/null || refuse 'apt could not refresh its package lists'
214
+ elevated apt-get install -y -qq "${NATIVE_PACKAGES[@]}" >/dev/null || refuse 'apt could not install git and tmux'
215
+ }
216
+
217
+ stage_native_kit() {
218
+ local copy
219
+ NATIVE_STAGE=$(mktemp -d) || refuse 'no temporary directory for the Kit package'
220
+ trap 'rm -rf "$NATIVE_STAGE"' EXIT
221
+ docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
222
+ copy=$(docker create --platform "linux/$ARCH" "$IMAGE") || refuse 'the Kit image could not be opened'
223
+ mkdir "$NATIVE_STAGE/bin" "$NATIVE_STAGE/lib"
224
+ if ! docker cp --quiet "$copy:/usr/local/bin/node" "$NATIVE_STAGE/bin/node" ||
225
+ ! docker cp --quiet "$copy:/usr/local/lib/node_modules" "$NATIVE_STAGE/lib/node_modules"; then
226
+ docker rm "$copy" >/dev/null 2>&1 || true
227
+ refuse 'the Kit package could not be copied from its image'
228
+ fi
229
+ docker rm "$copy" >/dev/null
230
+ ln -s ../lib/node_modules/@agentshouse/kit/bin/kit.js "$NATIVE_STAGE/bin/kit"
231
+ ln -s ../lib/node_modules/@agentshouse/kit/src/session-bin/house "$NATIVE_STAGE/bin/house"
232
+ ln -s ../lib/node_modules/npm/bin/npm-cli.js "$NATIVE_STAGE/bin/npm"
233
+ ln -s ../lib/node_modules/npm/bin/npx-cli.js "$NATIVE_STAGE/bin/npx"
234
+ printf '%s\n' "$IMAGE" > "$NATIVE_STAGE/image"
235
+ chmod 0755 "$NATIVE_STAGE" "$NATIVE_STAGE/bin" "$NATIVE_STAGE/lib"
236
+ chmod 0644 "$NATIVE_STAGE/image"
237
+ }
238
+
239
+ place_native_kit() {
240
+ printf 'Installing House Kit in %s; sudo may ask for your password.\n' "$NATIVE_PREFIX"
241
+ elevated rm -rf "$NATIVE_PREFIX" && elevated mv "$NATIVE_STAGE" "$NATIVE_PREFIX" && elevated chown -R root:root "$NATIVE_PREFIX" ||
242
+ refuse "House Kit could not be installed in $NATIVE_PREFIX"
243
+ }
244
+
245
+ start_native_service() {
246
+ printf '[Unit]\nDescription=House Kit\nWants=network-online.target\nAfter=network-online.target docker.service\n\n[Service]\nUser=%s\nWorkingDirectory=%s\nEnvironment=PATH=%s/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nExecStart=%s/bin/node %s/bin/kit resident\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=multi-user.target\n' \
247
+ "$(id -un)" "$NATIVE_WORKSPACE" "$NATIVE_PREFIX" "$NATIVE_PREFIX" "$NATIVE_PREFIX" | elevated tee "$NATIVE_UNIT" >/dev/null &&
248
+ elevated chmod 0644 "$NATIVE_UNIT" || refuse "$NATIVE_UNIT could not be written"
249
+ elevated systemctl daemon-reload || refuse 'systemd could not read the House Kit service'
250
+ elevated systemctl --quiet enable --now "$NATIVE_SERVICE" || refuse 'the House Kit service could not be started'
251
+ }
252
+
253
+ run_kit() {
254
+ if ((NATIVE)); then
255
+ native_kit kit "$@"
256
+ else
257
+ "${DOCKER_RUN[@]}" "$@"
258
+ fi
259
+ }
260
+
261
+ kit_running() {
262
+ if ((NATIVE)); then
263
+ systemctl is-active --quiet "$NATIVE_SERVICE"
264
+ else
265
+ [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]
266
+ fi
267
+ }
268
+
269
+ kit_installed() {
270
+ if ((NATIVE)); then
271
+ [[ -f "$NATIVE_UNIT" ]]
272
+ else
273
+ docker container inspect "$NAME" >/dev/null 2>&1
274
+ fi
275
+ }
276
+
277
+ installed_image() {
278
+ if ((NATIVE == 0)); then
279
+ docker inspect --format '{{.Config.Image}}' "$NAME"
280
+ elif [[ -f "$NATIVE_PREFIX/image" ]]; then
281
+ cat "$NATIVE_PREFIX/image"
282
+ fi
283
+ }
284
+
285
+ check_installation() {
286
+ if ((NATIVE)); then
287
+ grep -qx "User=$(id -un)" "$NATIVE_UNIT" || refuse 'House Kit runs natively on this host for another account'
288
+ return
289
+ fi
290
+ [[ "$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$NAME")" == unless-stopped ]] || refuse 'the installed Kit has a different lifecycle'
291
+ [[ "$(docker inspect --format '{{len .Mounts}}' "$NAME")" == 2 ]] || refuse 'the installed Kit has unexpected mounts'
292
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$KIT_HOME" ]] || refuse 'the installed Kit uses another Kit home'
293
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed Kit home is not a writable bind mount'
294
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$WORKSPACE" ]] || refuse 'the installed Kit uses another workspace root'
295
+ [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed workspace is not a writable bind mount'
296
+ [[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
297
+ }
298
+
299
+ forward_kit() {
300
+ if ((NATIVE)); then
301
+ native_kit "$FORWARDED" ${FORWARD[@]+"${FORWARD[@]}"}
302
+ return
303
+ fi
304
+ local placed=() entry=() terminal=() here
305
+ if [[ "$FORWARDED" == house ]]; then
306
+ here=$(pwd -P)
307
+ [[ "$here" == "$WORKSPACE" || "$here" == "$WORKSPACE"/* ]] || refuse "the current directory is outside the workspace $WORKSPACE"
308
+ placed=(--workdir "/agents/house${here#"$WORKSPACE"}")
309
+ entry=(--entrypoint house)
310
+ fi
311
+ if [[ -t 0 && -t 1 ]]; then terminal=(-t); fi
312
+ if [[ -t 0 && ! -t 1 ]]; then
313
+ ECHOED=$(stty -g)
314
+ trap 'stty "$ECHOED"' EXIT
315
+ stty -echo
316
+ fi
317
+ if kit_running; then
318
+ docker exec -i ${terminal[@]+"${terminal[@]}"} ${placed[@]+"${placed[@]}"} "$NAME" "$FORWARDED" ${FORWARD[@]+"${FORWARD[@]}"}
319
+ else
320
+ "${DOCKER_RUN[@]:0:2}" -i ${terminal[@]+"${terminal[@]}"} ${placed[@]+"${placed[@]}"} ${entry[@]+"${entry[@]}"} "${DOCKER_RUN[@]:2}" ${FORWARD[@]+"${FORWARD[@]}"}
321
+ fi
322
+ }
323
+
324
+ start_kit() {
325
+ if ((NATIVE)); then
326
+ start_native_service
327
+ else
328
+ start_resident
329
+ fi
330
+ }
331
+
332
+ update_kit() {
333
+ local stopped=0
334
+ if ((NATIVE)); then
335
+ stage_native_kit
336
+ else
337
+ docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
338
+ fi
339
+ if ! kit_running; then
340
+ stopped=1
341
+ elif ((NATIVE)); then
342
+ native_kit kit resident drain
343
+ else
344
+ docker exec "$NAME" kit resident drain
345
+ fi
346
+ if ((NATIVE)); then
347
+ elevated systemctl stop "$NATIVE_SERVICE" || refuse 'the House Kit service could not be stopped'
348
+ place_native_kit
349
+ else
350
+ docker rm -f "$NAME" >/dev/null
351
+ fi
352
+ rm -f "$KIT_HOME/state/update-drain"
353
+ ((stopped == 0)) || run_kit credential check
354
+ start_kit
355
+ printf 'House Kit updated for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
356
+ }
357
+
358
+ resume_kit() {
359
+ run_kit credential check
360
+ if kit_running; then
361
+ printf 'House Kit is already running for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
362
+ return
363
+ fi
364
+ if ((NATIVE)); then
365
+ elevated systemctl start "$NATIVE_SERVICE" || refuse 'the House Kit service could not be started'
366
+ else
367
+ docker start "$NAME" >/dev/null
368
+ fi
369
+ printf 'House Kit restarted for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
370
+ }
371
+
372
+ connect_kit() {
373
+ if [[ -f "$KIT_HOME/credential" ]]; then
374
+ run_kit credential check
375
+ else
376
+ local login=()
377
+ if ((HOUSE_SELECTED)); then login+=(--house "$HOUSE"); fi
378
+ if ((MANUAL)); then login+=(--manual); fi
379
+ login_kit ${login[@]+"${login[@]}"}
380
+ fi
381
+ run_kit apply
382
+ start_kit
383
+ printf 'House Kit connected for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
384
+ }
385
+
386
+ if [[ "${1:-}" == --linux && ( "${2:-}" == kit || "${2:-}" == house ) ]]; then
387
+ NATIVE=1
388
+ shift
389
+ fi
390
+
172
391
  if [[ "${1:-}" == kit || "${1:-}" == house ]]; then
173
392
  FORWARDING=1
174
393
  FORWARDED="$1"
@@ -199,9 +418,20 @@ while (($#)); do
199
418
  SKILLS=false
200
419
  shift
201
420
  ;;
421
+ --linux)
422
+ NATIVE=1
423
+ shift
424
+ ;;
202
425
  *) refuse "unknown argument $1" ;;
203
426
  esac
204
427
  done
428
+ if ((NATIVE)); then
429
+ ((WORKSPACE_SELECTED == 0)) || refuse "--linux keeps its workspace at $NATIVE_WORKSPACE; --workspace is for a container"
430
+ WORKSPACE=$NATIVE_WORKSPACE
431
+ AVAILABILITY=continuous
432
+ elif [[ -e "$NATIVE_UNIT" ]]; then
433
+ refuse 'House Kit runs natively on this host; rerun this bootstrap with --linux'
434
+ fi
205
435
 
206
436
  [[ "$IMAGE" =~ ^ghcr\.io/agentshouse/kit@sha256:[0-9a-f]{64}$ ]] || refuse 'this bootstrap has not been published with an immutable image digest'
207
437
  case "$(uname -s)" in
@@ -225,21 +455,36 @@ case "$(uname -s)" in
225
455
  ;;
226
456
  *) refuse "unsupported host $(uname -s); this bootstrap is for Linux and macOS" ;;
227
457
  esac
458
+ ((NATIVE == 0)) || check_native_host
228
459
  [[ "$WORKSPACE" == /* ]] || refuse 'workspace root must be an absolute path'
229
- if [[ "$RUNTIME" == 'Docker Desktop' ]]; then
230
- establish_docker_desktop "$CHIP"
231
- DOCKER_SYSTEM=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null) || refuse "Docker Desktop does not answer: $(docker_cause)"
232
- [[ "$DOCKER_SYSTEM" == 'Docker Desktop' ]] || refuse "Docker answers from $DOCKER_SYSTEM, not Docker Desktop; stop that engine or switch Docker to Docker Desktop, then rerun this bootstrap"
460
+ if ((NATIVE && FORWARDING)); then
461
+ [[ -f "$NATIVE_UNIT" ]] || refuse 'House Kit is not installed; run this bootstrap with --linux first'
233
462
  else
234
- establish_docker_engine
463
+ if [[ "$RUNTIME" == 'Docker Desktop' ]]; then
464
+ establish_docker_desktop "$CHIP"
465
+ DOCKER_SYSTEM=$(docker info --format '{{.OperatingSystem}}' 2>/dev/null) || refuse "Docker Desktop does not answer: $(docker_cause)"
466
+ [[ "$DOCKER_SYSTEM" == 'Docker Desktop' ]] || refuse "Docker answers from $DOCKER_SYSTEM, not Docker Desktop; stop that engine or switch Docker to Docker Desktop, then rerun this bootstrap"
467
+ else
468
+ establish_docker_engine
469
+ fi
470
+ case "$DOCKER_PLATFORM" in
471
+ linux/x86_64|linux/amd64) DOCKER_ARCH=amd64 ;;
472
+ linux/aarch64|linux/arm64) DOCKER_ARCH=arm64 ;;
473
+ *) refuse "unsupported $RUNTIME platform $DOCKER_PLATFORM" ;;
474
+ esac
475
+ [[ "$DOCKER_ARCH" == "$ARCH" ]] || refuse "host linux/$ARCH does not match $RUNTIME linux/$DOCKER_ARCH"
235
476
  fi
236
- case "$DOCKER_PLATFORM" in
237
- linux/x86_64|linux/amd64) DOCKER_ARCH=amd64 ;;
238
- linux/aarch64|linux/arm64) DOCKER_ARCH=arm64 ;;
239
- *) refuse "unsupported $RUNTIME platform $DOCKER_PLATFORM" ;;
240
- esac
241
- [[ "$DOCKER_ARCH" == "$ARCH" ]] || refuse "host linux/$ARCH does not match $RUNTIME linux/$DOCKER_ARCH"
242
- if ((FORWARDING)); then
477
+ if ((NATIVE)); then
478
+ if ((FORWARDING == 0)) && docker container inspect "$NAME" >/dev/null 2>&1; then
479
+ refuse "House Kit runs in the container $NAME on this computer; remove that container, then rerun this bootstrap with --linux"
480
+ fi
481
+ [[ ! -L "${NATIVE_WORKSPACE%/*}" ]] || refuse "${NATIVE_WORKSPACE%/*} is a symbolic link"
482
+ if [[ ! -e "$NATIVE_WORKSPACE" && ! -L "$NATIVE_WORKSPACE" ]]; then
483
+ printf 'Creating %s for the Agents on this host; sudo may ask for your password.\n' "$NATIVE_WORKSPACE"
484
+ { [[ -d "${NATIVE_WORKSPACE%/*}" ]] || elevated install -d -m 0755 "${NATIVE_WORKSPACE%/*}"; } &&
485
+ elevated install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$NATIVE_WORKSPACE" || refuse "$NATIVE_WORKSPACE could not be created"
486
+ fi
487
+ elif ((FORWARDING)); then
243
488
  docker container inspect "$NAME" >/dev/null 2>&1 || refuse 'House Kit is not installed; run this bootstrap without arguments first'
244
489
  WORKSPACE=$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")
245
490
  fi
@@ -270,92 +515,60 @@ fi
270
515
  [[ "$HOUSE" == https://* || "$HOUSE" == http://127.0.0.1:* || "$HOUSE" == http://localhost:* ]] || refuse 'House origin must use HTTPS or local loopback HTTP'
271
516
  if ((FORWARDING == 0)); then
272
517
  if [[ ! -f "$KIT_HOME/kit.json" ]]; then
273
- printf '{"availability":"intermittent","execution_authority":"allow","migrators":[],"instances":[],"clients":[],"host":null,"skills":%s}\n' "$SKILLS" > "$KIT_HOME/kit.json"
518
+ printf '{"availability":"%s","execution_authority":"allow","migrators":[],"instances":[],"clients":[],"host":null,"skills":%s}\n' "$AVAILABILITY" "$SKILLS" > "$KIT_HOME/kit.json"
274
519
  elif grep -Eq '"skills"[[:space:]]*:' "$KIT_HOME/kit.json"; then
275
520
  sed -E -i.old "s/(\"skills\"[[:space:]]*:[[:space:]]*)(true|false)/\\1$SKILLS/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
276
521
  else
277
522
  sed -E -i.old "1s/^([[:space:]]*)\\{/\\1{\"skills\":$SKILLS,/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
278
523
  fi
524
+ if ((NATIVE)); then
525
+ sed -E -i.old "s/(\"availability\"[[:space:]]*:[[:space:]]*)\"[^\"]*\"/\\1\"$AVAILABILITY\"/" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
526
+ fi
279
527
  rm -f "$KIT_HOME/kit.json.old"
280
528
  grep -Eq "\"skills\"[[:space:]]*:[[:space:]]*$SKILLS" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
529
+ ((NATIVE == 0)) || grep -Eq "\"availability\"[[:space:]]*:[[:space:]]*\"$AVAILABILITY\"" "$KIT_HOME/kit.json" || refuse 'kit.json could not be updated'
281
530
  fi
282
- if [[ "$RUNTIME" == 'Docker Desktop' && "$HOUSE" == https://* ]]; then
283
- NETWORK=()
284
- LOGIN_PORT=$((49152 + RANDOM % 16384))
285
- PUBLISHED=(--publish "127.0.0.1:$LOGIN_PORT:$LOGIN_PORT" --env "HOUSE_KIT_LOGIN_PORT=$LOGIN_PORT")
531
+
532
+ if ((NATIVE)); then
533
+ RERUN='with --linux'
534
+ else
535
+ RERUN='without arguments'
536
+ if [[ "$RUNTIME" == 'Docker Desktop' && "$HOUSE" == https://* ]]; then
537
+ NETWORK=()
538
+ LOGIN_PORT=$((49152 + RANDOM % 16384))
539
+ PUBLISHED=(--publish "127.0.0.1:$LOGIN_PORT:$LOGIN_PORT" --env "HOUSE_KIT_LOGIN_PORT=$LOGIN_PORT")
540
+ fi
541
+ DOCKER_RUN=(docker run --rm ${NETWORK[@]+"${NETWORK[@]}"} --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 --env "HOUSE_KIT_HOSTNAME=$(hostname)" "$IMAGE")
286
542
  fi
287
- DOCKER_RUN=(docker run --rm ${NETWORK[@]+"${NETWORK[@]}"} --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 --env "HOUSE_KIT_HOSTNAME=$(hostname)" "$IMAGE")
288
543
 
289
- if docker container inspect "$NAME" >/dev/null 2>&1; then
290
- INSTALLED_IMAGE=$(docker inspect --format '{{.Config.Image}}' "$NAME")
291
- ((FORWARDING == 0)) || [[ "$INSTALLED_IMAGE" == "$IMAGE" ]] || refuse 'an installed Kit uses a different image; rerun this bootstrap without arguments to update it'
292
- [[ "$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$NAME")" == unless-stopped ]] || refuse 'the installed Kit has a different lifecycle'
293
- [[ "$(docker inspect --format '{{len .Mounts}}' "$NAME")" == 2 ]] || refuse 'the installed Kit has unexpected mounts'
294
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$KIT_HOME" ]] || refuse 'the installed Kit uses another Kit home'
295
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/kit-home"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed Kit home is not a writable bind mount'
296
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Source}}{{end}}{{end}}' "$NAME")" == "$WORKSPACE" ]] || refuse 'the installed Kit uses another workspace root'
297
- [[ "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/agents/house"}}{{.Type}}/{{.RW}}{{end}}{{end}}' "$NAME")" == bind/true ]] || refuse 'the installed workspace is not a writable bind mount'
298
- [[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
544
+ if kit_installed; then
545
+ check_installation
299
546
  [[ -f "$KIT_HOME/credential" && -f "$KIT_HOME/environment" && -f "$KIT_HOME/house" ]] || refuse 'the installed Kit has no enrolled authority'
300
547
  if ((FORWARDING)); then
548
+ [[ "$(installed_image)" == "$IMAGE" ]] || refuse "an installed Kit uses a different image; rerun this bootstrap $RERUN to update it"
301
549
  if [[ "$FORWARDED" == kit && "${FORWARD[0]:-}" == login ]]; then
302
550
  login_kit ${FORWARD[@]+"${FORWARD[@]:1}"}
303
551
  exit 0
304
552
  fi
305
- PLACED=()
306
- ENTRY=()
307
- if [[ "$FORWARDED" == house ]]; then
308
- HERE=$(pwd -P)
309
- [[ "$HERE" == "$WORKSPACE" || "$HERE" == "$WORKSPACE"/* ]] || refuse "the current directory is outside the workspace $WORKSPACE"
310
- PLACED=(--workdir "/agents/house${HERE#"$WORKSPACE"}")
311
- ENTRY=(--entrypoint house)
312
- fi
313
- TERMINAL=()
314
- if [[ -t 0 && -t 1 ]]; then TERMINAL=(-t); fi
315
- if [[ -t 0 && ! -t 1 ]]; then
316
- ECHOED=$(stty -g)
317
- trap 'stty "$ECHOED"' EXIT
318
- stty -echo
319
- fi
320
- if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
321
- docker exec -i ${TERMINAL[@]+"${TERMINAL[@]}"} ${PLACED[@]+"${PLACED[@]}"} "$NAME" "$FORWARDED" ${FORWARD[@]+"${FORWARD[@]}"}
322
- else
323
- "${DOCKER_RUN[@]:0:2}" -i ${TERMINAL[@]+"${TERMINAL[@]}"} ${PLACED[@]+"${PLACED[@]}"} ${ENTRY[@]+"${ENTRY[@]}"} "${DOCKER_RUN[@]:2}" ${FORWARD[@]+"${FORWARD[@]}"}
324
- fi
553
+ forward_kit
325
554
  exit
326
555
  fi
327
- if [[ "$INSTALLED_IMAGE" != "$IMAGE" ]]; then
328
- docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
329
- if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
330
- docker exec "$NAME" kit resident drain
331
- fi
332
- docker rm -f "$NAME" >/dev/null
333
- rm -f "$KIT_HOME/state/update-drain"
334
- start_resident
335
- printf 'House Kit updated for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
336
- exit 0
337
- fi
338
- "${DOCKER_RUN[@]}" credential check
339
- if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
340
- printf 'House Kit is already running for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
556
+ ((NATIVE == 0)) || establish_native_packages
557
+ if [[ "$(installed_image)" != "$IMAGE" ]]; then
558
+ update_kit
341
559
  else
342
- docker start "$NAME" >/dev/null
343
- printf 'House Kit restarted for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
560
+ resume_kit
344
561
  fi
345
562
  exit 0
346
563
  fi
347
564
 
348
- docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
349
-
350
- if [[ -f "$KIT_HOME/credential" ]]; then
351
- "${DOCKER_RUN[@]}" credential check
565
+ if ((NATIVE)); then
566
+ establish_native_packages
567
+ if [[ "$(installed_image)" != "$IMAGE" ]]; then
568
+ stage_native_kit
569
+ place_native_kit
570
+ fi
352
571
  else
353
- LOGIN=()
354
- if ((HOUSE_SELECTED)); then LOGIN+=(--house "$HOUSE"); fi
355
- if ((MANUAL)); then LOGIN+=(--manual); fi
356
- login_kit ${LOGIN[@]+"${LOGIN[@]}"}
572
+ docker pull --quiet --platform "linux/$ARCH" "$IMAGE" >/dev/null
357
573
  fi
358
-
359
- "${DOCKER_RUN[@]}" apply
360
- start_resident
361
- printf 'House Kit connected for Environment %s.\n' "$(cat "$KIT_HOME/environment")"
574
+ connect_kit
@@ -364,7 +364,7 @@ function Connect-Kit {
364
364
  $entry = @('--entrypoint', 'house')
365
365
  }
366
366
  $terminal = @()
367
- if (-not [Console]::IsInputRedirected -and -not [Console]::IsOutputRedirected) { $terminal = @('-t') }
367
+ if (-not [Console]::IsInputRedirected) { $terminal = @('-t') }
368
368
  if ((Read-Inspect '{{.State.Running}}') -ceq 'true') {
369
369
  & docker exec -i @terminal @placed $Name $Forwarded @Forward
370
370
  } else {
package/bootstrap.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.1.0-alpha.37",
3
+ "version": "0.1.0-alpha.38",
4
4
  "hosts": {
5
5
  "linux": {
6
- "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.37/connect-linux.sh)",
6
+ "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-linux.sh)",
7
7
  "asset": {
8
8
  "name": "connect-linux.sh",
9
- "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.37/connect-linux.sh"
9
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-linux.sh"
10
10
  },
11
11
  "workspace": {
12
12
  "argument": "--workspace",
@@ -14,10 +14,10 @@
14
14
  }
15
15
  },
16
16
  "windows": {
17
- "command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.37/connect-windows.ps1)))",
17
+ "command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-windows.ps1)))",
18
18
  "asset": {
19
19
  "name": "connect-windows.ps1",
20
- "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.37/connect-windows.ps1"
20
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-windows.ps1"
21
21
  },
22
22
  "workspace": {
23
23
  "argument": "--workspace",
package/docs/linux.md CHANGED
@@ -44,3 +44,28 @@ bash <(curl -fsSL …/connect-linux.sh) house git push --owner
44
44
 
45
45
  `--house http://127.0.0.1:<port>` or `--house http://localhost:<port>` runs Kit
46
46
  on the host network so it can reach a House served on this computer.
47
+
48
+ ## An Always-on server that hosts containers
49
+
50
+ An Always-on Ubuntu server that will run persistent container Environments
51
+ connects natively instead of in a container. Run the command with `--linux`:
52
+
53
+ ```sh
54
+ bash <(curl -fsSL …/connect-linux.sh) --linux
55
+ ```
56
+
57
+ It needs Ubuntu 26.04 LTS on `amd64`; any other host is refused. Every other
58
+ computer takes the ordinary command. It sets up:
59
+
60
+ - Docker Engine, as above, and `git` and `tmux` from Ubuntu's apt repository.
61
+ - `/opt/house-kit`: House Kit and the Node.js it runs on, copied from the
62
+ release's image and owned by root.
63
+ - `~/.house-kit`: Kit home, readable only by you.
64
+ - `/agents/house`: your Agents' workspace, readable only by you.
65
+ - `house-kit.service`: the systemd service that runs Kit as you and starts it
66
+ with the server.
67
+
68
+ The server connects as a continuous Environment. Put `--linux` before `kit` or
69
+ `house` to run a Kit command, as in
70
+ `bash <(curl -fsSL …/connect-linux.sh) --linux kit apply`, and run a newer
71
+ release's command with `--linux` to update it, as above.
package/docs/windows.md CHANGED
@@ -30,8 +30,7 @@ refused.
30
30
 
31
31
  - Append `kit` and a command to run it, as on [Linux](linux.md). Each run
32
32
  leaves its exit status in `$LASTEXITCODE`.
33
- - When a command asks for a secret, such as a Telegram bot token, keep its
34
- output on the terminal: with output redirected, what you type is echoed.
33
+ - A command whose output is redirected also writes its prompts and errors there.
35
34
  - A House on this computer needs host networking enabled in Docker Desktop's
36
35
  settings.
37
36
  - This bootstrap has not yet been run on a real Windows computer.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.1.0-alpha.37",
3
+ "version": "0.1.0-alpha.38",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",