fix: support Debian 13 offline bootstrap
This commit is contained in:
parent
2cb26128fb
commit
e950eb18b8
@ -169,7 +169,7 @@ dynamic_parallel_job_limit() {
|
||||
fi
|
||||
|
||||
load_average="$(one_minute_load_average)"
|
||||
load_ceiling="$(awk -v load="$load_average" 'BEGIN { value=int(load); if (load > value) value++; print value }')"
|
||||
load_ceiling="$(awk -v average="$load_average" 'BEGIN { value=int(average); if (average > value) value++; print value }')"
|
||||
dynamic_limit=$((hard_limit - load_ceiling))
|
||||
if [ "$dynamic_limit" -lt 1 ]; then
|
||||
dynamic_limit=1
|
||||
@ -711,6 +711,40 @@ validate_offline_package_target() {
|
||||
fi
|
||||
}
|
||||
|
||||
validate_offline_package_requirements() {
|
||||
local root=$1
|
||||
local target=$2
|
||||
|
||||
case "$target" in
|
||||
"ubuntu 26.04 "*)
|
||||
if ! compgen -G "$root/packages/apt/npm_*.deb" >/dev/null; then
|
||||
warn "Ubuntu 26.04 offline package is missing the required standalone npm package."
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
refresh_offline_package_repositories() {
|
||||
local root=$1
|
||||
local repo_dir branch
|
||||
|
||||
offline_mode_is_force && return
|
||||
command -v git >/dev/null 2>&1 || return
|
||||
curl -m 3 -sI https://github.com >/dev/null 2>&1 || return
|
||||
|
||||
for repo_dir in "$root/repos/xworkspace-console" "$root/repos/playbooks"; do
|
||||
[ -d "$repo_dir/.git" ] || continue
|
||||
branch="$(git -C "$repo_dir" symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
|
||||
[ -n "$branch" ] || continue
|
||||
info "Refreshing packaged $(basename "$repo_dir") checkout from origin/$branch..."
|
||||
if ! git -C "$repo_dir" fetch origin "$branch" >/dev/null 2>&1 ||
|
||||
! git -C "$repo_dir" reset --hard "origin/$branch" >/dev/null 2>&1; then
|
||||
warn "Unable to refresh packaged $(basename "$repo_dir") checkout; using bundled revision."
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
run_offline_installer() {
|
||||
local root=$1
|
||||
local target=$2
|
||||
@ -718,11 +752,17 @@ run_offline_installer() {
|
||||
local env_args=(
|
||||
"AI_WORKSPACE_OFFLINE_ACTIVE=true"
|
||||
"AI_WORKSPACE_DEPLOYMENT_LOCK_HELD=true"
|
||||
# The bundled repositories can retain the release builder's uid. Keep
|
||||
# this exception scoped to the offline installer process and children.
|
||||
"GIT_CONFIG_COUNT=1"
|
||||
"GIT_CONFIG_KEY_0=safe.directory"
|
||||
"GIT_CONFIG_VALUE_0=*"
|
||||
)
|
||||
local env_name
|
||||
|
||||
[ -f "$installer" ] || return 1
|
||||
validate_offline_package_target "$root" "$target"
|
||||
refresh_offline_package_repositories "$root"
|
||||
chmod +x "$installer"
|
||||
|
||||
for env_name in \
|
||||
@ -746,6 +786,8 @@ run_offline_installer() {
|
||||
ANSIBLE_VAULT_PASSWORD \
|
||||
AI_WORKSPACE_AUTH_TOKEN_FILE \
|
||||
AI_WORKSPACE_VAULT_PASSWORD_FILE \
|
||||
XWORKSPACE_CONSOLE_USER \
|
||||
XWORKSPACE_CONSOLE_HOME \
|
||||
XWORKSPACE_CONSOLE_SOURCE_REPO \
|
||||
XWORKSPACE_CONSOLE_SOURCE_VERSION \
|
||||
AI_WORKSPACE_APT_LOCK_TIMEOUT; do
|
||||
@ -792,6 +834,10 @@ try_bootstrap_from_offline_package() {
|
||||
offline_fail_or_fallback "Unable to prepare offline package from $source."
|
||||
return 1
|
||||
}
|
||||
if ! validate_offline_package_requirements "$root" "$target"; then
|
||||
offline_fail_or_fallback "Offline package requirements are incomplete for this host."
|
||||
return 1
|
||||
fi
|
||||
if run_offline_installer "$root" "$target"; then
|
||||
return 0
|
||||
fi
|
||||
@ -799,6 +845,41 @@ try_bootstrap_from_offline_package() {
|
||||
error "Offline package installer failed after making deployment changes; online fallback was not started."
|
||||
}
|
||||
|
||||
linux_default_console_user() {
|
||||
if [ -n "${XWORKSPACE_CONSOLE_USER:-}" ]; then
|
||||
printf '%s\n' "$XWORKSPACE_CONSOLE_USER"
|
||||
elif [ "$(id -u)" -eq 0 ]; then
|
||||
printf 'ubuntu\n'
|
||||
else
|
||||
id -un
|
||||
fi
|
||||
}
|
||||
|
||||
linux_default_console_home() {
|
||||
local user=$1
|
||||
if [ -n "${XWORKSPACE_CONSOLE_HOME:-}" ]; then
|
||||
printf '%s\n' "$XWORKSPACE_CONSOLE_HOME"
|
||||
elif command -v getent >/dev/null 2>&1 && getent passwd "$user" >/dev/null 2>&1; then
|
||||
getent passwd "$user" | cut -d: -f6
|
||||
elif [ "$user" = "root" ]; then
|
||||
printf '/root\n'
|
||||
else
|
||||
printf '/home/%s\n' "$user"
|
||||
fi
|
||||
}
|
||||
|
||||
append_linux_console_identity_vars() {
|
||||
local console_user=$1
|
||||
local console_home=$2
|
||||
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "xworkspace_console_user=$console_user")
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "xworkspace_console_home=$console_home")
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "xworkspace_console_root=$console_home/.local/state/ai-workspace")
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "xworkspace_console_config_dir=$console_home/.config/xworkspace")
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "xworkspace_console_scripts_dir=$console_home/.local/state/ai-workspace/scripts")
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "xworkspace_console_repo_dir=$console_home/xworkspace-console")
|
||||
}
|
||||
|
||||
resolve_unified_auth_token() {
|
||||
local token="${AI_WORKSPACE_AUTH_TOKEN:-}"
|
||||
if [ -z "$token" ]; then token="${XWORKSPACE_CONSOLE_AUTH_TOKEN:-}"; fi
|
||||
@ -1630,6 +1711,9 @@ if ! command -v ansible-playbook >/dev/null 2>&1 || ! command -v git >/dev/null
|
||||
install_prerequisites "$OS_NAME"
|
||||
fi
|
||||
|
||||
# Git may have been installed after the early best-effort configuration above.
|
||||
git config --global --add safe.directory '*' || true
|
||||
|
||||
export XWORKSPACE_CONSOLE_PUBLIC_ACCESS="${XWORKSPACE_CONSOLE_PUBLIC_ACCESS:-false}"
|
||||
export XWORKMATE_BRIDGE_PUBLIC_ACCESS="${XWORKMATE_BRIDGE_PUBLIC_ACCESS:-true}"
|
||||
export GATEWAY_OPENCLAW_PUBLIC_ACCESS="${GATEWAY_OPENCLAW_PUBLIC_ACCESS:-false}"
|
||||
@ -1897,6 +1981,11 @@ if [ "$(detect_os)" = "darwin" ]; then
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "gateway_openclaw_home=$HOME")
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "gateway_openclaw_compile_cache_dir=$HOME/.cache/openclaw-compile-cache")
|
||||
ANSIBLE_EXTRA_VARS+=("-e" "gateway_openclaw_service_path=$DARWIN_SERVICE_PATH")
|
||||
else
|
||||
LINUX_CONSOLE_USER="$(linux_default_console_user)"
|
||||
LINUX_CONSOLE_HOME="$(linux_default_console_home "$LINUX_CONSOLE_USER")"
|
||||
info "Deploying AI Workspace runtime as $LINUX_CONSOLE_USER under $LINUX_CONSOLE_HOME..."
|
||||
append_linux_console_identity_vars "$LINUX_CONSOLE_USER" "$LINUX_CONSOLE_HOME"
|
||||
fi
|
||||
|
||||
# Export environment fallbacks for roles/scripts that read environment directly.
|
||||
|
||||
@ -70,6 +70,133 @@ test_non_root_without_sudo_fails_cleanly() (
|
||||
run_as_root apt-get update -y
|
||||
)
|
||||
|
||||
test_forced_offline_mode_does_not_refresh_repositories() (
|
||||
# shellcheck disable=SC2329
|
||||
git() {
|
||||
fail "forced offline mode invoked git"
|
||||
}
|
||||
AI_WORKSPACE_OFFLINE_MODE=force refresh_offline_package_repositories /nonexistent
|
||||
)
|
||||
|
||||
test_auto_offline_mode_refreshes_packaged_repositories() (
|
||||
package_root="$(mktemp -d)"
|
||||
mkdir -p "$package_root/repos/xworkspace-console/.git" "$package_root/repos/playbooks/.git"
|
||||
git_log="$(mktemp)"
|
||||
# shellcheck disable=SC2329
|
||||
curl() {
|
||||
return 0
|
||||
}
|
||||
# shellcheck disable=SC2329
|
||||
git() {
|
||||
printf '%s\n' "$*" >> "$git_log"
|
||||
if [ "${3:-}" = "symbolic-ref" ]; then
|
||||
printf 'main\n'
|
||||
fi
|
||||
}
|
||||
|
||||
AI_WORKSPACE_OFFLINE_MODE=auto refresh_offline_package_repositories "$package_root"
|
||||
[ "$(grep -c 'fetch origin main' "$git_log")" -eq 2 ] || fail "packaged repositories were not fetched"
|
||||
[ "$(grep -c 'reset --hard origin/main' "$git_log")" -eq 2 ] || fail "packaged repositories were not updated"
|
||||
rm -rf "$package_root" "$git_log"
|
||||
)
|
||||
|
||||
test_ubuntu_2604_offline_package_requires_npm() (
|
||||
package_root="$(mktemp -d)"
|
||||
mkdir -p "$package_root/packages/apt"
|
||||
if validate_offline_package_requirements "$package_root" "ubuntu 26.04 amd64" 2>/dev/null; then
|
||||
fail "Ubuntu 26.04 package without npm was accepted"
|
||||
fi
|
||||
touch "$package_root/packages/apt/npm_9.2.0_all.deb"
|
||||
validate_offline_package_requirements "$package_root" "ubuntu 26.04 amd64"
|
||||
validate_offline_package_requirements "$package_root" "debian 12 amd64"
|
||||
rm -rf "$package_root"
|
||||
)
|
||||
|
||||
test_dynamic_parallel_limit_avoids_awk_reserved_names() (
|
||||
# shellcheck disable=SC2329
|
||||
online_cpu_count() { printf '4\n'; }
|
||||
# shellcheck disable=SC2329
|
||||
one_minute_load_average() { printf '1.2\n'; }
|
||||
[ "$(AI_WORKSPACE_MAX_PARALLEL_JOBS=auto dynamic_parallel_job_limit)" = "6" ] || fail "dynamic parallel limit was calculated incorrectly"
|
||||
)
|
||||
|
||||
test_offline_installer_gets_scoped_git_config() (
|
||||
installer_root="$(mktemp -d)"
|
||||
mkdir -p "$installer_root/scripts"
|
||||
cat > "$installer_root/scripts/ai-workspace-offline-install.sh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
[ "${GIT_CONFIG_COUNT:-}" = "1" ]
|
||||
[ "${GIT_CONFIG_KEY_0:-}" = "safe.directory" ]
|
||||
[ "${GIT_CONFIG_VALUE_0:-}" = "*" ]
|
||||
EOF
|
||||
chmod +x "$installer_root/scripts/ai-workspace-offline-install.sh"
|
||||
# shellcheck disable=SC2329
|
||||
validate_offline_package_target() { :; }
|
||||
# shellcheck disable=SC2329
|
||||
id() {
|
||||
[ "${1:-}" = "-u" ] && printf '0\n'
|
||||
}
|
||||
run_offline_installer "$installer_root" "debian 13 amd64"
|
||||
rm -rf "$installer_root"
|
||||
)
|
||||
|
||||
test_linux_root_defaults_to_ubuntu_home() (
|
||||
# shellcheck disable=SC2329
|
||||
id() {
|
||||
if [ "${1:-}" = "-u" ]; then
|
||||
printf '0\n'
|
||||
elif [ "${1:-}" = "-un" ]; then
|
||||
printf 'root\n'
|
||||
fi
|
||||
}
|
||||
# shellcheck disable=SC2329
|
||||
getent() {
|
||||
return 1
|
||||
}
|
||||
|
||||
user="$(linux_default_console_user)"
|
||||
home="$(linux_default_console_home "$user")"
|
||||
[ "$user" = "ubuntu" ] || fail "root Linux default user was not ubuntu"
|
||||
[ "$home" = "/home/ubuntu" ] || fail "root Linux default home was not /home/ubuntu"
|
||||
)
|
||||
|
||||
test_linux_non_root_uses_current_user_home() (
|
||||
# shellcheck disable=SC2329
|
||||
id() {
|
||||
if [ "${1:-}" = "-u" ]; then
|
||||
printf '501\n'
|
||||
elif [ "${1:-}" = "-un" ]; then
|
||||
printf 'shenlan\n'
|
||||
fi
|
||||
}
|
||||
# shellcheck disable=SC2329
|
||||
getent() {
|
||||
[ "${1:-}" = "passwd" ] && [ "${2:-}" = "shenlan" ] || return 1
|
||||
printf 'shenlan:x:501:20::/Users/shenlan:/bin/zsh\n'
|
||||
}
|
||||
|
||||
user="$(linux_default_console_user)"
|
||||
home="$(linux_default_console_home "$user")"
|
||||
[ "$user" = "shenlan" ] || fail "non-root Linux default user was not current user"
|
||||
[ "$home" = "/Users/shenlan" ] || fail "non-root Linux default home did not come from passwd"
|
||||
)
|
||||
|
||||
test_linux_identity_vars_can_be_overridden() (
|
||||
export XWORKSPACE_CONSOLE_USER=deploy
|
||||
export XWORKSPACE_CONSOLE_HOME=/srv/deploy
|
||||
user="$(linux_default_console_user)"
|
||||
home="$(linux_default_console_home "$user")"
|
||||
[ "$user" = "deploy" ] || fail "explicit console user was ignored"
|
||||
[ "$home" = "/srv/deploy" ] || fail "explicit console home was ignored"
|
||||
|
||||
ANSIBLE_EXTRA_VARS=()
|
||||
append_linux_console_identity_vars "$user" "$home"
|
||||
printf '%s\n' "${ANSIBLE_EXTRA_VARS[@]}" | grep -q '^xworkspace_console_user=deploy$' || fail "console user extra var missing"
|
||||
printf '%s\n' "${ANSIBLE_EXTRA_VARS[@]}" | grep -q '^xworkspace_console_home=/srv/deploy$' || fail "console home extra var missing"
|
||||
printf '%s\n' "${ANSIBLE_EXTRA_VARS[@]}" | grep -q '^xworkspace_console_repo_dir=/srv/deploy/xworkspace-console$' || fail "console repo extra var missing"
|
||||
)
|
||||
|
||||
test_root_does_not_require_sudo
|
||||
printf 'ok - root execution does not require sudo\n'
|
||||
test_non_root_uses_sudo
|
||||
@ -81,3 +208,19 @@ set -e
|
||||
[ "$privilege_status" -ne 0 ] || fail "non-root execution without sudo unexpectedly succeeded"
|
||||
printf '%s' "$privilege_error" | grep -q "Root privileges are required" || fail "missing sudo error was not actionable"
|
||||
printf 'ok - missing sudo reports a privilege error\n'
|
||||
test_forced_offline_mode_does_not_refresh_repositories
|
||||
printf 'ok - forced offline mode does not refresh packaged repositories\n'
|
||||
test_auto_offline_mode_refreshes_packaged_repositories
|
||||
printf 'ok - auto offline mode refreshes packaged repositories\n'
|
||||
test_ubuntu_2604_offline_package_requires_npm
|
||||
printf 'ok - Ubuntu 26.04 offline package requires npm\n'
|
||||
test_dynamic_parallel_limit_avoids_awk_reserved_names
|
||||
printf 'ok - dynamic parallel limit is compatible with modern gawk\n'
|
||||
test_offline_installer_gets_scoped_git_config
|
||||
printf 'ok - offline installer receives scoped Git ownership compatibility\n'
|
||||
test_linux_root_defaults_to_ubuntu_home
|
||||
printf 'ok - Linux root deployment defaults to ubuntu home\n'
|
||||
test_linux_non_root_uses_current_user_home
|
||||
printf 'ok - Linux non-root deployment uses passwd home\n'
|
||||
test_linux_identity_vars_can_be_overridden
|
||||
printf 'ok - Linux deployment identity can be overridden\n'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user