feat: build offline AI Workspace installer packages
This commit is contained in:
parent
656ca02a14
commit
65bb07ab06
196
.github/workflows/offline-package-ai-workspace-installer.yaml
vendored
Normal file
196
.github/workflows/offline-package-ai-workspace-installer.yaml
vendored
Normal 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}/"
|
||||
59
docs/OFFLINE_AI_WORKSPACE_INSTALLER.md
Normal file
59
docs/OFFLINE_AI_WORKSPACE_INSTALLER.md
Normal 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.
|
||||
124
scripts/ai-workspace-offline-install.sh
Executable file
124
scripts/ai-workspace-offline-install.sh
Executable 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 "$@"
|
||||
295
scripts/create-ai-workspace-offline-package.sh
Executable file
295
scripts/create-ai-workspace-offline-package.sh
Executable 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 "$@"
|
||||
@ -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)"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user