jishushell 0.4.2-beta2 → 0.4.10

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.
Files changed (75) hide show
  1. package/Dockerfile.openclaw-slim +58 -0
  2. package/INSTALL-NOTICE +7 -1
  3. package/dist/auth.js +3 -3
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli.js +517 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +21 -4
  8. package/dist/config.js +88 -54
  9. package/dist/config.js.map +1 -1
  10. package/dist/control.js +5 -5
  11. package/dist/control.js.map +1 -1
  12. package/dist/doctor.js +47 -14
  13. package/dist/doctor.js.map +1 -1
  14. package/dist/install.d.ts +1 -1
  15. package/dist/install.js +15 -29
  16. package/dist/install.js.map +1 -1
  17. package/dist/routes/backup.d.ts +2 -0
  18. package/dist/routes/backup.js +370 -0
  19. package/dist/routes/backup.js.map +1 -0
  20. package/dist/routes/instances.d.ts +1 -0
  21. package/dist/routes/instances.js +51 -11
  22. package/dist/routes/instances.js.map +1 -1
  23. package/dist/routes/setup.js +3 -5
  24. package/dist/routes/setup.js.map +1 -1
  25. package/dist/server.js +29 -1
  26. package/dist/server.js.map +1 -1
  27. package/dist/services/backup-manager.d.ts +253 -0
  28. package/dist/services/backup-manager.js +2014 -0
  29. package/dist/services/backup-manager.js.map +1 -0
  30. package/dist/services/backup-verify.d.ts +26 -0
  31. package/dist/services/backup-verify.js +240 -0
  32. package/dist/services/backup-verify.js.map +1 -0
  33. package/dist/services/instance-manager.d.ts +24 -4
  34. package/dist/services/instance-manager.js +218 -49
  35. package/dist/services/instance-manager.js.map +1 -1
  36. package/dist/services/nomad-manager.js +72 -131
  37. package/dist/services/nomad-manager.js.map +1 -1
  38. package/dist/services/process-manager.js +4 -3
  39. package/dist/services/process-manager.js.map +1 -1
  40. package/dist/services/setup-manager.d.ts +4 -2
  41. package/dist/services/setup-manager.js +268 -129
  42. package/dist/services/setup-manager.js.map +1 -1
  43. package/dist/utils/fs.d.ts +85 -0
  44. package/dist/utils/fs.js +111 -0
  45. package/dist/utils/fs.js.map +1 -0
  46. package/dist/utils/safe-json.d.ts +2 -0
  47. package/dist/utils/safe-json.js +22 -16
  48. package/dist/utils/safe-json.js.map +1 -1
  49. package/install/jishu-install-china.sh +3092 -0
  50. package/install/jishu-install.sh +310 -108
  51. package/install/jishu-uninstall.sh +276 -391
  52. package/install/post-install.sh +9 -0
  53. package/openclaw-entry.sh +15 -0
  54. package/package.json +4 -1
  55. package/public/assets/Dashboard-DhsrzJ4F.js +1 -0
  56. package/public/assets/{InitPassword-CslWYy8G.js → InitPassword-BjubiVdd.js} +1 -1
  57. package/public/assets/InstanceDetail-DMcywsof.js +17 -0
  58. package/public/assets/{Login-d45wtgVA.js → Login-CUoEZOWR.js} +1 -1
  59. package/public/assets/NewInstance-Bk0G4EiJ.js +1 -0
  60. package/public/assets/Settings-D5tHL_h5.js +1 -0
  61. package/public/assets/Setup-4t6E3Rut.js +1 -0
  62. package/public/assets/index-BJ47MWpF.css +1 -0
  63. package/public/assets/index-DbX85irc.js +16 -0
  64. package/public/assets/{usePolling-CqQ8hrNc.js → usePolling-CK0DfI4h.js} +1 -1
  65. package/public/assets/{vendor-i18n-Bvxxh8Di.js → vendor-i18n-CfW0RvgE.js} +1 -1
  66. package/public/assets/vendor-react-B1-3Yrt-.js +59 -0
  67. package/public/index.html +4 -4
  68. package/public/assets/Dashboard-Dxsq690N.js +0 -1
  69. package/public/assets/InstanceDetail-DmEkMj-t.js +0 -14
  70. package/public/assets/NewInstance-Czp5-AJe.js +0 -1
  71. package/public/assets/Settings-BKMGck05.js +0 -1
  72. package/public/assets/Setup-D3rfLWjZ.js +0 -1
  73. package/public/assets/index-77Ug7feY.css +0 -1
  74. package/public/assets/index-DkDnIohs.js +0 -16
  75. package/public/assets/vendor-react-DONn7uBV.js +0 -59
@@ -224,7 +224,7 @@ SKIP_OPENCLAW="${SKIP_OPENCLAW:-0}" # default=0 (install); use --skip 4 or --sk
224
224
  SKIP_JISHUSHELL="${SKIP_JISHUSHELL:-0}" # 1=skip install_jishushell
225
225
  SKIP_JISHUSHELL_SERVICE="${SKIP_JISHUSHELL_SERVICE:-0}" # 1=skip service registration
226
226
  OPENCLAW_NPM_VERSION="${OPENCLAW_NPM_VERSION:-latest}" # openclaw npm package version
227
- OPENCLAW_DOCKER_TAG="${OPENCLAW_DOCKER_TAG:-jishushell-openclaw:local}" # locally built image with Python
227
+ OPENCLAW_DOCKER_TAG="${OPENCLAW_DOCKER_TAG:-ghcr.io/x-aijishu/openclaw-runtime:latest}" # pre-built image from registry
228
228
  OPENCLAW_IMAGE="" # set dynamically after pull/build
229
229
  AUTO_YES="${AUTO_YES:-0}"
230
230
  DOCKER_CMD_PREFIX="" # Set to "sg docker -c" when group activated via sg
@@ -405,18 +405,33 @@ detect_os() {
405
405
  ui_success "OS: ${OS_NAME} (package manager: ${PKG_MANAGER})"
406
406
  }
407
407
 
408
- # Detect CPU architecture. Sets: ARCH (amd64 | arm64)
408
+ # Detect CPU architecture. Sets: ARCH (arm64)
409
+ # Only Arm-family (aarch64, arm64, armv7l) and Apple Silicon (Darwin/arm64)
410
+ # are supported. x86_64, i686, riscv, mips, s390x, ppc, etc. are rejected.
409
411
  detect_arch() {
410
- ARCH="$(uname -m)"
411
- case "$ARCH" in
412
- x86_64|amd64) ARCH="amd64" ;;
413
- aarch64|arm64) ARCH="arm64" ;;
412
+ local raw_arch
413
+ raw_arch="$(uname -m)"
414
+ case "$raw_arch" in
415
+ aarch64|arm64)
416
+ ARCH="arm64"
417
+ ;;
418
+ armv7l|armv8l|armhf)
419
+ # 32-bit Arm — may work but not officially tested
420
+ ARCH="arm64"
421
+ ui_warn "32-bit Arm detected (${raw_arch}). 64-bit OS on a 64-bit board is strongly recommended."
422
+ ;;
414
423
  *)
415
- ui_error "Unsupported CPU architecture: $ARCH"
424
+ ui_error "Unsupported CPU architecture: ${raw_arch}"
425
+ ui_error ""
426
+ ui_error "JishuShell runs exclusively on Arm-based devices (aarch64 / arm64)."
427
+ ui_error "Supported examples: Raspberry Pi 4/5, Orange Pi 5, Jetson Orin,"
428
+ ui_error " Rockchip RK3588, Apple Silicon Mac (arm64 macOS)."
429
+ ui_error ""
430
+ ui_error "x86_64 / i686 / RISC-V / MIPS / s390x / PowerPC are not supported."
416
431
  exit 1
417
432
  ;;
418
433
  esac
419
- ui_success "Architecture: ${ARCH}"
434
+ ui_success "Architecture: ${ARCH} (${raw_arch})"
420
435
  }
421
436
 
422
437
  # Verify sudo access. Sets: SUDO ("" if root, "sudo" otherwise)
@@ -446,7 +461,7 @@ check_sudo() {
446
461
  # This ensures 'sudo docker' works even after a long Docker install step
447
462
  # without prompting for the password again.
448
463
  if [[ -z "${_SUDO_KEEPALIVE_PID:-}" ]]; then
449
- ( while true; do sudo -n true 2>/dev/null; sleep 60; done ) &
464
+ ( while true; do sudo -n true 2>/dev/null; sleep 60; done ) &>/dev/null &
450
465
  _SUDO_KEEPALIVE_PID=$!
451
466
  disown "$_SUDO_KEEPALIVE_PID" 2>/dev/null || true
452
467
  fi
@@ -925,6 +940,41 @@ _ensure_nvm_shell_config() {
925
940
  install_docker() {
926
941
  ui_stage "Docker"
927
942
 
943
+ # ── macOS: use private Colima instance ─────────────────────────────────────
944
+ if [[ "$OS" == "macos" ]]; then
945
+ local need_brew=0
946
+ local need_profile=0
947
+
948
+ if ! command -v docker &>/dev/null || ! command -v colima &>/dev/null; then
949
+ need_brew=1
950
+ need_profile=1
951
+ elif ! _colima list 2>/dev/null | grep -q "${_COLIMA_PROFILE}"; then
952
+ need_profile=1
953
+ fi
954
+
955
+ if [[ $need_brew -eq 1 ]]; then
956
+ if ! _do_install_docker; then
957
+ ui_error "Colima installation failed"
958
+ return 1
959
+ fi
960
+ elif [[ $need_profile -eq 1 ]]; then
961
+ ui_info "Starting Colima VM (profile: ${_COLIMA_PROFILE})..."
962
+ mkdir -p "${_COLIMA_HOME}"
963
+ _colima start "${_COLIMA_PROFILE}" \
964
+ --vm-type vz --mount-type virtiofs --network-address \
965
+ --activate=false --cpu 2 --memory 4 --disk 60 >/dev/null \
966
+ || { ui_warn "colima start failed — run 'COLIMA_HOME=${_COLIMA_HOME} colima start ${_COLIMA_PROFILE}' manually"; return 1; }
967
+ export DOCKER_HOST="unix://${_COLIMA_SOCKET}"
968
+ ui_success "Colima is running"
969
+ else
970
+ ui_success "Docker and Colima already configured"
971
+ fi
972
+
973
+ _ensure_docker_running
974
+ return 0
975
+ fi
976
+
977
+ # ── Linux: standard Docker Engine ──────────────────────────────────────────
928
978
  local need_install_docker=0
929
979
  local need_install_compose=0
930
980
 
@@ -948,8 +998,6 @@ install_docker() {
948
998
  :
949
999
  elif command -v docker-compose &>/dev/null; then
950
1000
  :
951
- elif [[ "$OS" == "macos" ]]; then
952
- :
953
1001
  else
954
1002
  need_install_compose=1
955
1003
  fi
@@ -962,8 +1010,6 @@ install_docker() {
962
1010
 
963
1011
  if [[ $need_install_docker -eq 1 ]]; then
964
1012
  ui_info "Docker not found — installing..."
965
- # _do_install_docker installs docker-compose-plugin in the same apt command,
966
- # so Compose V2 will be available immediately after — no separate step needed.
967
1013
  if ! _do_install_docker; then
968
1014
  ui_warn "Official Docker install script failed — trying system package manager fallback..."
969
1015
  if ! _do_install_docker_apt_fallback; then
@@ -971,7 +1017,6 @@ install_docker() {
971
1017
  return 1
972
1018
  fi
973
1019
  fi
974
- # Compose is bundled; skip the separate install step
975
1020
  need_install_compose=0
976
1021
  fi
977
1022
 
@@ -995,13 +1040,23 @@ _do_install_docker() {
995
1040
  fi
996
1041
 
997
1042
  if [[ "$OS" == "macos" ]]; then
998
- ui_warn "Automated Docker installation is not supported on macOS"
999
- ui_info "Please install Docker Desktop from https://www.docker.com/products/docker-desktop/"
1000
- if command -v brew &>/dev/null; then
1001
- ui_info "Or via Homebrew: brew install --cask docker"
1043
+ ui_info "Installing docker and colima via Homebrew..."
1044
+ if ! command -v brew &>/dev/null; then
1045
+ ui_warn "Homebrew not found. Install it from https://brew.sh then re-run this script."
1046
+ return 1
1002
1047
  fi
1003
- ui_info "After installation, open Docker Desktop and wait for the daemon to start, then re-run this script"
1004
- return 1
1048
+ brew install -q docker colima || { ui_warn "brew install failed"; return 1; }
1049
+ ui_success "docker and colima installed"
1050
+
1051
+ mkdir -p "${_COLIMA_HOME}"
1052
+ ui_info "Starting Colima VM (profile: ${_COLIMA_PROFILE})..."
1053
+ _colima start "${_COLIMA_PROFILE}" \
1054
+ --vm-type vz --mount-type virtiofs --network-address \
1055
+ --activate=false --cpu 2 --memory 4 --disk 60 >/dev/null \
1056
+ || { ui_warn "colima start failed — run 'COLIMA_HOME=${_COLIMA_HOME} colima start ${_COLIMA_PROFILE}' manually"; return 1; }
1057
+ ui_success "Colima is running"
1058
+ export DOCKER_HOST="unix://${_COLIMA_SOCKET}"
1059
+ return 0
1005
1060
  fi
1006
1061
 
1007
1062
  # Step 1: download
@@ -1096,7 +1151,7 @@ _do_install_docker_apt_fallback() {
1096
1151
  fi
1097
1152
 
1098
1153
  if [[ "$OS" == "macos" ]]; then
1099
- ui_warn "No apt/dnf fallback available on macOS — please install Docker Desktop manually"
1154
+ ui_warn "No apt/dnf fallback available on macOS — install via Homebrew: brew install docker colima"
1100
1155
  return 1
1101
1156
  fi
1102
1157
 
@@ -1157,17 +1212,30 @@ _ensure_docker_running() {
1157
1212
  fi
1158
1213
 
1159
1214
  if [[ "$OS" == "macos" ]]; then
1215
+ export DOCKER_HOST="unix://${_COLIMA_SOCKET}"
1216
+ if ! docker info &>/dev/null 2>&1; then
1217
+ ui_info "Starting Colima VM..."
1218
+ mkdir -p "${_COLIMA_HOME}"
1219
+ if ! _colima start "${_COLIMA_PROFILE}" \
1220
+ --vm-type vz --mount-type virtiofs --network-address \
1221
+ --activate=false --cpu 2 --memory 4 --disk 60 >/dev/null; then
1222
+ ui_warn "colima start failed"
1223
+ ui_info "Run manually: COLIMA_HOME=${_COLIMA_HOME} colima start ${_COLIMA_PROFILE}"
1224
+ return 1
1225
+ fi
1226
+ fi
1160
1227
  local waited=0
1228
+ local timeout=120
1161
1229
  while ! docker info &>/dev/null 2>&1; do
1162
- if [[ $waited -ge 15 ]]; then
1163
- ui_warn "Docker daemon did not become ready within 15 seconds"
1164
- ui_info "Make sure Docker Desktop is open and running"
1230
+ if [[ $waited -ge $timeout ]]; then
1231
+ ui_warn "Docker daemon did not become ready within ${timeout} seconds"
1232
+ ui_info "Run: COLIMA_HOME=${_COLIMA_HOME} colima status ${_COLIMA_PROFILE}"
1165
1233
  return 1
1166
1234
  fi
1167
- sleep 1
1168
- (( waited++ )) || true
1235
+ sleep 2
1236
+ (( waited += 2 )) || true
1169
1237
  done
1170
- [[ $waited -lt 15 ]] && ui_success "Docker daemon is ready"
1238
+ ui_success "Docker daemon is ready"
1171
1239
  return 0
1172
1240
  fi
1173
1241
 
@@ -1303,6 +1371,21 @@ docker_exec() {
1303
1371
  fi
1304
1372
  }
1305
1373
 
1374
+ # Private Colima wrapper — runs colima with COLIMA_HOME scoped to JishuShell's
1375
+ # data directory so the VM, socket, and all state are fully isolated from any
1376
+ # user-level Docker Desktop or default Colima installation.
1377
+ #
1378
+ # Usage: _colima start jishushell --vm-type vz ...
1379
+ # _colima stop jishushell
1380
+ # _colima status jishushell
1381
+ _COLIMA_HOME="${JISHUSHELL_HOME}/colima"
1382
+ _COLIMA_PROFILE="jishushell"
1383
+ _COLIMA_SOCKET="${_COLIMA_HOME}/${_COLIMA_PROFILE}/docker.sock"
1384
+
1385
+ _colima() {
1386
+ COLIMA_HOME="${_COLIMA_HOME}" command colima "$@"
1387
+ }
1388
+
1306
1389
  # ─── 3. Nomad ────────────────────────────────────────────────────────────────
1307
1390
 
1308
1391
  install_nomad() {
@@ -1531,17 +1614,29 @@ _install_nomad_binary() {
1531
1614
  ui_success "Nomad installed: v${installed_version} → ${dest}"
1532
1615
  }
1533
1616
 
1534
- # Add ~/.jishushell/bin to PATH in shell startup files and current session
1617
+ # Add ~/.jishushell/bin and npm global bin to PATH in shell startup files and current session
1535
1618
  _ensure_jishushell_bin_in_path() {
1536
1619
  local bin_dir="${JISHUSHELL_BIN_DIR}"
1537
1620
  local marker="# jishushell-bin-path"
1621
+
1622
+ # Also ensure npm global bin is in PATH (for `npm install -g` with custom prefix)
1623
+ local npm_bin=""
1624
+ if command -v npm &>/dev/null; then
1625
+ npm_bin="$(npm config get prefix 2>/dev/null)/bin"
1626
+ fi
1627
+
1628
+ # Build PATH line: include npm global bin if it differs from jishushell bin
1538
1629
  local init_line="export PATH=\"${bin_dir}:\$PATH\""
1630
+ if [[ -n "$npm_bin" && "$npm_bin" != "$bin_dir" && -d "$npm_bin" ]]; then
1631
+ init_line="export PATH=\"${bin_dir}:${npm_bin}:\$PATH\""
1632
+ export PATH="${npm_bin}:${PATH}"
1633
+ fi
1539
1634
 
1540
1635
  # Export for the current running shell immediately
1541
1636
  export PATH="${bin_dir}:${PATH}"
1542
1637
 
1543
1638
  if [[ "$DRY_RUN" == "1" ]]; then
1544
- ui_info "[dry-run] Would add ${bin_dir} to PATH in shell startup files"
1639
+ ui_info "[dry-run] Would add PATH entries in shell startup files"
1545
1640
  return 0
1546
1641
  fi
1547
1642
 
@@ -1550,7 +1645,7 @@ _ensure_jishushell_bin_in_path() {
1550
1645
  for rc in "${rc_files[@]}"; do
1551
1646
  if [[ -f "$rc" ]] && ! grep -qF "$marker" "$rc" 2>/dev/null; then
1552
1647
  printf '\n%s\n%s\n' "$marker" "$init_line" >> "$rc"
1553
- ui_info "Added ${bin_dir} to PATH in ${rc}"
1648
+ ui_info "Added PATH entries in ${rc}"
1554
1649
  added=1
1555
1650
  fi
1556
1651
  done
@@ -1587,6 +1682,14 @@ _ensure_nomad_hcl() {
1587
1682
  # Dirs are created by the current user — no sudo needed
1588
1683
  chown -R "${REAL_USER}:${REAL_GID:-${REAL_USER}}" "${JISHUSHELL_HOME}" 2>/dev/null || true
1589
1684
 
1685
+ # Loopback interface name: lo0 on macOS, lo on Linux.
1686
+ # Forces Nomad to fingerprint 127.0.0.1 as the node IP so Docker port
1687
+ # publishing binds to loopback instead of the LAN IP. On macOS+Colima the
1688
+ # LAN IP doesn't exist inside the Lima VM, causing "cannot assign requested
1689
+ # address" when Docker tries to bind to it.
1690
+ local loopback_iface="lo"
1691
+ [[ "$OS" == "macos" ]] && loopback_iface="lo0"
1692
+
1590
1693
  cat > "$config_file" << NOMAD_HCL
1591
1694
  data_dir = "${nomad_data_dir}"
1592
1695
 
@@ -1608,6 +1711,7 @@ server {
1608
1711
  client {
1609
1712
  enabled = true
1610
1713
  servers = ["127.0.0.1:4647"]
1714
+ network_interface = "${loopback_iface}"
1611
1715
  alloc_dir = "${nomad_alloc_dir}"
1612
1716
 
1613
1717
  drain_on_shutdown {
@@ -1805,10 +1909,10 @@ _install_nomad_launchd() {
1805
1909
 
1806
1910
  mkdir -p "${HOME}/Library/LaunchAgents"
1807
1911
 
1808
- local docker_sock="${HOME}/.docker/run/docker.sock"
1809
- if [[ ! -S "$docker_sock" ]]; then
1810
- docker_sock="/var/run/docker.sock"
1811
- fi
1912
+ # Always use JishuShell's private Colima socket — hardcoded, not runtime-detected.
1913
+ # Colima may not be running yet when the plist is written; runtime fallback would
1914
+ # pick the wrong socket (Docker Desktop or /var/run/docker.sock).
1915
+ local docker_sock="${_COLIMA_SOCKET}"
1812
1916
 
1813
1917
  cat > "$plist_path" << PLIST
1814
1918
  <?xml version="1.0" encoding="UTF-8"?>
@@ -1870,6 +1974,54 @@ fs.writeFileSync(p, JSON.stringify(cfg, null, 2));
1870
1974
  " 2>/dev/null || true
1871
1975
  }
1872
1976
 
1977
+ _read_openclaw_image_from_panel() {
1978
+ local panel_file="${JISHUSHELL_HOME}/panel.json"
1979
+ node -e "
1980
+ const fs = require('fs');
1981
+ const p = '${panel_file}';
1982
+ try {
1983
+ const cfg = JSON.parse(fs.readFileSync(p, 'utf8'));
1984
+ if (typeof cfg.openclaw_image === 'string' && cfg.openclaw_image.trim()) {
1985
+ process.stdout.write(cfg.openclaw_image.trim());
1986
+ }
1987
+ } catch {}
1988
+ " 2>/dev/null || true
1989
+ }
1990
+
1991
+ _pin_openclaw_image_if_needed() {
1992
+ local image="$1"
1993
+ if [[ -z "${image}" ]]; then
1994
+ return 1
1995
+ fi
1996
+ if [[ ! "${image}" =~ :(latest|slim)$ ]]; then
1997
+ printf '%s' "${image}"
1998
+ return 0
1999
+ fi
2000
+
2001
+ local version=""
2002
+ version="$(docker_exec run --rm --entrypoint node "${image}" -p "require('/app/node_modules/openclaw/package.json').version" 2>/dev/null | tr -d '\r\n')"
2003
+ if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
2004
+ printf '%s' "${image}"
2005
+ return 0
2006
+ fi
2007
+
2008
+ local repo="${image%:*}"
2009
+ local pinned="${repo}:${version}"
2010
+ if docker_exec image inspect "${pinned}" &>/dev/null 2>&1; then
2011
+ printf '%s' "${pinned}"
2012
+ return 0
2013
+ fi
2014
+
2015
+ if docker_exec tag "${image}" "${pinned}" &>/dev/null 2>&1; then
2016
+ docker_exec rmi "${image}" &>/dev/null 2>&1 || true
2017
+ printf '%s' "${pinned}"
2018
+ return 0
2019
+ fi
2020
+
2021
+ printf '%s' "${image}"
2022
+ return 0
2023
+ }
2024
+
1873
2025
  # Install OpenClaw npm package on the host (for process manager / raw_exec modes).
1874
2026
  # Skipped when using official Docker image.
1875
2027
  _install_openclaw_npm() {
@@ -1921,25 +2073,15 @@ install_openclaw() {
1921
2073
  return 1
1922
2074
  fi
1923
2075
 
2076
+ local docker_tag="${OPENCLAW_DOCKER_TAG}"
2077
+ local configured_tag=""
2078
+
1924
2079
  if [[ "$DRY_RUN" == "1" ]]; then
1925
- ui_info "[dry-run] Would: npm install -g --prefix openclaw@${OPENCLAW_NPM_VERSION}"
1926
- ui_info "[dry-run] Would: docker build -t jishushell-openclaw:<version> (npm package + Python)"
2080
+ ui_info "[dry-run] Would: docker pull ${docker_tag} (fallback: local build)"
1927
2081
  return 0
1928
2082
  fi
1929
2083
 
1930
- # ── Step 1: Install OpenClaw npm package (used as docker build context) ──
1931
- _install_openclaw_npm || return 1
1932
-
1933
- # Resolve versioned tag from installed package (e.g. jishushell-openclaw:2026.3.31)
1934
- local pkg_dir="${JISHUSHELL_HOME}/packages/openclaw"
1935
- local oc_ver
1936
- oc_ver="$(node -p "require('${pkg_dir}/lib/node_modules/openclaw/package.json').version" 2>/dev/null || echo "")"
1937
- local docker_tag="${OPENCLAW_DOCKER_TAG}"
1938
- if [[ -n "$oc_ver" ]]; then
1939
- docker_tag="jishushell-openclaw:${oc_ver}"
1940
- fi
1941
-
1942
- # ── Step 2: Ensure Docker daemon is accessible ────────────────────────────
2084
+ # ── Step 1: Ensure Docker daemon is accessible ────────────────────────────
1943
2085
  if ! docker_exec info &>/dev/null 2>&1; then
1944
2086
  if command -v sg &>/dev/null 2>/dev/null && sg docker -c "docker info" &>/dev/null 2>&1; then
1945
2087
  DOCKER_CMD_PREFIX="sg docker -c"
@@ -1950,7 +2092,7 @@ install_openclaw() {
1950
2092
  else
1951
2093
  ui_warn "Docker daemon is not reachable"
1952
2094
  if [[ "$OS" == "macos" ]]; then
1953
- ui_warn "Make sure Docker Desktop is running"
2095
+ ui_warn "Run: COLIMA_HOME=${_COLIMA_HOME} colima start ${_COLIMA_PROFILE}"
1954
2096
  else
1955
2097
  ui_warn "Ensure Docker is running: sudo systemctl start docker"
1956
2098
  fi
@@ -1958,49 +2100,90 @@ install_openclaw() {
1958
2100
  fi
1959
2101
  fi
1960
2102
 
1961
- # ── Step 3: Skip if image already exists ──────────────────────────────────
2103
+ # ── Step 2: Reuse the currently configured pinned image if it already
2104
+ # exists locally. This avoids re-pulling :latest on machines where the
2105
+ # running JishuShell service has already migrated panel.json from a mutable
2106
+ # tag (e.g. :latest) to an immutable version tag (e.g. :2026.4.9).
2107
+ configured_tag="$(_read_openclaw_image_from_panel)"
2108
+ if [[ -n "${configured_tag}" ]] && docker_exec image inspect "${configured_tag}" &>/dev/null 2>&1; then
2109
+ OPENCLAW_IMAGE="$(_pin_openclaw_image_if_needed "${configured_tag}")"
2110
+ _save_openclaw_image_to_panel "${OPENCLAW_IMAGE}"
2111
+ ui_success "Docker image ${OPENCLAW_IMAGE} already exists — reusing configured image"
2112
+ return 0
2113
+ fi
2114
+
2115
+ # ── Step 3: Skip if the requested install tag already exists ─────────────
1962
2116
  if docker_exec image inspect "${docker_tag}" &>/dev/null 2>&1; then
1963
- OPENCLAW_IMAGE="${docker_tag}"
1964
- _save_openclaw_image_to_panel "${docker_tag}"
1965
- ui_success "Docker image ${docker_tag} already exists — skipping"
2117
+ OPENCLAW_IMAGE="$(_pin_openclaw_image_if_needed "${docker_tag}")"
2118
+ _save_openclaw_image_to_panel "${OPENCLAW_IMAGE}"
2119
+ ui_success "Docker image ${OPENCLAW_IMAGE} already exists — skipping"
1966
2120
  return 0
1967
2121
  fi
1968
2122
 
1969
- # ── Step 4: Build Docker image (npm package + Python) ─────────────────────
1970
- local pkg_dir="${JISHUSHELL_HOME}/packages/openclaw"
2123
+ # ── Step 4: Pull from registry, fallback to local build ──────────────────
2124
+ ui_info "Pulling OpenClaw Docker image: ${docker_tag} ..."
2125
+ log_detail ""
2126
+ log_detail "[$(date '+%H:%M:%S')] docker pull ${docker_tag}"
2127
+ if log_cmd docker_exec pull "${docker_tag}"; then
2128
+ OPENCLAW_IMAGE="$(_pin_openclaw_image_if_needed "${docker_tag}")"
2129
+ _save_openclaw_image_to_panel "${OPENCLAW_IMAGE}"
2130
+ ui_success "OpenClaw Docker image pulled: ${OPENCLAW_IMAGE}"
2131
+ return 0
2132
+ fi
2133
+
2134
+ # ── Step 3b: Fallback — build locally using bundled Dockerfile ────
2135
+ ui_warn "Pull failed, falling back to local build..."
2136
+
2137
+ # Locate the bundled Dockerfile.openclaw-slim + openclaw-entry.sh.
2138
+ # Both ship at the npm package root, alongside the install/ directory,
2139
+ # so from this script's perspective they are one level up.
2140
+ local script_dir
2141
+ if [[ -n "${BASH_SOURCE[0]:-}" ]]; then
2142
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
2143
+ else
2144
+ script_dir="${PWD}"
2145
+ fi
2146
+ local dockerfile_src="${script_dir}/../Dockerfile.openclaw-slim"
2147
+ local entrypoint_src="${script_dir}/../openclaw-entry.sh"
2148
+
2149
+ if [[ ! -f "${dockerfile_src}" || ! -f "${entrypoint_src}" ]]; then
2150
+ ui_error "Bundled build files not found near ${script_dir}/.."
2151
+ ui_error "Expected: Dockerfile.openclaw-slim and openclaw-entry.sh"
2152
+ return 1
2153
+ fi
2154
+
2155
+ local build_ctx
2156
+ build_ctx="$(mktemp -d)"
2157
+ trap "rm -rf '${build_ctx}'" EXIT
2158
+
2159
+ cp "${dockerfile_src}" "${build_ctx}/Dockerfile.openclaw-slim"
2160
+ cp "${entrypoint_src}" "${build_ctx}/openclaw-entry.sh"
1971
2161
 
1972
- # Write Dockerfile into the npm package directory (build context)
1973
- cat > "${pkg_dir}/Dockerfile" << 'DOCKERFILE'
1974
- FROM node:22-slim
1975
- USER root
1976
- RUN apt-get update && apt-get install -y --no-install-recommends \
1977
- procps hostname curl git lsof openssl \
1978
- python3 python3-pip python3-venv python3-dev && \
1979
- ln -sf /usr/bin/python3 /usr/local/bin/python && \
1980
- rm -rf /var/lib/apt/lists/*
1981
- WORKDIR /app
1982
- COPY --chown=node:node lib/node_modules/ ./node_modules/
1983
- RUN ln -sf /app/node_modules/openclaw/openclaw.mjs /app/openclaw.mjs && \
1984
- ln -sf /app/node_modules/openclaw/openclaw.mjs /usr/local/bin/openclaw && \
1985
- cp /app/node_modules/openclaw/package.json /app/package.json 2>/dev/null || true
1986
- USER node
1987
- CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
1988
- DOCKERFILE
1989
-
1990
- ui_info "Building Docker image: ${docker_tag} (npm package + Python)..."
2162
+ # Query current OpenClaw version from npm so the --build-arg busts the
2163
+ # Docker layer cache for the `RUN npm install openclaw@${ver}` step.
2164
+ # Fall back to "latest" if npm is unreachable.
2165
+ local openclaw_ver
2166
+ openclaw_ver="$(npm view openclaw version 2>/dev/null)"
2167
+ if [[ -z "${openclaw_ver}" ]]; then
2168
+ openclaw_ver="latest"
2169
+ fi
2170
+ log_detail "Resolved OpenClaw version for build: ${openclaw_ver}"
2171
+
2172
+ ui_info "Building OpenClaw Docker image locally: ${docker_tag} (openclaw@${openclaw_ver}) ..."
1991
2173
  log_detail ""
1992
- log_detail "[$(date '+%H:%M:%S')] docker build -t ${docker_tag} ${pkg_dir}"
1993
- if log_cmd docker_exec build --network=host -t "${docker_tag}" "${pkg_dir}"; then
2174
+ log_detail "[$(date '+%H:%M:%S')] docker build --network=host --build-arg OPENCLAW_VERSION=${openclaw_ver} -f Dockerfile.openclaw-slim -t ${docker_tag} ${build_ctx}"
2175
+ if log_cmd docker_exec build --network=host \
2176
+ --build-arg "OPENCLAW_VERSION=${openclaw_ver}" \
2177
+ -f "${build_ctx}/Dockerfile.openclaw-slim" \
2178
+ -t "${docker_tag}" "${build_ctx}"; then
1994
2179
  OPENCLAW_IMAGE="${docker_tag}"
1995
2180
  _save_openclaw_image_to_panel "${docker_tag}"
1996
- local _local_tag="jishushell-openclaw:local"
1997
- if [[ "${docker_tag}" != "${_local_tag}" ]]; then
1998
- docker_exec tag "${docker_tag}" "${_local_tag}" 2>/dev/null || true
1999
- fi
2000
- rm -f "${pkg_dir}/Dockerfile"
2001
- ui_success "OpenClaw Docker image built: ${docker_tag} (with Python)"
2181
+ rm -rf "${build_ctx}"
2182
+ trap - EXIT
2183
+ ui_success "OpenClaw Docker image built: ${docker_tag}"
2002
2184
  else
2003
- rm -f "${pkg_dir}/Dockerfile"
2185
+ rm -rf "${build_ctx}"
2186
+ trap - EXIT
2004
2187
  ui_error "Failed to build OpenClaw Docker image"
2005
2188
  return 1
2006
2189
  fi
@@ -2043,7 +2226,7 @@ show_install_plan() {
2043
2226
  ui_kv "Node.js" "$(if [[ $SKIP_NODE -eq 1 ]]; then echo 'skip'; else echo "v${NODE_VERSION} via nvm v${NVM_VERSION}"; fi)"
2044
2227
  ui_kv "Docker" "$(if [[ $SKIP_DOCKER -eq 1 ]]; then echo 'skip'; else echo 'latest stable'; fi)"
2045
2228
  ui_kv "Nomad" "$(if [[ $SKIP_NOMAD -eq 1 ]]; then echo 'skip'; else echo "v${NOMAD_VERSION}"; fi)"
2046
- ui_kv "OpenClaw" "$(if [[ \"${SKIP_OPENCLAW}\" == \"1\" ]]; then echo 'skip'; else echo \"openclaw@${OPENCLAW_NPM_VERSION} + docker build ${OPENCLAW_DOCKER_TAG}\"; fi)"
2229
+ ui_kv "OpenClaw" "$(if [[ \"${SKIP_OPENCLAW}\" == \"1\" ]]; then echo 'skip'; else echo \"docker pull ${OPENCLAW_DOCKER_TAG}\"; fi)"
2047
2230
  if [[ $with_jishushell -eq 1 ]]; then
2048
2231
  local _plan_jishu
2049
2232
  if [[ $SKIP_JISHUSHELL -eq 1 ]]; then
@@ -2114,7 +2297,15 @@ show_summary() {
2114
2297
  fi
2115
2298
 
2116
2299
  if [[ "${SKIP_OPENCLAW}" != "1" ]]; then
2117
- if [[ -n "${OPENCLAW_IMAGE}" ]] && docker_exec image inspect "${OPENCLAW_IMAGE}" &>/dev/null 2>&1; then
2300
+ local _summary_openclaw_image=""
2301
+ _summary_openclaw_image="$(_read_openclaw_image_from_panel)"
2302
+ if [[ -z "${_summary_openclaw_image}" && -n "${OPENCLAW_IMAGE}" ]]; then
2303
+ _summary_openclaw_image="${OPENCLAW_IMAGE}"
2304
+ fi
2305
+
2306
+ if [[ -n "${_summary_openclaw_image}" ]] && docker_exec image inspect "${_summary_openclaw_image}" &>/dev/null 2>&1; then
2307
+ ui_kv "OpenClaw" "✓ ${_summary_openclaw_image}"
2308
+ elif [[ -n "${OPENCLAW_IMAGE}" ]] && docker_exec image inspect "${OPENCLAW_IMAGE}" &>/dev/null 2>&1; then
2118
2309
  ui_kv "OpenClaw" "✓ ${OPENCLAW_IMAGE}"
2119
2310
  elif [[ "$DRY_RUN" == "1" ]]; then
2120
2311
  ui_kv "OpenClaw" "- dry-run"
@@ -2462,7 +2653,9 @@ WantedBy=multi-user.target"
2462
2653
  ${SUDO} systemctl restart jishushell 2>/dev/null || true
2463
2654
  ui_success "JishuShell systemd service updated and restarted"
2464
2655
  else
2465
- ui_success "JishuShell systemd service already installed"
2656
+ # Package may have been upgraded — always restart to pick up new code
2657
+ ${SUDO} systemctl restart jishushell 2>/dev/null || true
2658
+ ui_success "JishuShell systemd service restarted"
2466
2659
  fi
2467
2660
  }
2468
2661
 
@@ -2672,7 +2865,7 @@ Steps:
2672
2865
  1 Node.js (via nvm)
2673
2866
  2 Docker
2674
2867
  3 Nomad
2675
- 4 OpenClaw (npm install + docker build)
2868
+ 4 OpenClaw (docker pull / local build)
2676
2869
  5 JishuShell
2677
2870
  6 JishuShell service registration (autostart)
2678
2871
 
@@ -2735,27 +2928,37 @@ _prompt_install_confirm() {
2735
2928
  echo -e " commercial use, competitive offerings, or redistribution."
2736
2929
  echo ""
2737
2930
 
2738
- if [[ $SKIP_DOCKER -eq 0 ]]; then
2931
+ if [[ $SKIP_DOCKER -eq 0 && "$OS" == "linux" ]]; then
2739
2932
  echo -e " ${BOLD}Docker Engine${NC}"
2740
- echo -e " ${MUTED} URL : https://github.com/moby/moby${NC}"
2741
- echo -e " ${MUTED} License : Apache License, Version 2.0${NC}"
2742
- echo -e " ${MUTED} https://www.apache.org/licenses/LICENSE-2.0${NC}"
2743
- echo -e " ${MUTED} Author : Docker, Inc.${NC}"
2933
+ echo -e " ${MUTED} Docker Engine (container runtime — Linux)${NC}"
2934
+ echo -e " ${MUTED} URL : https://github.com/moby/moby${NC}"
2935
+ echo -e " ${MUTED} License : Apache License, Version 2.0${NC}"
2936
+ echo -e " ${MUTED} Author : Docker, Inc.${NC}"
2937
+ echo -e " ${MUTED} https://github.com/moby/moby/blob/master/LICENSE${NC}"
2938
+ echo ""
2939
+ fi
2940
+ if [[ $SKIP_DOCKER -eq 0 && "$OS" == "macos" ]]; then
2941
+ echo -e " ${BOLD}Colima${NC}"
2942
+ echo -e " ${MUTED} Colima (container runtime — macOS)${NC}"
2943
+ echo -e " ${MUTED} URL : https://github.com/abiosoft/colima${NC}"
2944
+ echo -e " ${MUTED} License : MIT License${NC}"
2945
+ echo -e " ${MUTED} https://github.com/abiosoft/colima/blob/main/LICENSE${NC}"
2946
+ echo -e " ${MUTED} Author : Abiola Ibrahim${NC}"
2744
2947
  echo ""
2745
2948
  fi
2746
2949
  if [[ $SKIP_NOMAD -eq 0 ]]; then
2747
- echo -e " ${BOLD}Nomad v${NOMAD_VERSION}+ (>= 1.7.0)${NC}"
2748
- echo -e " ${MUTED} URL : https://github.com/hashicorp/nomad${NC}"
2749
- echo -e " ${MUTED} License : Business Source License 1.1 (BSL 1.1)${NC}"
2750
- echo -e " ${MUTED} https://github.com/hashicorp/nomad/blob/main/LICENSE${NC}"
2751
- echo -e " ${MUTED} Licensor: International Business Machines Corporation (IBM)${NC}"
2752
- echo -e " ${MUTED} Work : Nomad Version 1.7.0 or later. (c) 2024 IBM Corp.${NC}"
2950
+ echo -e " ${BOLD}Nomad${NC}"
2951
+ echo -e " ${MUTED} Nomad v${NOMAD_VERSION}+ (>= 1.7.0)${NC}"
2952
+ echo -e " ${MUTED} URL : https://github.com/hashicorp/nomad${NC}"
2953
+ echo -e " ${MUTED} License : Business Source License 1.1 (BSL 1.1)${NC}"
2954
+ echo -e " ${MUTED} https://github.com/hashicorp/nomad/blob/main/LICENSE${NC}"
2955
+ echo -e " ${MUTED} Licensor: International Business Machines Corporation (IBM)${NC}"
2956
+ echo -e " ${MUTED} Work : Nomad Version 1.7.0 or later. (c) 2024 IBM Corp.${NC}"
2753
2957
  echo ""
2754
2958
  fi
2755
- echo -e " ${MUTED}─────────────────────────────────────────────────────────${NC}"
2756
- echo -e " ${MUTED}By continuing you acknowledge that you have read the above${NC}"
2757
- echo -e " ${MUTED}notices and agree to each package's license terms.${NC}"
2758
- echo -e " ${MUTED}sudo privileges are required to write to system directories.${NC}"
2959
+ echo -e " ${ACCENT}─────────────────────────────────────────────────────────${NC}"
2960
+ echo -e " By continuing you acknowledge that you have read the above"
2961
+ echo -e " notices and agree to each package's license terms."
2759
2962
  echo ""
2760
2963
  } >/dev/tty
2761
2964
 
@@ -2767,6 +2970,9 @@ _prompt_install_confirm() {
2767
2970
  exit 0
2768
2971
  ;;
2769
2972
  esac
2973
+
2974
+ echo "" >/dev/tty
2975
+ echo -e " ${INFO}sudo privileges are required to write to system directories.${NC}" >/dev/tty
2770
2976
  echo "" >/dev/tty
2771
2977
  }
2772
2978
 
@@ -2885,7 +3091,7 @@ _jishu_install_main() {
2885
3091
  # so the keepalive's early sudo -n true calls are intentional no-ops.
2886
3092
  if [[ $EUID -ne 0 ]] && command -v sudo &>/dev/null; then
2887
3093
  if [[ -z "${_SUDO_KEEPALIVE_PID:-}" ]]; then
2888
- ( while true; do sudo -n true 2>/dev/null; sleep 60; done ) &
3094
+ ( while true; do sudo -n true 2>/dev/null; sleep 60; done ) &>/dev/null &
2889
3095
  _SUDO_KEEPALIVE_PID=$!
2890
3096
  disown "$_SUDO_KEEPALIVE_PID" 2>/dev/null || true
2891
3097
  fi
@@ -2900,10 +3106,6 @@ _jishu_install_main() {
2900
3106
  detect_os
2901
3107
  detect_arch
2902
3108
  show_install_plan --with-jishushell
2903
- if [[ "$OS" == "macos" ]]; then
2904
- ui_warn "macOS is not supported yet — coming soon!"
2905
- exit 0
2906
- fi
2907
3109
  _prompt_install_confirm
2908
3110
  check_sudo
2909
3111
  ensure_prerequisites