Add offline Workstation payload foundation

This commit is contained in:
Jino Jose 2026-07-14 11:16:06 +05:30
parent 81fa1cdb07
commit 21bca7de3f
8 changed files with 245 additions and 0 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ coverage/
!.env.example
test-results/
playwright-report/
autoinstall/offline-output/

View File

@ -0,0 +1,33 @@
# Nexus One AI Offline Server Payload
This directory defines the payload embedded by `build-iso-offline.sh`. The
offline installer is deliberately separate from the existing online bootstrap
ISO until disconnected clean-install validation is complete.
## Offline Workstation contents
- the exact `aipackage` source revision used for the build
- Ubuntu, Docker, NVIDIA and appliance `.deb` packages
- Python wheels for the backend, RAG, ChromaDB and optional JupyterLab
- Miniconda installer and an offline Python 3.11 environment package
- Ollama runtime with `phi3:mini` and `nomic-embed-text`
- vLLM and web/monitoring container images
- the pinned Phi-3 Hugging Face model snapshot used by vLLM
- K3s, MinIO and `mc` binaries for update/model packs (not enabled by Starter)
- SHA-256 manifests and build provenance
Server L/Max models are intentionally separate signed model packs. Optional external
connectors cannot operate without a customer-approved route to their target.
## Build stages
1. On a connected Ubuntu 22.04 staging host with at least 80 GiB free, run
`sudo bash autoinstall/offline/build-bundle.sh`.
2. Review `autoinstall/offline-output/manifest/` and sign
`SHA256SUMS` with the release key.
3. Run `sudo bash autoinstall/build-iso-offline.sh` to inject the verified
payload into the Ubuntu Server ISO.
4. Validate with all network interfaces disconnected.
The bundle builder fails closed when an expected payload is absent. An ISO must
not be labelled offline merely because the source tree was embedded.

View File

@ -0,0 +1,27 @@
# Packages required in addition to the Ubuntu 22.04.5 Server media.
ansible
avahi-daemon
build-essential
ca-certificates
curl
ffmpeg
git
gnupg
htop
jq
libmupdf-dev
lsb-release
mupdf-tools
net-tools
nginx
openssh-server
python3-pip
python3-venv
python3.11
python3.11-venv
rsync
software-properties-common
ttyd
unzip
wget
whiptail

View File

@ -0,0 +1,115 @@
#!/usr/bin/env bash
# Stage all network-fetched Server Starter inputs for a disconnected install.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PACKAGE_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
OUTPUT_DIR="${OFFLINE_OUTPUT_DIR:-$PACKAGE_DIR/autoinstall/offline-output}"
PAYLOAD_DIR="$OUTPUT_DIR/payload"
MANIFEST_DIR="$OUTPUT_DIR/manifest"
# shellcheck source=payload.env
source "$SCRIPT_DIR/payload.env"
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
echo "ERROR: run as root on a connected Ubuntu 22.04 staging host." >&2
exit 1
fi
. /etc/os-release
if [ "${ID:-}" != ubuntu ] || [ "${VERSION_ID:-}" != 22.04 ]; then
echo "ERROR: Ubuntu 22.04 staging host required; found ${PRETTY_NAME:-unknown}." >&2
exit 1
fi
for command in apt-get curl docker git python3 rsync sha256sum tar; do
command -v "$command" >/dev/null || {
echo "ERROR: missing staging command: $command" >&2
exit 1
}
done
available_kb="$(df -Pk "$(dirname "$OUTPUT_DIR")" | awk 'NR==2 {print $4}')"
if [ "$available_kb" -lt $((80 * 1024 * 1024)) ]; then
echo "ERROR: at least 80 GiB free is required for the Workstation offline payload." >&2
exit 1
fi
rm -rf "$OUTPUT_DIR"
mkdir -p "$PAYLOAD_DIR"/{apt,containers,models,python,runtime,source} "$MANIFEST_DIR"
echo "-> Capturing pinned appliance source"
git -C "$PACKAGE_DIR" rev-parse HEAD > "$MANIFEST_DIR/source-commit.txt"
rsync -a --delete \
--exclude '.git/' --exclude '*.iso' --exclude 'offline-output/' \
--exclude 'how-it-flows.gif' \
"$PACKAGE_DIR/" "$PAYLOAD_DIR/source/aipackage/"
echo "-> Downloading Ubuntu package payload"
export DEBIAN_FRONTEND=noninteractive
mapfile -t apt_packages < <(sed -E '/^[[:space:]]*(#|$)/d' "$SCRIPT_DIR/apt-packages.txt")
apt-get update
apt-get install -y --download-only --reinstall \
-o "Dir::Cache::archives=$PAYLOAD_DIR/apt" "${apt_packages[@]}"
echo "-> Downloading Python wheelhouse"
python3 -m pip download --dest "$PAYLOAD_DIR/python" \
-r "$PACKAGE_DIR/ansible/roles/cezen-backend/files/requirements.txt" \
-r "$SCRIPT_DIR/python-packages.txt"
echo "-> Capturing Miniconda installer"
curl --fail --location --retry 3 "$MINICONDA_URL" \
--output "$PAYLOAD_DIR/runtime/miniconda.sh"
chmod 0755 "$PAYLOAD_DIR/runtime/miniconda.sh"
echo "-> Pulling and exporting container images"
: > "$MANIFEST_DIR/container-images.txt"
while IFS= read -r image; do
case "$image" in ''|'#'*) continue ;; esac
docker pull "$image"
safe_name="$(printf '%s' "$image" | tr '/:@' '___')"
docker save "$image" | gzip -1 > "$PAYLOAD_DIR/containers/$safe_name.tar.gz"
digest="$(docker image inspect "$image" --format '{{join .RepoDigests ","}}')"
printf '%s\t%s\n' "$image" "$digest" >> "$MANIFEST_DIR/container-images.txt"
done < "$SCRIPT_DIR/container-images.txt"
echo "-> Capturing Ollama runtime and Starter models"
if ! command -v ollama >/dev/null; then
curl --fail --location --retry 3 https://ollama.com/install.sh | sh
fi
systemctl start ollama 2>/dev/null || true
for model in $OLLAMA_MODELS; do ollama pull "$model"; done
tar -C / -czf "$PAYLOAD_DIR/runtime/ollama-runtime.tar.gz" \
usr/local/bin/ollama usr/local/lib/ollama
ollama_root="${OLLAMA_MODELS_DIR:-/usr/share/ollama/.ollama/models}"
[ -d "$ollama_root" ] || ollama_root="/root/.ollama/models"
[ -d "$ollama_root" ] || {
echo "ERROR: Ollama model store not found after pull." >&2
exit 1
}
tar -C "$(dirname "$ollama_root")" -czf "$PAYLOAD_DIR/models/ollama-models.tar.gz" models
echo "-> Capturing vLLM Starter model snapshot"
python3 -m pip install --quiet huggingface-hub
python3 - "$VLLM_MODEL" "$PAYLOAD_DIR/models/huggingface" <<'PY'
import sys
from huggingface_hub import snapshot_download
snapshot_download(repo_id=sys.argv[1], local_dir=sys.argv[2])
PY
echo "-> Writing provenance and integrity manifest"
{
printf 'schema=%s\n' "$OFFLINE_SCHEMA"
printf 'tier=%s\n' "$OFFLINE_TIER"
printf 'ubuntu_release=%s\n' "$UBUNTU_RELEASE"
printf 'built_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf 'builder=%s\n' "$(hostname -f 2>/dev/null || hostname)"
} > "$MANIFEST_DIR/provenance.env"
(
cd "$OUTPUT_DIR"
find payload manifest -type f ! -name SHA256SUMS -print0 \
| sort -z | xargs -0 sha256sum > manifest/SHA256SUMS
sha256sum -c manifest/SHA256SUMS >/dev/null
)
echo "Offline payload staged at $OUTPUT_DIR"
du -sh "$OUTPUT_DIR"

View File

@ -0,0 +1,7 @@
# Bundle builder records the resolved digest for each image.
ghcr.io/open-webui/open-webui:main
vllm/vllm-openai:v0.10.2
nvcr.io/nvidia/k8s/dcgm-exporter:3.3.0-3.2.0-ubuntu22.04
prom/prometheus:latest
prom/node-exporter:latest
grafana/grafana:latest

View File

@ -0,0 +1,8 @@
# Offline Server Starter release inputs. Mutable tags are resolved to immutable
# image digests in the generated provenance file at bundle-build time.
OFFLINE_SCHEMA="cezen.offline_payload.v1"
OFFLINE_TIER="workstation"
UBUNTU_RELEASE="22.04.5"
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh"
OLLAMA_MODELS="phi3:mini nomic-embed-text"
VLLM_MODEL="microsoft/Phi-3-mini-4k-instruct"

View File

@ -0,0 +1,16 @@
# Additional wheelhouse groups beyond backend requirements.txt.
chromadb==0.5.23
jupyterlab
ipywidgets
ipykernel
notebook
nbconvert
langchain
langchain-community
llama-index
transformers
huggingface-hub
peft
bitsandbytes
accelerate
sentence-transformers

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PACKAGE_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
OUTPUT_DIR="${OFFLINE_OUTPUT_DIR:-$PACKAGE_DIR/autoinstall/offline-output}"
required=(
manifest/provenance.env
manifest/source-commit.txt
manifest/container-images.txt
manifest/SHA256SUMS
payload/source/aipackage/install.sh
payload/runtime/miniconda.sh
payload/runtime/ollama-runtime.tar.gz
payload/models/ollama-models.tar.gz
)
for path in "${required[@]}"; do
[ -s "$OUTPUT_DIR/$path" ] || {
echo "ERROR: required offline payload is missing or empty: $path" >&2
exit 1
}
done
for directory in payload/apt payload/containers payload/python payload/models/huggingface; do
find "$OUTPUT_DIR/$directory" -type f -print -quit | grep -q . || {
echo "ERROR: required offline payload directory is empty: $directory" >&2
exit 1
}
done
(
cd "$OUTPUT_DIR"
sha256sum -c manifest/SHA256SUMS
)
echo "Offline payload integrity and completeness checks passed."