feat: build offline AI Workspace installer packages

This commit is contained in:
Haitao Pan 2026-06-14 13:50:36 +08:00
parent 656ca02a14
commit 65bb07ab06
5 changed files with 679 additions and 0 deletions

View File

@ -0,0 +1,196 @@
name: Build Offline AI Workspace All-in-One Package
on:
push:
paths:
- 'scripts/create-ai-workspace-offline-package.sh'
- 'scripts/ai-workspace-offline-install.sh'
- '.github/workflows/offline-package-ai-workspace-installer.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag. Leave empty to use offline-ai-workspace-<run_number>"
required: false
type: string
playbooks_ref:
description: "ai-workspace-infra/playbooks git ref"
required: false
default: "main"
type: string
console_ref:
description: "ai-workspace-lab/xworkspace-console git ref"
required: false
default: "main"
type: string
core_skills_ref:
description: "ai-workspace-lab/xworkspace-core-skills git ref"
required: false
default: "main"
type: string
permissions:
contents: write
concurrency:
group: build-offline-ai-workspace-all-in-one
cancel-in-progress: false
jobs:
build-offline-package:
name: Build ${{ matrix.distro }}-${{ matrix.version }}-${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- distro: debian
version: "13"
arch: amd64
- distro: debian
version: "13"
arch: arm64
- distro: debian
version: "12"
arch: amd64
- distro: debian
version: "12"
arch: arm64
- distro: debian
version: "11"
arch: amd64
- distro: debian
version: "11"
arch: arm64
- distro: ubuntu
version: "26.04"
arch: amd64
- distro: ubuntu
version: "26.04"
arch: arm64
- distro: ubuntu
version: "24.04"
arch: amd64
- distro: ubuntu
version: "24.04"
arch: arm64
- distro: ubuntu
version: "22.04"
arch: amd64
- distro: ubuntu
version: "22.04"
arch: arm64
steps:
- uses: actions/checkout@v4
- name: Enable QEMU for cross-arch package collection
uses: docker/setup-qemu-action@v3
- name: Install build dependencies
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y curl git jq python3-pip
- name: Build offline package
env:
DISTRO_ID: ${{ matrix.distro }}
DISTRO_VERSION: ${{ matrix.version }}
ARCH: ${{ matrix.arch }}
PLAYBOOKS_REF: ${{ github.event.inputs.playbooks_ref || 'main' }}
CONSOLE_REF: ${{ github.event.inputs.console_ref || 'main' }}
CORE_SKILLS_REF: ${{ github.event.inputs.core_skills_ref || 'main' }}
PACKAGE_VERSION: ${{ github.run_number }}
run: |
set -euo pipefail
chmod +x scripts/create-ai-workspace-offline-package.sh
scripts/create-ai-workspace-offline-package.sh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ai-workspace-all-in-one-offline-${{ matrix.distro }}-${{ matrix.version }}-${{ matrix.arch }}
path: ai-workspace-all-in-one-offline-${{ matrix.distro }}-${{ matrix.version }}-${{ matrix.arch }}.tar.gz
if-no-files-found: error
test-offline-package:
needs: build-offline-package
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- distro: debian
version: "13"
arch: amd64
- distro: ubuntu
version: "24.04"
arch: amd64
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: ai-workspace-all-in-one-offline-${{ matrix.distro }}-${{ matrix.version }}-${{ matrix.arch }}
path: offline-test
- name: Verify offline package contents
run: |
set -euo pipefail
cd offline-test
tar -tzf ai-workspace-all-in-one-offline-${{ matrix.distro }}-${{ matrix.version }}-${{ matrix.arch }}.tar.gz > contents.txt
grep -q 'scripts/ai-workspace-offline-install.sh' contents.txt
grep -q 'metadata/manifest.json' contents.txt
grep -q 'repos/playbooks' contents.txt
grep -q 'repos/xworkspace-console' contents.txt
grep -q 'packages/apt/Packages.gz' contents.txt
publish-release:
needs: test-offline-package
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-ai-workspace-{0}', github.run_number) }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/offline-package/ai-workspace
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download all package artifacts
uses: actions/download-artifact@v4
with:
path: release-artifacts
pattern: ai-workspace-all-in-one-offline-*
merge-multiple: true
- name: Upload packages to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: release-artifacts/*.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Rsync packages to remote mirror
if: ${{ env.RSYNC_SSH_KEY != '' && env.RSYNC_SSH_USER != '' && env.VPS_HOST != '' }}
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y rsync openssh-client
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
remote_dir="${REMOTE_ROOT}/${TAG_NAME}"
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "mkdir -p '${remote_dir}'"
rsync -av -e "ssh -i ~/.ssh/id_rsa" release-artifacts/*.tar.gz \
"${RSYNC_SSH_USER}@${VPS_HOST}:${remote_dir}/"

View File

@ -0,0 +1,59 @@
# Offline AI Workspace Installer
`offline-package-ai-workspace-installer.yaml` builds tarball resource packs for
`setup-ai-workspace-all-in-one.sh`.
## Supported Targets
- Debian: 13, 12, 11
- Ubuntu LTS: 26.04, 24.04, 22.04
- Architectures: amd64, arm64
Ubuntu 20.04 is not in the default matrix because standard support has moved to
Ubuntu Pro/ESM.
## Package Contents
- `repos/playbooks`
- `repos/xworkspace-console`
- `repos/xworkspace-core-skills`
- `repos/xworkmate-bridge`
- `repos/qmd`
- `repos/litellm`
- `packages/apt`
- `packages/npm`
- `packages/npm-cache`
- `packages/pip`
- `packages/bin`
- `packages/images`
- `scripts/ai-workspace-offline-install.sh`
- `metadata/manifest.json`
- `metadata/*.commit`
## Runtime Usage
Extract the target package on the host and run:
```bash
sudo ./scripts/ai-workspace-offline-install.sh
```
The script configures a local APT repository, installs bundled binaries, loads
packaged container images when Docker is available, and runs the packaged
all-in-one bootstrap with local source directories.
## Deployment Timing Notes
A remote `setup-ai-workspace-all-in-one.sh` run on `acp-bridge.onwalk.net`
showed these visible timing hotspots:
- OpenClaw npm package/plugin install and dependency repair: about 68 seconds
- Codex ACP Go build: about 37 seconds
- ACP vhosts for Codex/OpenCode/Gemini/Hermes: about 95 seconds
- Console runtime apt/package setup and ttyd: about 47 seconds
- Agent skill sync and quality checks: about 48 seconds
The first several minutes of the sampled log included Node, apt, and AI runtime
setup before the captured timing window. Those are treated as resource-heavy
setup phases and are included in the offline package through APT caches, npm
tarballs, pip wheels, local git sources, binaries, and container image tarballs.

View File

@ -0,0 +1,124 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
APT_DIR="${ROOT}/packages/apt"
BIN_DIR="${ROOT}/packages/bin"
IMAGE_DIR="${ROOT}/packages/images"
NPM_CACHE_DIR="${ROOT}/packages/npm-cache"
PIP_WHEEL_DIR="${ROOT}/packages/pip"
info() {
printf '\033[1;34m[INFO]\033[0m %s\n' "$*" >&2
}
warn() {
printf '\033[1;33m[WARN]\033[0m %s\n' "$*" >&2
}
require_root() {
if [ "$(id -u)" -ne 0 ]; then
echo "Run this installer as root or with sudo." >&2
exit 1
fi
}
configure_local_apt_repo() {
if [ ! -d "${APT_DIR}" ] || ! compgen -G "${APT_DIR}/*.deb" >/dev/null; then
warn "No offline .deb cache found at ${APT_DIR}; skipping local APT repo setup."
return
fi
info "Configuring local APT repository from ${APT_DIR}"
cat > /etc/apt/sources.list.d/ai-workspace-offline.list <<EOF
deb [trusted=yes] file:${APT_DIR} ./
EOF
apt-get update -o Dir::Etc::sourcelist="sources.list.d/ai-workspace-offline.list" \
-o Dir::Etc::sourceparts="-" \
-o APT::Get::List-Cleanup="0"
}
install_bundled_binaries() {
if compgen -G "${BIN_DIR}/vault_*_linux_*.zip" >/dev/null; then
info "Installing bundled Vault binary"
apt-get install -y unzip || true
tmp="$(mktemp -d)"
unzip -o "${BIN_DIR}"/vault_*_linux_*.zip -d "${tmp}"
install -m 0755 "${tmp}/vault" /usr/local/bin/vault
rm -rf "${tmp}"
fi
case "$(uname -m)" in
x86_64|amd64) ttyd_arch=x86_64 ;;
aarch64|arm64) ttyd_arch=aarch64 ;;
*) ttyd_arch="" ;;
esac
if [ -n "${ttyd_arch}" ] && [ -f "${BIN_DIR}/ttyd.${ttyd_arch}" ]; then
info "Installing bundled ttyd binary"
install -m 0755 "${BIN_DIR}/ttyd.${ttyd_arch}" /usr/local/bin/ttyd
fi
}
load_container_images() {
if [ ! -d "${IMAGE_DIR}" ] || ! compgen -G "${IMAGE_DIR}/*.tar" >/dev/null; then
return
fi
if command -v docker >/dev/null 2>&1; then
for image_tar in "${IMAGE_DIR}"/*.tar; do
info "Loading container image ${image_tar}"
docker load -i "${image_tar}" || true
done
else
warn "Docker is not installed yet; bundled image tarballs remain in ${IMAGE_DIR}."
fi
}
configure_language_package_caches() {
if [ -d "${NPM_CACHE_DIR}" ]; then
info "Configuring npm to prefer bundled cache"
npm config set cache "${NPM_CACHE_DIR}" --global || true
npm config set prefer-offline true --global || true
fi
if [ -d "${PIP_WHEEL_DIR}" ]; then
info "Configuring pip to prefer bundled wheelhouse"
mkdir -p /etc/pip.conf.d
cat > /etc/pip.conf <<EOF
[global]
find-links = ${PIP_WHEEL_DIR}
prefer-binary = true
EOF
fi
}
run_bootstrap() {
local setup_script="${ROOT}/repos/xworkspace-console/scripts/setup-ai-workspace-all-in-one.sh"
if [ ! -x "${setup_script}" ]; then
chmod +x "${setup_script}"
fi
export PLAYBOOK_DIR="${PLAYBOOK_DIR:-${ROOT}/repos/playbooks}"
export XWORKSPACE_CONSOLE_DIR="${XWORKSPACE_CONSOLE_DIR:-${ROOT}/repos/xworkspace-console}"
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_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}"
info "Running packaged AI Workspace all-in-one bootstrap"
bash "${setup_script}"
}
main() {
require_root
configure_local_apt_repo
install_bundled_binaries
load_container_images
configure_language_package_caches
run_bootstrap
}
main "$@"

View File

@ -0,0 +1,295 @@
#!/usr/bin/env bash
set -euo pipefail
DISTRO_ID="${DISTRO_ID:?DISTRO_ID is required, e.g. debian or ubuntu}"
DISTRO_VERSION="${DISTRO_VERSION:?DISTRO_VERSION is required, e.g. 13 or 24.04}"
ARCH="${ARCH:-amd64}"
PACKAGE_VERSION="${PACKAGE_VERSION:-$(date -u +%Y%m%d%H%M%S)}"
PLAYBOOKS_REPO="${PLAYBOOKS_REPO:-https://github.com/ai-workspace-infra/playbooks.git}"
PLAYBOOKS_REF="${PLAYBOOKS_REF:-main}"
CONSOLE_REPO="${CONSOLE_REPO:-https://github.com/ai-workspace-lab/xworkspace-console.git}"
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}"
NODEJS_MAJOR_VERSIONS="${NODEJS_MAJOR_VERSIONS:-22 24}"
NODEJS_22_VERSION="${NODEJS_22_VERSION:-22.22.3}"
NODEJS_24_VERSION="${NODEJS_24_VERSION:-24.16.0}"
VAULT_VERSION="${VAULT_VERSION:-1.21.4}"
TTYD_VERSION="${TTYD_VERSION:-latest}"
POSTGRES_IMAGE="${POSTGRES_IMAGE:-postgres:17.7}"
OPENCLAW_VERSION="${OPENCLAW_VERSION:-2026.6.6}"
PLAYWRIGHT_VERSION="${PLAYWRIGHT_VERSION:-1.60.0}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
case "${ARCH}" in
x86_64|amd64) ARCH=amd64 ;;
aarch64|arm64) ARCH=arm64 ;;
*) echo "Unsupported ARCH: ${ARCH}" >&2; exit 1 ;;
esac
case "${DISTRO_ID}:${DISTRO_VERSION}" in
debian:11|debian:12|debian:13|ubuntu:22.04|ubuntu:24.04|ubuntu:26.04) ;;
*)
echo "Unsupported target: ${DISTRO_ID}:${DISTRO_VERSION}" >&2
echo "Supported: debian 11/12/13 and ubuntu 22.04/24.04/26.04" >&2
exit 1
;;
esac
WORKDIR="${WORKDIR:-${REPO_ROOT}/ai-workspace-all-in-one-offline-${DISTRO_ID}-${DISTRO_VERSION}-${ARCH}}"
OUT="${OUT:-${REPO_ROOT}/ai-workspace-all-in-one-offline-${DISTRO_ID}-${DISTRO_VERSION}-${ARCH}.tar.gz}"
image_for_target() {
case "${DISTRO_ID}" in
debian) echo "debian:${DISTRO_VERSION}" ;;
ubuntu) echo "ubuntu:${DISTRO_VERSION}" ;;
esac
}
safe_name() {
tr '/:@+' '----' <<<"$1"
}
clone_repo() {
local url=$1
local ref=$2
local dest=$3
git clone --depth 1 --branch "${ref}" "${url}" "${dest}" 2>/dev/null || {
git clone --depth 1 "${url}" "${dest}"
git -C "${dest}" fetch --depth 1 origin "${ref}"
git -C "${dest}" checkout FETCH_HEAD
}
git -C "${dest}" rev-parse HEAD
}
download_apt_packages() {
local image=$1
local platform="linux/${ARCH}"
local apt_dir="${WORKDIR}/packages/apt"
local apt_lists="${WORKDIR}/metadata/apt"
mkdir -p "${apt_dir}" "${apt_lists}"
docker run --rm --platform "${platform}" \
-e DISTRO_ID="${DISTRO_ID}" \
-e NODEJS_MAJOR_VERSIONS="${NODEJS_MAJOR_VERSIONS}" \
-e NODEJS_22_VERSION="${NODEJS_22_VERSION}" \
-e NODEJS_24_VERSION="${NODEJS_24_VERSION}" \
-v "${apt_dir}:/offline-apt" \
-v "${apt_lists}:/offline-meta" \
"${image}" \
bash -lc "$(cat <<'CONTAINER'
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y ca-certificates curl gnupg dpkg-dev apt-transport-https
install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /etc/apt/keyrings/nodesource.gpg
for major in ${NODEJS_MAJOR_VERSIONS}; do
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${major}.x nodistro main" \
> "/etc/apt/sources.list.d/nodesource-node${major}.list"
done
curl -fsSL https://dl.google.com/linux/linux_signing_key.pub \
| gpg --dearmor -o /etc/apt/keyrings/google-linux-signing-key.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/google-linux-signing-key.gpg] https://dl.google.com/linux/chrome/deb/ stable main" \
> /etc/apt/sources.list.d/google-chrome.list
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg -o /etc/apt/keyrings/yarn.asc
echo "deb [signed-by=/etc/apt/keyrings/yarn.asc] https://dl.yarnpkg.com/debian/ stable main" \
> /etc/apt/sources.list.d/yarn.list
curl -fsSL https://download.docker.com/linux/${DISTRO_ID}/gpg -o /etc/apt/keyrings/docker.asc || true
if [ "${DISTRO_ID}" = "ubuntu" ] || [ "${DISTRO_ID}" = "debian" ]; then
. /etc/os-release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${DISTRO_ID} ${VERSION_CODENAME} stable" \
> /etc/apt/sources.list.d/docker.list
fi
apt-get update -y || true
packages=(
ansible git curl ca-certificates gnupg jq rsync unzip wget
caddy xfce4 python3 python3-pip python3-venv python3-dev python3-setuptools
build-essential pkg-config python-is-python3 pandoc fonts-noto-cjk
fonts-noto-cjk-extra fonts-wqy-zenhei fonts-wqy-microhei
google-chrome-stable nodejs yarn
)
if apt-cache show docker-ce >/dev/null 2>&1; then
packages+=(docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin)
elif apt-cache show docker.io >/dev/null 2>&1; then
packages+=(docker.io docker-compose-plugin)
fi
if apt-cache show golang-go >/dev/null 2>&1; then
packages+=(golang-go)
fi
if apt-cache show texlive-xetex >/dev/null 2>&1; then
packages+=(texlive-xetex texlive-latex-extra texlive-fonts-recommended texlive-lang-chinese latexmk)
fi
apt-get install --download-only -y --no-install-recommends "${packages[@]}" || true
apt-get download "nodejs=${NODEJS_22_VERSION}-1nodesource1" || true
apt-get download "nodejs=${NODEJS_24_VERSION}-1nodesource1" || true
cp -n ./*.deb /offline-apt/ 2>/dev/null || true
find /var/cache/apt/archives -name '*.deb' -exec cp -n {} /offline-apt/ \;
cd /offline-apt
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
find . -maxdepth 1 -name '*.deb' -printf '%f\n' | sort > /offline-meta/deb-files.txt
CONTAINER
)" \
-e DISTRO_ID="${DISTRO_ID}" \
-e NODEJS_MAJOR_VERSIONS="${NODEJS_MAJOR_VERSIONS}"
}
download_npm_packages() {
local npm_dir="${WORKDIR}/packages/npm"
local npm_cache_dir="${WORKDIR}/packages/npm-cache"
mkdir -p "${npm_dir}" "${npm_cache_dir}"
npm pack --pack-destination "${npm_dir}" "opencode-ai"
npm pack --pack-destination "${npm_dir}" "@google/gemini-cli"
npm pack --pack-destination "${npm_dir}" "@openai/codex"
npm pack --pack-destination "${npm_dir}" "@anthropic-ai/claude-code"
npm pack --pack-destination "${npm_dir}" "openclaw@${OPENCLAW_VERSION}"
npm pack --pack-destination "${npm_dir}" "@openclaw/codex@${OPENCLAW_VERSION}"
npm pack --pack-destination "${npm_dir}" "playwright@${PLAYWRIGHT_VERSION}"
npm cache add --cache "${npm_cache_dir}" "opencode-ai"
npm cache add --cache "${npm_cache_dir}" "@google/gemini-cli"
npm cache add --cache "${npm_cache_dir}" "@openai/codex"
npm cache add --cache "${npm_cache_dir}" "@anthropic-ai/claude-code"
npm cache add --cache "${npm_cache_dir}" "openclaw@${OPENCLAW_VERSION}"
npm cache add --cache "${npm_cache_dir}" "@openclaw/codex@${OPENCLAW_VERSION}"
npm cache add --cache "${npm_cache_dir}" "playwright@${PLAYWRIGHT_VERSION}"
}
download_pip_wheels() {
local wheel_dir="${WORKDIR}/packages/pip"
mkdir -p "${wheel_dir}"
python3 -m pip download --dest "${wheel_dir}" \
"litellm[proxy] @ git+${LITELLM_REPO}@${LITELLM_REF}" \
prisma psycopg2-binary || true
}
download_binaries() {
local bin_dir="${WORKDIR}/packages/bin"
local vault_arch="${ARCH}"
local ttyd_arch
mkdir -p "${bin_dir}"
case "${ARCH}" in
amd64) ttyd_arch=x86_64 ;;
arm64) ttyd_arch=aarch64 ;;
esac
curl -fsSL \
"https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_${vault_arch}.zip" \
-o "${bin_dir}/vault_${VAULT_VERSION}_linux_${vault_arch}.zip"
if [ "${TTYD_VERSION}" = "latest" ]; then
curl -fsSL "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.${ttyd_arch}" \
-o "${bin_dir}/ttyd.${ttyd_arch}"
else
curl -fsSL "https://github.com/tsl0922/ttyd/releases/download/${TTYD_VERSION}/ttyd.${ttyd_arch}" \
-o "${bin_dir}/ttyd.${ttyd_arch}"
fi
chmod +x "${bin_dir}/ttyd.${ttyd_arch}"
}
export_container_images() {
local images_dir="${WORKDIR}/packages/images"
mkdir -p "${images_dir}"
docker pull --platform "linux/${ARCH}" "${POSTGRES_IMAGE}"
docker save "${POSTGRES_IMAGE}" -o "${images_dir}/$(safe_name "${POSTGRES_IMAGE}").tar"
}
write_manifest() {
local manifest="${WORKDIR}/metadata/manifest.json"
mkdir -p "$(dirname "${manifest}")"
cat > "${manifest}" <<JSON
{
"name": "ai-workspace-all-in-one-offline",
"packageVersion": "${PACKAGE_VERSION}",
"target": {
"distro": "${DISTRO_ID}",
"version": "${DISTRO_VERSION}",
"arch": "${ARCH}"
},
"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}"}
},
"versions": {
"vault": "${VAULT_VERSION}",
"openclaw": "${OPENCLAW_VERSION}",
"playwright": "${PLAYWRIGHT_VERSION}",
"postgresImage": "${POSTGRES_IMAGE}"
}
}
JSON
}
main() {
rm -rf "${WORKDIR}" "${OUT}"
mkdir -p "${WORKDIR}/repos" "${WORKDIR}/scripts" "${WORKDIR}/metadata"
local image
image="$(image_for_target)"
echo "Building AI Workspace offline package for ${DISTRO_ID} ${DISTRO_VERSION} ${ARCH}"
echo "Using package workspace: ${WORKDIR}"
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_npm_packages
download_pip_wheels
download_binaries
export_container_images
cp "${SCRIPT_DIR}/ai-workspace-offline-install.sh" "${WORKDIR}/scripts/"
chmod +x "${WORKDIR}/scripts/ai-workspace-offline-install.sh"
write_manifest
cat > "${WORKDIR}/README.md" <<'README'
# AI Workspace All-in-One Offline Package
Extract this archive on the target host, then run:
```bash
sudo ./scripts/ai-workspace-offline-install.sh
```
The installer configures a local APT repository from `packages/apt`, loads bundled
container images when Docker is available, and runs the packaged
`xworkspace-console/scripts/setup-ai-workspace-all-in-one.sh` with local source
directories for playbooks, console, core skills, and bridge code.
Set the same environment variables supported by the online installer before
running the script, for example `TOKEN`, `XWORKMATE_BRIDGE_DOMAIN`, or
`AI_WORKSPACE_SECURITY_LEVEL`.
README
tar -czf "${OUT}" -C "$(dirname "${WORKDIR}")" "$(basename "${WORKDIR}")"
ls -lh "${OUT}"
}
main "$@"

View File

@ -23,6 +23,7 @@ set -euo pipefail
# Unified auth token passed to xworkmate-bridge, LiteLLM, OpenClaw, and Vault.
# PLAYBOOK_DIR (optional local playbooks checkout; useful for macOS validation)
# XWORKSPACE_CONSOLE_DIR (optional local xworkspace-console checkout for macOS)
# QMD_SOURCE_REPO / LITELLM_SOURCE_REPO (optional local git sources for offline installs)
# AI_WORKSPACE_DARWIN_MODE=local (default on macOS) | ansible
# ==============================================================================
@ -995,6 +996,10 @@ append_var "VAULT_DEPLOY_MODE" "vault_deploy_mode"
append_var "XWORKSPACE_CONSOLE_ENABLE_XRDP" "xworkspace_console_enable_xrdp"
append_var "AI_WORKSPACE_RUNTIME_MODES" "ai_workspace_runtime_modes"
append_var "POSTGRESQL_DEPLOY_MODE" "postgresql_deploy_mode"
append_var "QMD_SOURCE_REPO" "qmd_source_repo"
append_var "QMD_VERSION" "qmd_version"
append_var "LITELLM_SOURCE_REPO" "litellm_source_repo"
append_var "LITELLM_VERSION" "litellm_version"
# 4. Resolve one auth token for the bridge and downstream service UIs/APIs.
UNIFIED_AUTH_TOKEN="$(resolve_unified_auth_token)"