feat: aggregate prebuilt workspace releases
This commit is contained in:
parent
52d2243478
commit
6f85f4d183
@ -1,14 +1,6 @@
|
||||
name: Build Offline AI Workspace All-in-One Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'scripts/create-ai-workspace-offline-package.sh'
|
||||
- 'scripts/ai-workspace-offline-install.sh'
|
||||
- 'scripts/setup-ai-workspace-all-in-one.sh'
|
||||
- '.github/workflows/offline-package-ai-workspace-installer.yaml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@ -30,6 +22,26 @@ on:
|
||||
required: false
|
||||
default: "main"
|
||||
type: string
|
||||
console_runtime_release_tag:
|
||||
description: "Console runtime release tag, or latest-runtime"
|
||||
required: false
|
||||
default: "latest-runtime"
|
||||
type: string
|
||||
bridge_runtime_release_tag:
|
||||
description: "Bridge runtime release tag, or latest-runtime"
|
||||
required: false
|
||||
default: "latest-runtime"
|
||||
type: string
|
||||
qmd_runtime_release_tag:
|
||||
description: "QMD runtime release tag, or latest-runtime"
|
||||
required: false
|
||||
default: "latest-runtime"
|
||||
type: string
|
||||
litellm_runtime_release_tag:
|
||||
description: "LiteLLM runtime release tag, or latest-runtime"
|
||||
required: false
|
||||
default: "latest-runtime"
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@ -102,6 +114,11 @@ jobs:
|
||||
PLAYBOOKS_REF: ${{ github.event.inputs.playbooks_ref || 'main' }}
|
||||
CONSOLE_REF: ${{ github.event_name == 'push' && github.sha || github.event.inputs.console_ref || 'main' }}
|
||||
CORE_SKILLS_REF: ${{ github.event.inputs.core_skills_ref || 'main' }}
|
||||
CONSOLE_RUNTIME_RELEASE_TAG: ${{ github.event.inputs.console_runtime_release_tag || 'latest-runtime' }}
|
||||
BRIDGE_RUNTIME_RELEASE_TAG: ${{ github.event.inputs.bridge_runtime_release_tag || 'latest-runtime' }}
|
||||
QMD_RUNTIME_RELEASE_TAG: ${{ github.event.inputs.qmd_runtime_release_tag || 'latest-runtime' }}
|
||||
LITELLM_RUNTIME_RELEASE_TAG: ${{ github.event.inputs.litellm_runtime_release_tag || 'latest-runtime' }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PACKAGE_VERSION: ${{ github.run_number }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@ -180,6 +197,10 @@ jobs:
|
||||
grep -q 'metadata/target.env' contents.txt
|
||||
grep -q 'repos/playbooks' contents.txt
|
||||
grep -q 'repos/xworkspace-console' contents.txt
|
||||
grep -q 'packages/components/xworkspace-console-runtime.tar.gz' contents.txt
|
||||
grep -q 'packages/components/qmd-runtime.tar.gz' contents.txt
|
||||
grep -q 'metadata/litellm-runtime.env' contents.txt
|
||||
grep -q 'metadata/components/xworkmate-bridge.tag' contents.txt
|
||||
grep -q 'packages/apt/Packages.gz' contents.txt
|
||||
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
|
||||
grep -Eq 'packages/playwright-browsers/.*/chrome-linux(64)?/chrome$' contents.txt
|
||||
|
||||
128
.github/workflows/runtime-release.yaml
vendored
Normal file
128
.github/workflows/runtime-release.yaml
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
name: Build XWorkspace Console Runtime Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- api/**
|
||||
- dashboard/**
|
||||
- scripts/**
|
||||
- .github/workflows/runtime-release.yaml
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: xworkspace-console-runtime-release-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build linux-${{ matrix.arch }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: api/go.mod
|
||||
cache: false
|
||||
|
||||
- name: Build target runtime
|
||||
env:
|
||||
TARGET_ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
root="dist/runtime/xworkspace-console"
|
||||
mkdir -p "${root}/dashboard" "${root}/bin" "dist/assets"
|
||||
|
||||
cp -a scripts "${root}/"
|
||||
cp dashboard/package.json dashboard/package-lock.json dashboard/index.html \
|
||||
dashboard/tsconfig.json dashboard/vite.config.ts "${root}/dashboard/"
|
||||
cp -a dashboard/src "${root}/dashboard/"
|
||||
|
||||
docker run --rm --platform "linux/${TARGET_ARCH}" \
|
||||
-v "${PWD}/dashboard:/src:ro" \
|
||||
-v "${PWD}/${root}/dashboard:/out" \
|
||||
node:24-bookworm \
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
cp -a /src/. /tmp/dashboard/
|
||||
cd /tmp/dashboard
|
||||
npm ci --no-audit --no-fund
|
||||
npm run build
|
||||
cp -a dist node_modules /out/
|
||||
'
|
||||
|
||||
(
|
||||
cd api
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH="${TARGET_ARCH}" \
|
||||
go build -buildvcs=false -trimpath -o "../${root}/bin/xworkspace-api" .
|
||||
)
|
||||
|
||||
cat > "${root}/manifest.json" <<JSON
|
||||
{
|
||||
"component": "xworkspace-console",
|
||||
"commit": "${GITHUB_SHA}",
|
||||
"os": "linux",
|
||||
"arch": "${TARGET_ARCH}",
|
||||
"apiBinary": "bin/xworkspace-api",
|
||||
"dashboard": "dashboard"
|
||||
}
|
||||
JSON
|
||||
tar -czf "dist/assets/xworkspace-console-runtime-linux-${TARGET_ARCH}.tar.gz" \
|
||||
-C "dist/runtime" xworkspace-console
|
||||
(
|
||||
cd dist/assets
|
||||
sha256sum -- ./*.tar.gz | sed 's# \./# #' > "SHA256SUMS-${TARGET_ARCH}"
|
||||
)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: xworkspace-console-runtime-linux-${{ matrix.arch }}
|
||||
path: |
|
||||
dist/assets/*.tar.gz
|
||||
dist/assets/SHA256SUMS-*
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Publish runtime release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: xworkspace-console-runtime-linux-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
- name: Publish assets
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="runtime-${GITHUB_SHA::12}"
|
||||
cat dist/SHA256SUMS-* | sort -u > dist/SHA256SUMS
|
||||
rm -f dist/SHA256SUMS-*
|
||||
if gh release view "${tag}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
|
||||
gh release upload "${tag}" dist/*.tar.gz dist/SHA256SUMS \
|
||||
--repo "${GITHUB_REPOSITORY}" --clobber
|
||||
else
|
||||
gh release create "${tag}" dist/*.tar.gz dist/SHA256SUMS \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
--target "${GITHUB_SHA}" \
|
||||
--title "XWorkspace Console runtime ${GITHUB_SHA::12}" \
|
||||
--notes "Prebuilt Linux runtime assets. No target-host build is required."
|
||||
fi
|
||||
|
||||
gh workflow run offline-package-ai-workspace-installer.yaml \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
-f "console_runtime_release_tag=${tag}"
|
||||
@ -454,13 +454,21 @@ ssh root@acp-bridge.onwalk.net \
|
||||
- 收口铁律:任一 Phase 2 产物在**被 Phase 3 消费前**必须 `finished`。
|
||||
- dpkg/全局 npm/全局 pip **绝不** `async`(§10.2)。
|
||||
|
||||
### 10.5 Shell 层 fork 并发(≤5,预取层)
|
||||
### 10.5 Shell 层动态 fork 并发(≤ CPU 核心数 × 2,预取层)
|
||||
|
||||
bootstrap 把可并行的外部 I/O 收敛到一个**有界 fork 池(上限 5)**,在 ansible 前(Phase 2 预取)与摘要阶段使用:
|
||||
bootstrap 把可并行的外部 I/O 收敛到一个**负载自适应的有界 fork 池**,在 ansible 前(Phase 2 预取)与摘要阶段使用。硬上限为目标主机在线 CPU 核心数的 2 倍;`AI_WORKSPACE_MAX_PARALLEL_JOBS` 可设更低人工上限,默认 `auto`。每次启动子任务前读取 1 分钟 load average,按 `min(人工上限, 2 × CPU - ceil(load1))` 动态收缩,最低保留 1 路:
|
||||
|
||||
```bash
|
||||
MAX_FORKS=5; pids=(); rc=0
|
||||
run_bounded(){ while [ "$(jobs -rp | wc -l)" -ge "$MAX_FORKS" ]; do wait -n; done; "$@" & pids+=($!); }
|
||||
CPU_COUNT="$(getconf _NPROCESSORS_ONLN)"
|
||||
HARD_LIMIT=$((CPU_COUNT * 2))
|
||||
LOAD_CEILING="$(awk -v load="$(cut -d' ' -f1 /proc/loadavg)" 'BEGIN { n=int(load); print load > n ? n + 1 : n }')"
|
||||
DYNAMIC_LIMIT=$((HARD_LIMIT - LOAD_CEILING))
|
||||
[ "$DYNAMIC_LIMIT" -ge 1 ] || DYNAMIC_LIMIT=1
|
||||
|
||||
run_bounded() {
|
||||
while [ "$(jobs -rp | wc -l)" -ge "$DYNAMIC_LIMIT" ]; do wait -n; done
|
||||
"$@" &
|
||||
}
|
||||
|
||||
# Phase 2 预取:5 仓库 pull + 二进制下载 + 镜像 pull(离线包存在则短路跳过)
|
||||
for r in playbooks console core-skills qmd litellm; do run_bounded fetch_repo "$r"; done
|
||||
@ -470,7 +478,7 @@ for p in "${pids[@]}"; do wait "$p" || rc=1; done
|
||||
[ "$rc" -eq 0 ] || { echo "[phase2] 存在失败子任务"; exit 1; }
|
||||
```
|
||||
|
||||
- 健康探测 fan-out(摘要前):对 Portal/Bridge/OpenClaw/QMD/Hermes/PG/Vault/LiteLLM 的 `systemctl is-active`+`curl` 并发≤5,统一汇总。
|
||||
- 健康探测 fan-out(摘要前):对 Portal/Bridge/OpenClaw/QMD/Hermes/PG/Vault/LiteLLM 的 `systemctl is-active`+`curl` 使用同一动态上限,统一按固定顺序汇总。
|
||||
- 每子进程带日志前缀(`[repo:qmd]`/`[bin:vault]`),失败非零退出、不静默。
|
||||
- 串行保留:`ansible-playbook` 主执行(Phase 1/Phase 3 由其内部保证)、一次性 token/摘要打印。
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
APT_DIR="${ROOT}/packages/apt"
|
||||
BIN_DIR="${ROOT}/packages/bin"
|
||||
COMPONENT_DIR="${ROOT}/packages/components"
|
||||
IMAGE_DIR="${ROOT}/packages/images"
|
||||
NPM_CACHE_DIR="${ROOT}/packages/npm-cache"
|
||||
NPM_RUNTIME_CACHE_DIR="${AI_WORKSPACE_NPM_CACHE_DIR:-/var/cache/ai-workspace/npm}"
|
||||
@ -134,18 +135,12 @@ EOF
|
||||
}
|
||||
|
||||
configure_local_git_sources() {
|
||||
local repo git_dir
|
||||
for repo in \
|
||||
"${ROOT}/repos/xworkspace-console" \
|
||||
"${ROOT}/repos/qmd" \
|
||||
"${ROOT}/repos/litellm"; do
|
||||
git_dir="${repo}/.git"
|
||||
[ -d "${git_dir}" ] || continue
|
||||
if ! git config --system --get-all safe.directory 2>/dev/null | grep -Fxq "${git_dir}"; then
|
||||
git config --system --add safe.directory "${git_dir}"
|
||||
SAFE_GIT_DIRS+=("${git_dir}")
|
||||
fi
|
||||
done
|
||||
local git_dir="${ROOT}/repos/xworkspace-console/.git"
|
||||
[ -d "${git_dir}" ] || return
|
||||
if ! git config --system --get-all safe.directory 2>/dev/null | grep -Fxq "${git_dir}"; then
|
||||
git config --system --add safe.directory "${git_dir}"
|
||||
SAFE_GIT_DIRS+=("${git_dir}")
|
||||
fi
|
||||
}
|
||||
|
||||
append_available_package() {
|
||||
@ -369,11 +364,16 @@ configure_language_package_caches() {
|
||||
info "Configuring pip to prefer bundled wheelhouse"
|
||||
export PIP_FIND_LINKS="${PIP_WHEEL_DIR}"
|
||||
export PIP_PREFER_BINARY=true
|
||||
export PIP_NO_INDEX=true
|
||||
if command -v python3 >/dev/null 2>&1 &&
|
||||
python3 -m pip --version >/dev/null 2>&1; then
|
||||
python3 -m pip config --global set global.find-links "${PIP_WHEEL_DIR}"
|
||||
python3 -m pip config --global set global.prefer-binary true
|
||||
python3 -m pip config --global set global.no-index true
|
||||
fi
|
||||
elif [ "${AI_WORKSPACE_PREBUILT_COMPONENTS_REQUIRED:-true}" = "true" ]; then
|
||||
echo "Bundled LiteLLM wheelhouse is required but missing: ${PIP_WHEEL_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
@ -385,18 +385,22 @@ run_bootstrap() {
|
||||
|
||||
export PLAYBOOK_DIR="${PLAYBOOK_DIR:-${ROOT}/repos/playbooks}"
|
||||
export XWORKSPACE_CONSOLE_DIR="${XWORKSPACE_CONSOLE_DIR:-${ROOT}/repos/xworkspace-console}"
|
||||
export XWORKSPACE_CONSOLE_SOURCE_REPO="${XWORKSPACE_CONSOLE_SOURCE_REPO:-file://${ROOT}/repos/xworkspace-console}"
|
||||
export XWORKSPACE_CONSOLE_SOURCE_VERSION="${XWORKSPACE_CONSOLE_SOURCE_VERSION:-$(cat "${ROOT}/metadata/xworkspace-console.commit")}"
|
||||
export XWORKSPACE_CORE_SKILLS_DIR="${XWORKSPACE_CORE_SKILLS_DIR:-${ROOT}/repos/xworkspace-core-skills}"
|
||||
export XWORKMATE_BRIDGE_SOURCE_DIR="${XWORKMATE_BRIDGE_SOURCE_DIR:-${ROOT}/repos/xworkmate-bridge}"
|
||||
export QMD_SOURCE_REPO="${QMD_SOURCE_REPO:-file://${ROOT}/repos/qmd}"
|
||||
export LITELLM_SOURCE_REPO="${LITELLM_SOURCE_REPO:-file://${ROOT}/repos/litellm}"
|
||||
export XWORKSPACE_CONSOLE_RUNTIME_ARCHIVE="${XWORKSPACE_CONSOLE_RUNTIME_ARCHIVE:-${COMPONENT_DIR}/xworkspace-console-runtime.tar.gz}"
|
||||
export QMD_RUNTIME_ARCHIVE="${QMD_RUNTIME_ARCHIVE:-${COMPONENT_DIR}/qmd-runtime.tar.gz}"
|
||||
if [ -f "${ROOT}/metadata/litellm-runtime.env" ]; then
|
||||
# shellcheck disable=SC1091
|
||||
source "${ROOT}/metadata/litellm-runtime.env"
|
||||
export LITELLM_PACKAGE_SPEC
|
||||
fi
|
||||
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}"
|
||||
export VAULT_PUBLIC_ACCESS="${VAULT_PUBLIC_ACCESS:-false}"
|
||||
export AI_WORKSPACE_OFFLINE_ACTIVE=true
|
||||
export AI_WORKSPACE_PREBUILT_COMPONENTS_REQUIRED=true
|
||||
export AI_WORKSPACE_USE_PREBUILT_BRIDGE=true
|
||||
export AI_WORKSPACE_RUNTIME_PREBUILD_ENABLED=false
|
||||
export AI_WORKSPACE_DEPLOYMENT_LOCK_HELD=true
|
||||
export AI_WORKSPACE_APT_LOCK_TIMEOUT
|
||||
|
||||
|
||||
@ -12,13 +12,14 @@ CONSOLE_REPO="${CONSOLE_REPO:-https://github.com/ai-workspace-lab/xworkspace-con
|
||||
CONSOLE_REF="${CONSOLE_REF:-main}"
|
||||
CORE_SKILLS_REPO="${CORE_SKILLS_REPO:-https://github.com/ai-workspace-lab/xworkspace-core-skills.git}"
|
||||
CORE_SKILLS_REF="${CORE_SKILLS_REF:-main}"
|
||||
XWORKMATE_BRIDGE_REPO="${XWORKMATE_BRIDGE_REPO:-https://github.com/ai-workspace-lab/xworkmate-bridge.git}"
|
||||
XWORKMATE_BRIDGE_REF="${XWORKMATE_BRIDGE_REF:-release/v1.1.4}"
|
||||
QMD_REPO="${QMD_REPO:-https://github.com/ai-workspace-services/qmd.git}"
|
||||
QMD_REF="${QMD_REF:-main}"
|
||||
LITELLM_REPO="${LITELLM_REPO:-https://github.com/ai-workspace-services/litellm.git}"
|
||||
LITELLM_REF="${LITELLM_REF:-litellm_internal_staging}"
|
||||
LITELLM_DEBIAN_11_VERSION="${LITELLM_DEBIAN_11_VERSION:-1.74.9}"
|
||||
CONSOLE_RUNTIME_RELEASE_REPO="${CONSOLE_RUNTIME_RELEASE_REPO:-ai-workspace-lab/xworkspace-console}"
|
||||
CONSOLE_RUNTIME_RELEASE_TAG="${CONSOLE_RUNTIME_RELEASE_TAG:-latest-runtime}"
|
||||
BRIDGE_RUNTIME_RELEASE_REPO="${BRIDGE_RUNTIME_RELEASE_REPO:-ai-workspace-lab/xworkmate-bridge}"
|
||||
BRIDGE_RUNTIME_RELEASE_TAG="${BRIDGE_RUNTIME_RELEASE_TAG:-latest-runtime}"
|
||||
QMD_RUNTIME_RELEASE_REPO="${QMD_RUNTIME_RELEASE_REPO:-ai-workspace-services/qmd}"
|
||||
QMD_RUNTIME_RELEASE_TAG="${QMD_RUNTIME_RELEASE_TAG:-latest-runtime}"
|
||||
LITELLM_RUNTIME_RELEASE_REPO="${LITELLM_RUNTIME_RELEASE_REPO:-ai-workspace-services/litellm}"
|
||||
LITELLM_RUNTIME_RELEASE_TAG="${LITELLM_RUNTIME_RELEASE_TAG:-latest-runtime}"
|
||||
|
||||
NODEJS_MAJOR_VERSIONS="${NODEJS_MAJOR_VERSIONS:-22 24}"
|
||||
NODEJS_22_VERSION="${NODEJS_22_VERSION:-22.22.3}"
|
||||
@ -29,8 +30,6 @@ POSTGRES_IMAGE="${POSTGRES_IMAGE:-postgres:17.7}"
|
||||
OPENCLAW_VERSION="${OPENCLAW_VERSION:-2026.6.6}"
|
||||
PLAYWRIGHT_VERSION="${PLAYWRIGHT_VERSION:-1.60.0}"
|
||||
GOOGLE_CHROME_VERSION="${GOOGLE_CHROME_VERSION:-149.0.7827.114-1}"
|
||||
UV_VERSION="${UV_VERSION:-0.11.21}"
|
||||
PORTABLE_PYTHON_VERSION="${PORTABLE_PYTHON_VERSION:-3.13.14}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
@ -77,15 +76,100 @@ clone_repo() {
|
||||
git -C "${dest}" rev-parse HEAD
|
||||
}
|
||||
|
||||
github_api() {
|
||||
local path=$1
|
||||
local headers=(-H "Accept: application/vnd.github+json")
|
||||
if [ -n "${GH_TOKEN:-${GITHUB_TOKEN:-}}" ]; then
|
||||
headers+=(-H "Authorization: Bearer ${GH_TOKEN:-${GITHUB_TOKEN}}")
|
||||
fi
|
||||
curl -fsSL --retry 5 --retry-all-errors "${headers[@]}" "https://api.github.com${path}"
|
||||
}
|
||||
|
||||
resolve_runtime_tag() {
|
||||
local repo=$1
|
||||
local requested=$2
|
||||
local attempt tag=""
|
||||
if [ "${requested}" != "latest-runtime" ]; then
|
||||
printf '%s\n' "${requested}"
|
||||
return
|
||||
fi
|
||||
for attempt in $(seq 1 40); do
|
||||
tag="$(
|
||||
github_api "/repos/${repo}/releases?per_page=100" |
|
||||
jq -r '[.[] | select(.draft == false and (.tag_name | startswith("runtime-")))] | first | .tag_name // empty'
|
||||
)"
|
||||
if [ -n "${tag}" ]; then
|
||||
printf '%s\n' "${tag}"
|
||||
return
|
||||
fi
|
||||
echo "Waiting for the first runtime release from ${repo} (${attempt}/40)..." >&2
|
||||
sleep 30
|
||||
done
|
||||
}
|
||||
|
||||
download_release_asset() {
|
||||
local repo=$1 requested_tag=$2 asset=$3 destination=$4 metadata_name=$5
|
||||
local tag release_json asset_url checksums_url expected actual
|
||||
tag="$(resolve_runtime_tag "${repo}" "${requested_tag}")"
|
||||
[ -n "${tag}" ] || { echo "No runtime release found for ${repo}" >&2; exit 1; }
|
||||
release_json="$(github_api "/repos/${repo}/releases/tags/${tag}")"
|
||||
asset_url="$(jq -r --arg name "${asset}" '.assets[] | select(.name == $name) | .browser_download_url' <<<"${release_json}")"
|
||||
checksums_url="$(jq -r '.assets[] | select(.name == "SHA256SUMS") | .browser_download_url' <<<"${release_json}")"
|
||||
[ -n "${asset_url}" ] && [ "${asset_url}" != "null" ] ||
|
||||
{ echo "Release asset ${asset} is missing from ${repo}@${tag}" >&2; exit 1; }
|
||||
[ -n "${checksums_url}" ] && [ "${checksums_url}" != "null" ] ||
|
||||
{ echo "SHA256SUMS is missing from ${repo}@${tag}" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$(dirname "${destination}")" "${WORKDIR}/metadata/components"
|
||||
curl -fsSL --retry 5 --retry-all-errors "${asset_url}" -o "${destination}"
|
||||
curl -fsSL --retry 5 --retry-all-errors "${checksums_url}" \
|
||||
-o "${WORKDIR}/metadata/components/${metadata_name}.SHA256SUMS"
|
||||
expected="$(awk -v file="${asset}" '$2 == file || $2 == "*" file { print $1; exit }' \
|
||||
"${WORKDIR}/metadata/components/${metadata_name}.SHA256SUMS")"
|
||||
actual="$(sha256sum "${destination}" | awk '{print $1}')"
|
||||
[ -n "${expected}" ] && [ "${expected}" = "${actual}" ] ||
|
||||
{ echo "Checksum mismatch or missing checksum for ${repo}@${tag}/${asset}" >&2; exit 1; }
|
||||
printf '%s\n' "${tag}" > "${WORKDIR}/metadata/components/${metadata_name}.tag"
|
||||
printf '%s\n' "${actual}" > "${WORKDIR}/metadata/components/${metadata_name}.sha256"
|
||||
}
|
||||
|
||||
download_component_releases() {
|
||||
local component_dir="${WORKDIR}/packages/components"
|
||||
local bridge_tmp="${WORKDIR}/.bridge-runtime"
|
||||
local litellm_tmp="${WORKDIR}/.litellm-runtime"
|
||||
local console_asset="xworkspace-console-runtime-linux-${ARCH}.tar.gz"
|
||||
local bridge_asset="xworkmate-bridge-linux-${ARCH}.tar.gz"
|
||||
local qmd_asset="qmd-runtime-linux-${ARCH}.tar.gz"
|
||||
local litellm_asset="litellm-runtime-${DISTRO_ID}-${DISTRO_VERSION}-${ARCH}.tar.gz"
|
||||
|
||||
mkdir -p "${component_dir}" "${bridge_tmp}" "${litellm_tmp}" "${WORKDIR}/packages/bin"
|
||||
download_release_asset "${CONSOLE_RUNTIME_RELEASE_REPO}" "${CONSOLE_RUNTIME_RELEASE_TAG}" \
|
||||
"${console_asset}" "${component_dir}/xworkspace-console-runtime.tar.gz" xworkspace-console
|
||||
download_release_asset "${BRIDGE_RUNTIME_RELEASE_REPO}" "${BRIDGE_RUNTIME_RELEASE_TAG}" \
|
||||
"${bridge_asset}" "${bridge_tmp}/${bridge_asset}" xworkmate-bridge
|
||||
download_release_asset "${QMD_RUNTIME_RELEASE_REPO}" "${QMD_RUNTIME_RELEASE_TAG}" \
|
||||
"${qmd_asset}" "${component_dir}/qmd-runtime.tar.gz" qmd
|
||||
download_release_asset "${LITELLM_RUNTIME_RELEASE_REPO}" "${LITELLM_RUNTIME_RELEASE_TAG}" \
|
||||
"${litellm_asset}" "${litellm_tmp}/${litellm_asset}" litellm
|
||||
|
||||
tar -xzf "${bridge_tmp}/${bridge_asset}" -C "${bridge_tmp}"
|
||||
install -m 0755 "${bridge_tmp}/xworkmate-bridge/bin/xworkmate-go-core" \
|
||||
"${WORKDIR}/packages/bin/xworkmate-go-core.${ARCH}"
|
||||
tar -xzf "${litellm_tmp}/${litellm_asset}" -C "${litellm_tmp}"
|
||||
cp -a "${litellm_tmp}/litellm-runtime/packages/pip" "${WORKDIR}/packages/"
|
||||
if [ -d "${litellm_tmp}/litellm-runtime/packages/python" ]; then
|
||||
cp -a "${litellm_tmp}/litellm-runtime/packages/python" "${WORKDIR}/packages/"
|
||||
fi
|
||||
cp "${litellm_tmp}/litellm-runtime/metadata/runtime.env" \
|
||||
"${WORKDIR}/metadata/litellm-runtime.env"
|
||||
}
|
||||
|
||||
download_apt_packages() {
|
||||
local image=$1
|
||||
local platform="linux/${ARCH}"
|
||||
local apt_dir="${WORKDIR}/packages/apt"
|
||||
local apt_lists="${WORKDIR}/metadata/apt"
|
||||
local wheel_dir="${WORKDIR}/packages/pip"
|
||||
local python_dir="${WORKDIR}/packages/python"
|
||||
|
||||
mkdir -p "${apt_dir}" "${apt_lists}" "${wheel_dir}" "${python_dir}"
|
||||
mkdir -p "${apt_dir}" "${apt_lists}"
|
||||
|
||||
docker run --rm --platform "${platform}" \
|
||||
-e DISTRO_ID="${DISTRO_ID}" \
|
||||
@ -94,14 +178,8 @@ download_apt_packages() {
|
||||
-e NODEJS_22_VERSION="${NODEJS_22_VERSION}" \
|
||||
-e NODEJS_24_VERSION="${NODEJS_24_VERSION}" \
|
||||
-e GOOGLE_CHROME_VERSION="${GOOGLE_CHROME_VERSION}" \
|
||||
-e LITELLM_DEBIAN_11_VERSION="${LITELLM_DEBIAN_11_VERSION}" \
|
||||
-e UV_VERSION="${UV_VERSION}" \
|
||||
-e PORTABLE_PYTHON_VERSION="${PORTABLE_PYTHON_VERSION}" \
|
||||
-v "${apt_dir}:/offline-apt" \
|
||||
-v "${apt_lists}:/offline-meta" \
|
||||
-v "${wheel_dir}:/offline-pip" \
|
||||
-v "${python_dir}:/offline-python" \
|
||||
-v "${WORKDIR}/repos/litellm:/litellm-src:ro" \
|
||||
"${image}" \
|
||||
bash -lc "$(cat <<'CONTAINER'
|
||||
set -euo pipefail
|
||||
@ -253,40 +331,6 @@ cd /offline-apt
|
||||
dpkg-scanpackages --multiversion . /dev/null | gzip -9c > Packages.gz
|
||||
find . -maxdepth 1 -name '*.deb' -printf '%f\n' | sort > /offline-meta/deb-files.txt
|
||||
|
||||
retry_command apt-get install -y --no-install-recommends \
|
||||
build-essential git libpq-dev python3 python3-dev python3-pip python3-venv
|
||||
python_bin=python3
|
||||
if [ "${DISTRO_ID}:${DISTRO_VERSION}" = "ubuntu:26.04" ]; then
|
||||
case "$(dpkg --print-architecture)" in
|
||||
amd64) uv_arch=x86_64 ;;
|
||||
arm64) uv_arch=aarch64 ;;
|
||||
*) echo "Unsupported uv architecture: $(dpkg --print-architecture)" >&2; exit 1 ;;
|
||||
esac
|
||||
curl -fsSL \
|
||||
"https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${uv_arch}-unknown-linux-gnu.tar.gz" \
|
||||
-o /tmp/uv.tar.gz
|
||||
tar -xzf /tmp/uv.tar.gz -C /tmp
|
||||
install -m 0755 "$(find /tmp -type f -path '*/uv-*/uv' -print -quit)" /usr/local/bin/uv
|
||||
uv python install "${PORTABLE_PYTHON_VERSION}" --install-dir /offline-python --no-bin
|
||||
python_bin="$(find -L /offline-python -type f -path '*/bin/python3.13' -perm /111 -print -quit)"
|
||||
if [ -z "${python_bin}" ]; then
|
||||
echo "Portable Python ${PORTABLE_PYTHON_VERSION} was not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
find /offline-python -name EXTERNALLY-MANAGED -delete
|
||||
"${python_bin}" -m ensurepip --upgrade
|
||||
fi
|
||||
"${python_bin}" -m venv /tmp/ai-workspace-wheel-builder
|
||||
/tmp/ai-workspace-wheel-builder/bin/pip install --upgrade pip setuptools wheel
|
||||
litellm_package_spec="/litellm-src[proxy]"
|
||||
if [ "${DISTRO_ID}:${DISTRO_VERSION}" = "debian:11" ]; then
|
||||
litellm_package_spec="litellm[proxy]==${LITELLM_DEBIAN_11_VERSION}"
|
||||
fi
|
||||
/tmp/ai-workspace-wheel-builder/bin/pip wheel \
|
||||
--wheel-dir /offline-pip \
|
||||
"${litellm_package_spec}" \
|
||||
prisma \
|
||||
psycopg2-binary
|
||||
CONTAINER
|
||||
)"
|
||||
}
|
||||
@ -298,8 +342,6 @@ warm_npm_dependency_cache() {
|
||||
-e OPENCLAW_VERSION="${OPENCLAW_VERSION}" \
|
||||
-e PLAYWRIGHT_VERSION="${PLAYWRIGHT_VERSION}" \
|
||||
-v "${npm_cache_dir}:/offline-cache" \
|
||||
-v "${WORKDIR}/repos/xworkspace-console/dashboard:/sources/dashboard:ro" \
|
||||
-v "${WORKDIR}/repos/qmd:/sources/qmd:ro" \
|
||||
node:24-bookworm \
|
||||
bash -lc "$(cat <<'CONTAINER'
|
||||
set -euo pipefail
|
||||
@ -307,19 +349,6 @@ export npm_config_cache=/offline-cache
|
||||
export npm_config_audit=false
|
||||
export npm_config_fund=false
|
||||
|
||||
mkdir -p /tmp/projects
|
||||
cp -a /sources/dashboard /tmp/projects/dashboard
|
||||
cp -a /sources/qmd /tmp/projects/qmd
|
||||
|
||||
(
|
||||
cd /tmp/projects/dashboard
|
||||
npm ci --ignore-scripts --no-audit --no-fund
|
||||
)
|
||||
(
|
||||
cd /tmp/projects/qmd
|
||||
npm install --ignore-scripts --no-audit --no-fund --package-lock=false
|
||||
)
|
||||
|
||||
npm install --ignore-scripts --no-audit --no-fund --prefix /tmp/global \
|
||||
opencode-ai \
|
||||
@google/gemini-cli \
|
||||
@ -379,19 +408,6 @@ download_binaries() {
|
||||
chmod +x "${bin_dir}/ttyd.${ttyd_arch}"
|
||||
}
|
||||
|
||||
build_xworkmate_bridge_binary() {
|
||||
local bin_dir="${WORKDIR}/packages/bin"
|
||||
mkdir -p "${bin_dir}"
|
||||
docker run --rm --platform "linux/${ARCH}" \
|
||||
-e GOOS=linux \
|
||||
-e GOARCH="${ARCH}" \
|
||||
-e CGO_ENABLED=0 \
|
||||
-v "${WORKDIR}/repos/xworkmate-bridge:/src:ro" \
|
||||
-v "${bin_dir}:/out" \
|
||||
golang:1.25-bookworm \
|
||||
bash -lc 'cd /src && /usr/local/go/bin/go build -buildvcs=false -trimpath -o "/out/xworkmate-go-core.${GOARCH}" .'
|
||||
}
|
||||
|
||||
export_container_images() {
|
||||
local images_dir="${WORKDIR}/packages/images"
|
||||
mkdir -p "${images_dir}"
|
||||
@ -419,19 +435,19 @@ EOF
|
||||
"sources": {
|
||||
"playbooks": {"repo": "${PLAYBOOKS_REPO}", "ref": "${PLAYBOOKS_REF}"},
|
||||
"xworkspaceConsole": {"repo": "${CONSOLE_REPO}", "ref": "${CONSOLE_REF}"},
|
||||
"xworkspaceCoreSkills": {"repo": "${CORE_SKILLS_REPO}", "ref": "${CORE_SKILLS_REF}"},
|
||||
"xworkmateBridge": {"repo": "${XWORKMATE_BRIDGE_REPO}", "ref": "${XWORKMATE_BRIDGE_REF}"},
|
||||
"qmd": {"repo": "${QMD_REPO}", "ref": "${QMD_REF}"},
|
||||
"litellm": {"repo": "${LITELLM_REPO}", "ref": "${LITELLM_REF}"}
|
||||
"xworkspaceCoreSkills": {"repo": "${CORE_SKILLS_REPO}", "ref": "${CORE_SKILLS_REF}"}
|
||||
},
|
||||
"componentReleases": {
|
||||
"xworkspaceConsole": {"repo": "${CONSOLE_RUNTIME_RELEASE_REPO}", "tag": "$(cat "${WORKDIR}/metadata/components/xworkspace-console.tag")"},
|
||||
"xworkmateBridge": {"repo": "${BRIDGE_RUNTIME_RELEASE_REPO}", "tag": "$(cat "${WORKDIR}/metadata/components/xworkmate-bridge.tag")"},
|
||||
"qmd": {"repo": "${QMD_RUNTIME_RELEASE_REPO}", "tag": "$(cat "${WORKDIR}/metadata/components/qmd.tag")"},
|
||||
"litellm": {"repo": "${LITELLM_RUNTIME_RELEASE_REPO}", "tag": "$(cat "${WORKDIR}/metadata/components/litellm.tag")"}
|
||||
},
|
||||
"versions": {
|
||||
"vault": "${VAULT_VERSION}",
|
||||
"openclaw": "${OPENCLAW_VERSION}",
|
||||
"playwright": "${PLAYWRIGHT_VERSION}",
|
||||
"googleChrome": "${GOOGLE_CHROME_VERSION}",
|
||||
"litellmDebian11": "${LITELLM_DEBIAN_11_VERSION}",
|
||||
"uv": "${UV_VERSION}",
|
||||
"portablePython": "${PORTABLE_PYTHON_VERSION}",
|
||||
"postgresImage": "${POSTGRES_IMAGE}"
|
||||
}
|
||||
}
|
||||
@ -451,15 +467,11 @@ main() {
|
||||
clone_repo "${PLAYBOOKS_REPO}" "${PLAYBOOKS_REF}" "${WORKDIR}/repos/playbooks" > "${WORKDIR}/metadata/playbooks.commit"
|
||||
clone_repo "${CONSOLE_REPO}" "${CONSOLE_REF}" "${WORKDIR}/repos/xworkspace-console" > "${WORKDIR}/metadata/xworkspace-console.commit"
|
||||
clone_repo "${CORE_SKILLS_REPO}" "${CORE_SKILLS_REF}" "${WORKDIR}/repos/xworkspace-core-skills" > "${WORKDIR}/metadata/xworkspace-core-skills.commit"
|
||||
clone_repo "${XWORKMATE_BRIDGE_REPO}" "${XWORKMATE_BRIDGE_REF}" "${WORKDIR}/repos/xworkmate-bridge" > "${WORKDIR}/metadata/xworkmate-bridge.commit"
|
||||
clone_repo "${QMD_REPO}" "${QMD_REF}" "${WORKDIR}/repos/qmd" > "${WORKDIR}/metadata/qmd.commit"
|
||||
clone_repo "${LITELLM_REPO}" "${LITELLM_REF}" "${WORKDIR}/repos/litellm" > "${WORKDIR}/metadata/litellm.commit"
|
||||
|
||||
download_apt_packages "${image}"
|
||||
download_component_releases
|
||||
warm_npm_dependency_cache
|
||||
download_playwright_browser
|
||||
download_binaries
|
||||
build_xworkmate_bridge_binary
|
||||
export_container_images
|
||||
|
||||
cp "${SCRIPT_DIR}/ai-workspace-offline-install.sh" "${WORKDIR}/scripts/"
|
||||
|
||||
@ -26,7 +26,8 @@ set -euo pipefail
|
||||
# XWORKSPACE_CONSOLE_DIR (optional local xworkspace-console checkout for macOS)
|
||||
# XWORKSPACE_CONSOLE_SOURCE_REPO / XWORKSPACE_CONSOLE_SOURCE_VERSION
|
||||
# (optional Git source used by the Linux console playbook)
|
||||
# QMD_SOURCE_REPO / LITELLM_SOURCE_REPO (optional local git sources for offline installs)
|
||||
# XWORKSPACE_CONSOLE_RUNTIME_ARCHIVE / QMD_RUNTIME_ARCHIVE
|
||||
# LITELLM_PACKAGE_SPEC / AI_WORKSPACE_PREBUILT_COMPONENTS_REQUIRED
|
||||
# AI_WORKSPACE_OFFLINE_MODE=auto (default) | force | off
|
||||
# AI_WORKSPACE_OFFLINE_PACKAGE (local tarball/directory or URL)
|
||||
# AI_WORKSPACE_OFFLINE_PACKAGE_URL (direct tarball URL)
|
||||
@ -37,6 +38,11 @@ set -euo pipefail
|
||||
# AI_WORKSPACE_OFFLINE_WORK_DIR=/tmp/ai-workspace-offline
|
||||
# AI_WORKSPACE_DEPLOYMENT_LOCK_TIMEOUT=1800
|
||||
# AI_WORKSPACE_APT_LOCK_TIMEOUT=900
|
||||
# AI_WORKSPACE_PREFETCH_ENABLED=true
|
||||
# AI_WORKSPACE_MAX_PARALLEL_JOBS=auto (never exceeds 2 x online CPU cores)
|
||||
# AI_WORKSPACE_PREFETCH_DIR=/var/tmp/ai-workspace-prefetch
|
||||
# AI_WORKSPACE_SPLIT_PHASES=true
|
||||
# AI_WORKSPACE_RUNTIME_PREBUILD_ENABLED=false
|
||||
# AI_WORKSPACE_DARWIN_MODE=local (default on macOS) | ansible
|
||||
# ==============================================================================
|
||||
|
||||
@ -70,6 +76,15 @@ if [ -z "${AI_WORKSPACE_OFFLINE_WORK_DIR:-}" ]; then
|
||||
fi
|
||||
AI_WORKSPACE_DEPLOYMENT_LOCK_TIMEOUT=${AI_WORKSPACE_DEPLOYMENT_LOCK_TIMEOUT:-"1800"}
|
||||
AI_WORKSPACE_APT_LOCK_TIMEOUT=${AI_WORKSPACE_APT_LOCK_TIMEOUT:-"900"}
|
||||
AI_WORKSPACE_PREFETCH_ENABLED=${AI_WORKSPACE_PREFETCH_ENABLED:-"true"}
|
||||
AI_WORKSPACE_MAX_PARALLEL_JOBS=${AI_WORKSPACE_MAX_PARALLEL_JOBS:-"auto"}
|
||||
AI_WORKSPACE_PREFETCH_DIR=${AI_WORKSPACE_PREFETCH_DIR:-"/var/tmp/ai-workspace-prefetch"}
|
||||
AI_WORKSPACE_SPLIT_PHASES=${AI_WORKSPACE_SPLIT_PHASES:-"true"}
|
||||
AI_WORKSPACE_RUNTIME_PREBUILD_ENABLED=${AI_WORKSPACE_RUNTIME_PREBUILD_ENABLED:-"false"}
|
||||
BOUNDED_JOB_PIDS=()
|
||||
BOUNDED_JOB_LABELS=()
|
||||
BOUNDED_JOB_FAILED=0
|
||||
PARALLEL_LIMIT_WARNING_EMITTED=false
|
||||
|
||||
# Function: Output messages
|
||||
info() {
|
||||
@ -86,6 +101,120 @@ error() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
reset_bounded_jobs() {
|
||||
BOUNDED_JOB_PIDS=()
|
||||
BOUNDED_JOB_LABELS=()
|
||||
BOUNDED_JOB_FAILED=0
|
||||
}
|
||||
|
||||
validate_parallel_job_limit() {
|
||||
case "$AI_WORKSPACE_MAX_PARALLEL_JOBS" in
|
||||
auto) ;;
|
||||
''|*[!0-9]*|0) error "AI_WORKSPACE_MAX_PARALLEL_JOBS must be auto or a positive integer." ;;
|
||||
esac
|
||||
}
|
||||
|
||||
online_cpu_count() {
|
||||
local count=""
|
||||
if command -v getconf >/dev/null 2>&1; then
|
||||
count="$(getconf _NPROCESSORS_ONLN 2>/dev/null || true)"
|
||||
fi
|
||||
if [ -z "$count" ] && command -v nproc >/dev/null 2>&1; then
|
||||
count="$(nproc 2>/dev/null || true)"
|
||||
fi
|
||||
if [ -z "$count" ] && command -v sysctl >/dev/null 2>&1; then
|
||||
count="$(sysctl -n hw.logicalcpu 2>/dev/null || true)"
|
||||
fi
|
||||
case "$count" in
|
||||
''|*[!0-9]*|0) count=1 ;;
|
||||
esac
|
||||
printf '%s\n' "$count"
|
||||
}
|
||||
|
||||
one_minute_load_average() {
|
||||
if [ -r /proc/loadavg ]; then
|
||||
awk '{print $1}' /proc/loadavg
|
||||
return
|
||||
fi
|
||||
if command -v sysctl >/dev/null 2>&1; then
|
||||
sysctl -n vm.loadavg 2>/dev/null | awk '{gsub(/[{}]/, ""); print $1}'
|
||||
return
|
||||
fi
|
||||
printf '0\n'
|
||||
}
|
||||
|
||||
dynamic_parallel_job_limit() {
|
||||
local cpu_count hard_limit configured_limit load_average load_ceiling dynamic_limit
|
||||
cpu_count="$(online_cpu_count)"
|
||||
hard_limit=$((cpu_count * 2))
|
||||
configured_limit="$hard_limit"
|
||||
if [ "$AI_WORKSPACE_MAX_PARALLEL_JOBS" != "auto" ]; then
|
||||
configured_limit="$AI_WORKSPACE_MAX_PARALLEL_JOBS"
|
||||
if [ "$configured_limit" -gt "$hard_limit" ]; then
|
||||
configured_limit="$hard_limit"
|
||||
if [ "$PARALLEL_LIMIT_WARNING_EMITTED" = "false" ]; then
|
||||
warn "Parallel job limit was capped at ${hard_limit} (2 x ${cpu_count} online CPU cores)."
|
||||
PARALLEL_LIMIT_WARNING_EMITTED=true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
load_average="$(one_minute_load_average)"
|
||||
load_ceiling="$(awk -v load="$load_average" 'BEGIN { value=int(load); if (load > value) value++; print value }')"
|
||||
dynamic_limit=$((hard_limit - load_ceiling))
|
||||
if [ "$dynamic_limit" -lt 1 ]; then
|
||||
dynamic_limit=1
|
||||
fi
|
||||
if [ "$dynamic_limit" -gt "$configured_limit" ]; then
|
||||
dynamic_limit="$configured_limit"
|
||||
fi
|
||||
printf '%s\n' "$dynamic_limit"
|
||||
}
|
||||
|
||||
wait_for_bounded_job() {
|
||||
local index=$1
|
||||
local pid="${BOUNDED_JOB_PIDS[$index]}"
|
||||
local label="${BOUNDED_JOB_LABELS[$index]}"
|
||||
|
||||
if wait "$pid"; then
|
||||
info "Parallel job completed: $label"
|
||||
else
|
||||
warn "Parallel job failed: $label"
|
||||
BOUNDED_JOB_FAILED=1
|
||||
fi
|
||||
unset 'BOUNDED_JOB_PIDS[index]'
|
||||
unset 'BOUNDED_JOB_LABELS[index]'
|
||||
BOUNDED_JOB_PIDS=("${BOUNDED_JOB_PIDS[@]}")
|
||||
BOUNDED_JOB_LABELS=("${BOUNDED_JOB_LABELS[@]}")
|
||||
}
|
||||
|
||||
run_bounded() {
|
||||
local label=$1
|
||||
local dynamic_limit
|
||||
shift
|
||||
|
||||
validate_parallel_job_limit
|
||||
dynamic_limit="$(dynamic_parallel_job_limit)"
|
||||
while [ "${#BOUNDED_JOB_PIDS[@]}" -ge "$dynamic_limit" ]; do
|
||||
wait_for_bounded_job 0
|
||||
dynamic_limit="$(dynamic_parallel_job_limit)"
|
||||
done
|
||||
|
||||
(
|
||||
set -o pipefail
|
||||
"$@" 2>&1 | sed "s/^/[${label}] /"
|
||||
) &
|
||||
BOUNDED_JOB_PIDS+=("$!")
|
||||
BOUNDED_JOB_LABELS+=("$label")
|
||||
}
|
||||
|
||||
wait_for_bounded_jobs() {
|
||||
while [ "${#BOUNDED_JOB_PIDS[@]}" -gt 0 ]; do
|
||||
wait_for_bounded_job 0
|
||||
done
|
||||
[ "$BOUNDED_JOB_FAILED" -eq 0 ]
|
||||
}
|
||||
|
||||
mask_secret() {
|
||||
local val="${1:-}"
|
||||
if [ -z "$val" ]; then
|
||||
@ -984,7 +1113,10 @@ PY
|
||||
}
|
||||
|
||||
ensure_core_skills_source() {
|
||||
if [ "${AI_WORKSPACE_OFFLINE_ACTIVE:-false}" = "true" ] &&
|
||||
if [ "${AI_WORKSPACE_PREFETCH_COMPLETED:-false}" = "true" ] &&
|
||||
[ -d "$XWORKSPACE_CORE_SKILLS_DIR/skills" ]; then
|
||||
info "Using prefetched xworkspace-core-skills directory at $XWORKSPACE_CORE_SKILLS_DIR"
|
||||
elif [ "${AI_WORKSPACE_OFFLINE_ACTIVE:-false}" = "true" ] &&
|
||||
[ -d "$XWORKSPACE_CORE_SKILLS_DIR/skills" ]; then
|
||||
info "Using packaged xworkspace-core-skills directory at $XWORKSPACE_CORE_SKILLS_DIR"
|
||||
elif [ -d "$XWORKSPACE_CORE_SKILLS_DIR/.git" ]; then
|
||||
@ -1003,7 +1135,10 @@ ensure_core_skills_source() {
|
||||
}
|
||||
|
||||
ensure_xworkmate_bridge_source() {
|
||||
if [ "${AI_WORKSPACE_OFFLINE_ACTIVE:-false}" = "true" ] &&
|
||||
if [ "${AI_WORKSPACE_PREFETCH_COMPLETED:-false}" = "true" ] &&
|
||||
[ -f "$XWORKMATE_BRIDGE_SOURCE_DIR/go.mod" ]; then
|
||||
info "Using prefetched xworkmate-bridge source at $XWORKMATE_BRIDGE_SOURCE_DIR"
|
||||
elif [ "${AI_WORKSPACE_OFFLINE_ACTIVE:-false}" = "true" ] &&
|
||||
[ -f "$XWORKMATE_BRIDGE_SOURCE_DIR/go.mod" ]; then
|
||||
info "Using packaged xworkmate-bridge source at $XWORKMATE_BRIDGE_SOURCE_DIR"
|
||||
elif [ -d "$XWORKMATE_BRIDGE_SOURCE_DIR/.git" ]; then
|
||||
@ -1022,6 +1157,234 @@ ensure_xworkmate_bridge_source() {
|
||||
[ -f "$XWORKMATE_BRIDGE_SOURCE_DIR/go.mod" ] || error "xworkmate-bridge source missing: $XWORKMATE_BRIDGE_SOURCE_DIR/go.mod"
|
||||
}
|
||||
|
||||
read_playbook_default() {
|
||||
local file=$1
|
||||
local key=$2
|
||||
sed -n "s/^${key}:[[:space:]]*[\"']\\{0,1\\}\\([^\"']*\\)[\"']\\{0,1\\}[[:space:]]*$/\\1/p" "$file" | head -n 1
|
||||
}
|
||||
|
||||
prefetch_git_repository() {
|
||||
local label=$1
|
||||
local repo=$2
|
||||
local ref=$3
|
||||
local dest=$4
|
||||
|
||||
if [ -d "$dest/.git" ]; then
|
||||
git -C "$dest" remote set-url origin "$repo"
|
||||
git -C "$dest" fetch --force --prune origin "$ref"
|
||||
else
|
||||
rm -rf "$dest"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
git clone --no-checkout "$repo" "$dest"
|
||||
git -C "$dest" fetch --force origin "$ref"
|
||||
fi
|
||||
git -C "$dest" checkout --force --detach FETCH_HEAD
|
||||
git -C "$dest" clean -ffd
|
||||
printf '%s\n' "$(git -C "$dest" rev-parse HEAD)" > "$dest/.ai-workspace-prefetched-commit"
|
||||
info "Prefetched $label at $(cat "$dest/.ai-workspace-prefetched-commit")"
|
||||
}
|
||||
|
||||
prefetch_postgres_image() {
|
||||
local image=$1
|
||||
docker pull "$image"
|
||||
}
|
||||
|
||||
prefetch_independent_sources() {
|
||||
if [ "$AI_WORKSPACE_PREFETCH_ENABLED" != "true" ]; then
|
||||
info "Phase 2 prefetch disabled by AI_WORKSPACE_PREFETCH_ENABLED."
|
||||
return
|
||||
fi
|
||||
if [ "${AI_WORKSPACE_OFFLINE_ACTIVE:-false}" = "true" ]; then
|
||||
info "Offline package is active; skipping online Phase 2 prefetch."
|
||||
return
|
||||
fi
|
||||
if [ "$(detect_os)" != "linux" ]; then
|
||||
return
|
||||
fi
|
||||
validate_parallel_job_limit
|
||||
|
||||
local console_dir="$AI_WORKSPACE_PREFETCH_DIR/xworkspace-console"
|
||||
local qmd_dir="$AI_WORKSPACE_PREFETCH_DIR/qmd"
|
||||
local litellm_dir="$AI_WORKSPACE_PREFETCH_DIR/litellm"
|
||||
local qmd_repo qmd_ref litellm_repo litellm_ref postgres_image
|
||||
qmd_repo="${QMD_SOURCE_REPO:-$(read_playbook_default roles/vhosts/qmd/defaults/main.yml qmd_source_repo)}"
|
||||
qmd_ref="${QMD_VERSION:-$(read_playbook_default roles/vhosts/qmd/defaults/main.yml qmd_version)}"
|
||||
litellm_repo="${LITELLM_SOURCE_REPO:-$(read_playbook_default roles/vhosts/litellm/defaults/main.yml litellm_source_repo)}"
|
||||
litellm_ref="${LITELLM_VERSION:-$(read_playbook_default roles/vhosts/litellm/defaults/main.yml litellm_version)}"
|
||||
postgres_image="${POSTGRESQL_IMAGE:-$(read_playbook_default roles/vhosts/postgres/defaults/main.yml postgresql_image)}"
|
||||
[ -n "$qmd_repo" ] && [ -n "$qmd_ref" ] || error "Unable to resolve pinned QMD source."
|
||||
[ -n "$litellm_repo" ] && [ -n "$litellm_ref" ] || error "Unable to resolve pinned LiteLLM source."
|
||||
|
||||
info "Starting load-adaptive Phase 2 source prefetch (current limit $(dynamic_parallel_job_limit), hard limit $(( $(online_cpu_count) * 2 )))..."
|
||||
reset_bounded_jobs
|
||||
run_bounded "repo:console" prefetch_git_repository \
|
||||
"xworkspace-console" "$XWORKSPACE_CONSOLE_REPO_URL" "${XWORKSPACE_CONSOLE_SOURCE_VERSION:-main}" "$console_dir"
|
||||
run_bounded "repo:core-skills" prefetch_git_repository \
|
||||
"xworkspace-core-skills" "$XWORKSPACE_CORE_SKILLS_REPO_URL" "main" "$XWORKSPACE_CORE_SKILLS_DIR"
|
||||
run_bounded "repo:bridge" prefetch_git_repository \
|
||||
"xworkmate-bridge" "$XWORKMATE_BRIDGE_REPO_URL" "$XWORKMATE_BRIDGE_BRANCH" "$XWORKMATE_BRIDGE_SOURCE_DIR"
|
||||
run_bounded "repo:qmd" prefetch_git_repository \
|
||||
"qmd" "$qmd_repo" "$qmd_ref" "$qmd_dir"
|
||||
run_bounded "repo:litellm" prefetch_git_repository \
|
||||
"litellm" "$litellm_repo" "$litellm_ref" "$litellm_dir"
|
||||
|
||||
if command -v docker >/dev/null 2>&1 &&
|
||||
printf ',%s,' "${AI_WORKSPACE_RUNTIME_MODES:-docker,systemd}" | grep -q ',docker,'; then
|
||||
[ -n "$postgres_image" ] || error "Unable to resolve pinned PostgreSQL image."
|
||||
run_bounded "image:postgres" prefetch_postgres_image "$postgres_image"
|
||||
fi
|
||||
if ! wait_for_bounded_jobs; then
|
||||
warn "Phase 2 source prefetch failed; continuing with the standard Ansible source tasks."
|
||||
return
|
||||
fi
|
||||
|
||||
export XWORKSPACE_CONSOLE_SOURCE_REPO="file://$console_dir"
|
||||
export XWORKSPACE_CONSOLE_SOURCE_VERSION
|
||||
XWORKSPACE_CONSOLE_SOURCE_VERSION="$(cat "$console_dir/.ai-workspace-prefetched-commit")"
|
||||
export QMD_SOURCE_REPO="file://$qmd_dir"
|
||||
export QMD_VERSION="$qmd_ref"
|
||||
export LITELLM_SOURCE_REPO="file://$litellm_dir"
|
||||
export LITELLM_VERSION="$litellm_ref"
|
||||
export AI_WORKSPACE_PREFETCH_COMPLETED=true
|
||||
success "Phase 2 source prefetch completed."
|
||||
}
|
||||
|
||||
ensure_runtime_build_user() {
|
||||
local user=$1
|
||||
local home=$2
|
||||
|
||||
if id "$user" >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
warn "Cannot create runtime build user $user without root privileges."
|
||||
return 1
|
||||
fi
|
||||
getent group "$user" >/dev/null 2>&1 || groupadd "$user"
|
||||
useradd --create-home --home-dir "$home" --gid "$user" --shell /bin/bash "$user"
|
||||
}
|
||||
|
||||
run_as_runtime_user() {
|
||||
local user=$1
|
||||
local home=$2
|
||||
shift 2
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
runuser -u "$user" -- env HOME="$home" "$@"
|
||||
else
|
||||
env HOME="$home" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_runtime_checkout() {
|
||||
local user=$1
|
||||
local home=$2
|
||||
local repo=$3
|
||||
local ref=$4
|
||||
local dest=$5
|
||||
|
||||
if [ -d "$dest/.git" ]; then
|
||||
run_as_runtime_user "$user" "$home" git -C "$dest" remote set-url origin "$repo"
|
||||
run_as_runtime_user "$user" "$home" git -C "$dest" fetch --force --prune origin "$ref"
|
||||
else
|
||||
rm -rf "$dest"
|
||||
install -d -o "$user" -g "$user" "$(dirname "$dest")"
|
||||
run_as_runtime_user "$user" "$home" git clone --no-checkout "$repo" "$dest"
|
||||
run_as_runtime_user "$user" "$home" git -C "$dest" fetch --force origin "$ref"
|
||||
fi
|
||||
run_as_runtime_user "$user" "$home" git -C "$dest" checkout --force --detach FETCH_HEAD
|
||||
run_as_runtime_user "$user" "$home" git -C "$dest" clean -ffd
|
||||
}
|
||||
|
||||
prebuild_console_dashboard() {
|
||||
local user=$1
|
||||
local home=$2
|
||||
local repo=$3
|
||||
local ref=$4
|
||||
local dest=$5
|
||||
local cache_dir=$6
|
||||
|
||||
prepare_runtime_checkout "$user" "$home" "$repo" "$ref" "$dest"
|
||||
install -d -o "$user" -g "$user" "$cache_dir"
|
||||
run_as_runtime_user "$user" "$home" env npm_config_cache="$cache_dir" \
|
||||
npm install --no-audit --no-fund --prefix "$dest/dashboard"
|
||||
run_as_runtime_user "$user" "$home" env npm_config_cache="$cache_dir" \
|
||||
npm run build --prefix "$dest/dashboard"
|
||||
run_as_runtime_user "$user" "$home" git -C "$dest" rev-parse HEAD \
|
||||
> "$dest/dashboard/.ai-workspace-build-commit"
|
||||
}
|
||||
|
||||
prebuild_qmd_runtime() {
|
||||
local user=$1
|
||||
local home=$2
|
||||
local repo=$3
|
||||
local ref=$4
|
||||
local dest=$5
|
||||
local cache_dir=$6
|
||||
|
||||
prepare_runtime_checkout "$user" "$home" "$repo" "$ref" "$dest"
|
||||
install -d -o "$user" -g "$user" "$cache_dir" "$home/.bun/bin"
|
||||
run_as_runtime_user "$user" "$home" env npm_config_cache="$cache_dir" \
|
||||
npm install --no-audit --no-fund --prefix "$dest"
|
||||
run_as_runtime_user "$user" "$home" env npm_config_cache="$cache_dir" \
|
||||
npm run build --prefix "$dest"
|
||||
run_as_runtime_user "$user" "$home" ln -sfn "$dest/bin/qmd" "$home/.bun/bin/qmd"
|
||||
}
|
||||
|
||||
preinstall_openclaw_runtime() {
|
||||
local user=$1
|
||||
local home=$2
|
||||
local version=$3
|
||||
local cache_dir=$4
|
||||
|
||||
install -d -o "$user" -g "$user" "$cache_dir" "$home/.local"
|
||||
run_as_runtime_user "$user" "$home" env npm_config_cache="$cache_dir" \
|
||||
npm install --global --omit=dev --no-audit --no-fund \
|
||||
--prefix "$home/.local" "openclaw@$version"
|
||||
}
|
||||
|
||||
prebuild_independent_runtimes() {
|
||||
if [ "$AI_WORKSPACE_RUNTIME_PREBUILD_ENABLED" != "true" ]; then
|
||||
info "Runtime prebuild disabled by AI_WORKSPACE_RUNTIME_PREBUILD_ENABLED."
|
||||
return
|
||||
fi
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
warn "npm is unavailable after the Node.js phase; continuing without runtime prebuild."
|
||||
return
|
||||
fi
|
||||
|
||||
local user="${AI_WORKSPACE_RUNTIME_USER:-ubuntu}"
|
||||
local home="${AI_WORKSPACE_RUNTIME_HOME:-/home/$user}"
|
||||
local console_repo="${XWORKSPACE_CONSOLE_SOURCE_REPO:-$XWORKSPACE_CONSOLE_REPO_URL}"
|
||||
local console_ref="${XWORKSPACE_CONSOLE_SOURCE_VERSION:-main}"
|
||||
local qmd_repo qmd_ref openclaw_version
|
||||
qmd_repo="${QMD_SOURCE_REPO:-$(read_playbook_default roles/vhosts/qmd/defaults/main.yml qmd_source_repo)}"
|
||||
qmd_ref="${QMD_VERSION:-$(read_playbook_default roles/vhosts/qmd/defaults/main.yml qmd_version)}"
|
||||
openclaw_version="$(read_playbook_default roles/vhosts/gateway_openclaw/defaults/main.yml gateway_openclaw_required_version)"
|
||||
|
||||
if ! ensure_runtime_build_user "$user" "$home"; then
|
||||
return
|
||||
fi
|
||||
|
||||
info "Starting load-adaptive runtime prebuild (current limit $(dynamic_parallel_job_limit))..."
|
||||
reset_bounded_jobs
|
||||
run_bounded "build:console" prebuild_console_dashboard \
|
||||
"$user" "$home" "$console_repo" "$console_ref" "$home/xworkspace-console" \
|
||||
"$AI_WORKSPACE_PREFETCH_DIR/npm-cache/console"
|
||||
run_bounded "build:qmd" prebuild_qmd_runtime \
|
||||
"$user" "$home" "$qmd_repo" "$qmd_ref" "$home/.local/src/qmd" \
|
||||
"$AI_WORKSPACE_PREFETCH_DIR/npm-cache/qmd"
|
||||
if [ -n "$openclaw_version" ]; then
|
||||
run_bounded "package:openclaw" preinstall_openclaw_runtime \
|
||||
"$user" "$home" "$openclaw_version" "$AI_WORKSPACE_PREFETCH_DIR/npm-cache/openclaw"
|
||||
fi
|
||||
if ! wait_for_bounded_jobs; then
|
||||
warn "One or more runtime prebuild jobs failed; the standard Ansible tasks will retry them serially."
|
||||
return
|
||||
fi
|
||||
success "Runtime prebuild completed."
|
||||
}
|
||||
|
||||
wait_for_url() {
|
||||
local url=$1
|
||||
local header=${2:-}
|
||||
@ -1059,8 +1422,10 @@ service_status_line() {
|
||||
local label=$1
|
||||
local unit_patterns=$2
|
||||
local port=${3:-}
|
||||
local health_url=${4:-}
|
||||
local detail="not detected"
|
||||
local state="inactive"
|
||||
local http_status=""
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
local unit
|
||||
@ -1088,9 +1453,81 @@ service_status_line() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$health_url" ] && command -v curl >/dev/null 2>&1; then
|
||||
http_status="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 2 --max-time 5 "$health_url" 2>/dev/null || true)"
|
||||
case "$http_status" in
|
||||
2*|3*|401)
|
||||
state="active"
|
||||
detail="${detail};http:${http_status}"
|
||||
;;
|
||||
'') detail="${detail};http:unreachable" ;;
|
||||
*) detail="${detail};http:${http_status}" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
printf ' %-28s : %-8s (%s)\n' "$label" "$state" "$detail"
|
||||
}
|
||||
|
||||
write_service_status() {
|
||||
local output_file=$1
|
||||
shift
|
||||
service_status_line "$@" > "$output_file"
|
||||
}
|
||||
|
||||
print_parallel_service_statuses() {
|
||||
local status_dir
|
||||
local labels=(
|
||||
"Portal / Console"
|
||||
"XWorkMate Bridge"
|
||||
"OpenClaw"
|
||||
"QMD"
|
||||
"Hermes"
|
||||
"PostgreSQL"
|
||||
"Vault"
|
||||
"LiteLLM"
|
||||
"Runtime desktop/browser"
|
||||
)
|
||||
local units=(
|
||||
"xworkspace-console.service xworkspace-api.service"
|
||||
"xworkmate-bridge.service xworkspace-bridge.service"
|
||||
"xworkspace-openclaw.service openclaw-gateway.service openclaw.service"
|
||||
"qmd-mcp.service xworkspace-qmd.service qmd.service qdrant.service"
|
||||
"acp-hermes.service xworkspace-hermes.service hermes.service"
|
||||
"postgresql.service postgresql@17-main.service postgresql@16-main.service postgresql@15-main.service xworkspace-postgres.service"
|
||||
"xworkspace-vault.service vault.service"
|
||||
"xworkspace-litellm.service litellm-proxy.service litellm.service"
|
||||
"xworkspace-shell.service display-manager.service gdm.service lightdm.service"
|
||||
)
|
||||
local ports=("17000" "8787" "18789" "8181" "3920" "5432" "8200" "4000" "")
|
||||
local urls=(
|
||||
"http://127.0.0.1:17000/"
|
||||
"http://127.0.0.1:8787/"
|
||||
"http://127.0.0.1:18789/channels"
|
||||
"http://127.0.0.1:8181/"
|
||||
"http://127.0.0.1:3920/"
|
||||
""
|
||||
"http://127.0.0.1:8200/v1/sys/health"
|
||||
"http://127.0.0.1:4000/health"
|
||||
""
|
||||
)
|
||||
local index
|
||||
|
||||
status_dir="$(mktemp -d)"
|
||||
reset_bounded_jobs
|
||||
for index in "${!labels[@]}"; do
|
||||
run_bounded "status:$index" write_service_status "$status_dir/$index" \
|
||||
"${labels[$index]}" "${units[$index]}" "${ports[$index]}" "${urls[$index]}"
|
||||
done
|
||||
if ! wait_for_bounded_jobs; then
|
||||
rm -rf "$status_dir"
|
||||
error "Parallel service status collection failed."
|
||||
fi
|
||||
for index in "${!labels[@]}"; do
|
||||
cat "$status_dir/$index"
|
||||
done
|
||||
rm -rf "$status_dir"
|
||||
}
|
||||
|
||||
cli_status_line() {
|
||||
local label=$1
|
||||
local bin=$2
|
||||
@ -1133,15 +1570,7 @@ print_deployment_summary() {
|
||||
|
||||
[服务状态]
|
||||
EOF
|
||||
service_status_line "Portal / Console" "xworkspace-console.service xworkspace-api.service" "17000"
|
||||
service_status_line "XWorkMate Bridge" "xworkmate-bridge.service xworkspace-bridge.service" "8787"
|
||||
service_status_line "OpenClaw" "xworkspace-openclaw.service openclaw-gateway.service openclaw.service" "18789"
|
||||
service_status_line "QMD" "qmd-mcp.service xworkspace-qmd.service qmd.service qdrant.service" "8181"
|
||||
service_status_line "Hermes" "acp-hermes.service xworkspace-hermes.service hermes.service" "3920"
|
||||
service_status_line "PostgreSQL" "postgresql.service postgresql@17-main.service postgresql@16-main.service postgresql@15-main.service xworkspace-postgres.service" "5432"
|
||||
service_status_line "Vault" "xworkspace-vault.service vault.service" "8200"
|
||||
service_status_line "LiteLLM" "xworkspace-litellm.service litellm-proxy.service litellm.service" "4000"
|
||||
service_status_line "Runtime desktop/browser" "xworkspace-shell.service display-manager.service gdm.service lightdm.service" ""
|
||||
print_parallel_service_statuses
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
@ -1390,6 +1819,13 @@ deploy_macos_local() {
|
||||
info "Logs: $api_log, $api_err, $dashboard_log, and $dashboard_err"
|
||||
}
|
||||
|
||||
if [ "${AI_WORKSPACE_LIBRARY_MODE:-false}" = "true" ]; then
|
||||
if (return 0 2>/dev/null); then
|
||||
return 0
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "Starting AI Workspace All-in-One Bootstrap..."
|
||||
|
||||
# 1. Install prerequisites (git, curl, ansible) if missing
|
||||
@ -1457,6 +1893,7 @@ else
|
||||
fi
|
||||
|
||||
patch_playbook_user_systemd
|
||||
prefetch_independent_sources
|
||||
ensure_core_skills_source
|
||||
ensure_xworkmate_bridge_source
|
||||
|
||||
@ -1499,8 +1936,10 @@ append_var "POSTGRESQL_DEPLOY_MODE" "postgresql_deploy_mode"
|
||||
append_var "AI_WORKSPACE_APT_LOCK_TIMEOUT" "ai_workspace_apt_lock_timeout"
|
||||
append_var "XWORKSPACE_CONSOLE_SOURCE_REPO" "xworkspace_console_source_repo"
|
||||
append_var "XWORKSPACE_CONSOLE_SOURCE_VERSION" "xworkspace_console_source_version"
|
||||
append_var "XWORKSPACE_CONSOLE_RUNTIME_ARCHIVE" "xworkspace_console_runtime_archive"
|
||||
append_var "QMD_SOURCE_REPO" "qmd_source_repo"
|
||||
append_var "QMD_VERSION" "qmd_version"
|
||||
append_var "QMD_RUNTIME_ARCHIVE" "qmd_runtime_archive"
|
||||
append_var "LITELLM_SOURCE_REPO" "litellm_source_repo"
|
||||
append_var "LITELLM_VERSION" "litellm_version"
|
||||
|
||||
@ -1552,11 +1991,30 @@ chmod 600 "$VAULT_FILE"
|
||||
|
||||
# 6. Run Ansible Playbook locally
|
||||
wait_for_apt_locks
|
||||
info "Running Ansible Playbook locally..."
|
||||
ansible-playbook -i '127.0.0.1,' -c local setup-ai-workspace-all-in-one.yml \
|
||||
--vault-password-file "$VAULT_FILE" \
|
||||
"${ANSIBLE_EXTRA_VARS[@]}"
|
||||
RET=$?
|
||||
RET=0
|
||||
if [ "$AI_WORKSPACE_SPLIT_PHASES" = "true" ]; then
|
||||
info "Running AI Workspace preflight..."
|
||||
ansible-playbook -i '127.0.0.1,' -c local setup-ai-workspace-preflight.yml \
|
||||
--vault-password-file "$VAULT_FILE" \
|
||||
"${ANSIBLE_EXTRA_VARS[@]}" || RET=$?
|
||||
if [ "$RET" -eq 0 ]; then
|
||||
info "Running serialized Node.js foundation phase..."
|
||||
ansible-playbook -i '127.0.0.1,' -c local setup-nodejs.yml \
|
||||
--vault-password-file "$VAULT_FILE" \
|
||||
"${ANSIBLE_EXTRA_VARS[@]}" || RET=$?
|
||||
fi
|
||||
if [ "$RET" -eq 0 ]; then
|
||||
info "Running remaining AI Workspace runtime phases..."
|
||||
ansible-playbook -i '127.0.0.1,' -c local setup-ai-workspace-runtime.yml \
|
||||
--vault-password-file "$VAULT_FILE" \
|
||||
"${ANSIBLE_EXTRA_VARS[@]}" || RET=$?
|
||||
fi
|
||||
else
|
||||
info "Running monolithic AI Workspace Playbook..."
|
||||
ansible-playbook -i '127.0.0.1,' -c local setup-ai-workspace-all-in-one.yml \
|
||||
--vault-password-file "$VAULT_FILE" \
|
||||
"${ANSIBLE_EXTRA_VARS[@]}" || RET=$?
|
||||
fi
|
||||
|
||||
if [ $RET -eq 0 ]; then
|
||||
success "AI Workspace deployed successfully!"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user