Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
277 lines
8.5 KiB
Bash
277 lines
8.5 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ORCH-SVC-34-004: Build air-gap bundle for Orchestrator service
|
|
# Packages container images, configs, and manifests for offline deployment.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
VERSION="${VERSION:-2025.10.0-edge}"
|
|
CHANNEL="${CHANNEL:-edge}"
|
|
BUNDLE_DIR="${BUNDLE_DIR:-$REPO_ROOT/out/bundles/orchestrator-${VERSION}}"
|
|
SRC_DIR="${SRC_DIR:-$REPO_ROOT/out/buildx/orchestrator}"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 [options]
|
|
|
|
Build an air-gap bundle for StellaOps Orchestrator service.
|
|
|
|
Options:
|
|
--version VERSION Bundle version (default: $VERSION)
|
|
--channel CHANNEL Release channel (default: $CHANNEL)
|
|
--output DIR Output bundle directory (default: $BUNDLE_DIR)
|
|
--source DIR Source buildx directory (default: $SRC_DIR)
|
|
--skip-images Skip OCI image export (use existing)
|
|
--help Show this help
|
|
|
|
Environment variables:
|
|
VERSION, CHANNEL, BUNDLE_DIR, SRC_DIR
|
|
|
|
Examples:
|
|
$0 --version 2025.10.0 --channel stable
|
|
VERSION=2025.10.0 CHANNEL=stable $0
|
|
EOF
|
|
exit "${1:-0}"
|
|
}
|
|
|
|
SKIP_IMAGES=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--version) VERSION="$2"; shift 2 ;;
|
|
--channel) CHANNEL="$2"; shift 2 ;;
|
|
--output) BUNDLE_DIR="$2"; shift 2 ;;
|
|
--source) SRC_DIR="$2"; shift 2 ;;
|
|
--skip-images) SKIP_IMAGES=true; shift ;;
|
|
--help) usage 0 ;;
|
|
*) echo "Unknown option: $1" >&2; usage 64 ;;
|
|
esac
|
|
done
|
|
|
|
BUNDLE_DIR="${BUNDLE_DIR:-$REPO_ROOT/out/bundles/orchestrator-${VERSION}}"
|
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
echo "[orchestrator-airgap] Building bundle v${VERSION} (${CHANNEL})"
|
|
echo "[orchestrator-airgap] Output: ${BUNDLE_DIR}"
|
|
|
|
mkdir -p "$BUNDLE_DIR"/{images,configs,manifests,docs}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Stage 1: Export container images as OCI archives
|
|
# ------------------------------------------------------------------------------
|
|
if [[ "$SKIP_IMAGES" == "false" ]]; then
|
|
echo "[orchestrator-airgap] Exporting container images..."
|
|
|
|
IMAGES=(
|
|
"orchestrator-web:${VERSION}"
|
|
"orchestrator-worker:${VERSION}"
|
|
)
|
|
|
|
for img in "${IMAGES[@]}"; do
|
|
img_name="${img%%:*}"
|
|
img_file="${BUNDLE_DIR}/images/${img_name}.oci.tar.gz"
|
|
|
|
if [[ -f "${SRC_DIR}/${img_name}/image.oci" ]]; then
|
|
echo "[orchestrator-airgap] Packaging ${img_name} from buildx output..."
|
|
gzip -c "${SRC_DIR}/${img_name}/image.oci" > "$img_file"
|
|
else
|
|
echo "[orchestrator-airgap] Exporting ${img_name} via docker save..."
|
|
docker save "registry.stella-ops.org/stellaops/${img}" | gzip > "$img_file"
|
|
fi
|
|
|
|
# Generate checksum
|
|
sha256sum "$img_file" | cut -d' ' -f1 > "${img_file}.sha256"
|
|
|
|
# Copy SBOM if available
|
|
if [[ -f "${SRC_DIR}/${img_name}/sbom.syft.json" ]]; then
|
|
cp "${SRC_DIR}/${img_name}/sbom.syft.json" "${BUNDLE_DIR}/manifests/${img_name}.sbom.json"
|
|
fi
|
|
done
|
|
else
|
|
echo "[orchestrator-airgap] Skipping image export (--skip-images)"
|
|
fi
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Stage 2: Copy configuration templates
|
|
# ------------------------------------------------------------------------------
|
|
echo "[orchestrator-airgap] Copying configuration templates..."
|
|
|
|
# Helm values overlay
|
|
if [[ -f "$REPO_ROOT/deploy/helm/stellaops/values-orchestrator.yaml" ]]; then
|
|
cp "$REPO_ROOT/deploy/helm/stellaops/values-orchestrator.yaml" \
|
|
"${BUNDLE_DIR}/configs/values-orchestrator.yaml"
|
|
fi
|
|
|
|
# Sample configuration
|
|
if [[ -f "$REPO_ROOT/etc/orchestrator.yaml.sample" ]]; then
|
|
cp "$REPO_ROOT/etc/orchestrator.yaml.sample" \
|
|
"${BUNDLE_DIR}/configs/orchestrator.yaml.sample"
|
|
fi
|
|
|
|
# PostgreSQL migration scripts
|
|
if [[ -d "$REPO_ROOT/src/Orchestrator/StellaOps.Orchestrator/migrations" ]]; then
|
|
mkdir -p "${BUNDLE_DIR}/configs/migrations"
|
|
cp "$REPO_ROOT/src/Orchestrator/StellaOps.Orchestrator/migrations/"*.sql \
|
|
"${BUNDLE_DIR}/configs/migrations/" 2>/dev/null || true
|
|
fi
|
|
|
|
# Bootstrap secrets template
|
|
cat > "${BUNDLE_DIR}/configs/secrets.env.example" <<'SECRETS_EOF'
|
|
# Orchestrator Secrets Template
|
|
# Copy to secrets.env and fill in values before deployment
|
|
|
|
# PostgreSQL password (required)
|
|
POSTGRES_PASSWORD=
|
|
|
|
# Authority JWT signing key (if using local Authority)
|
|
AUTHORITY_SIGNING_KEY=
|
|
|
|
# OpenTelemetry endpoint (optional)
|
|
OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
|
|
# Tenant encryption key for multi-tenant isolation (optional)
|
|
TENANT_ENCRYPTION_KEY=
|
|
SECRETS_EOF
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Stage 3: Generate bundle manifest
|
|
# ------------------------------------------------------------------------------
|
|
echo "[orchestrator-airgap] Generating bundle manifest..."
|
|
|
|
# Calculate checksums for all bundle files
|
|
MANIFEST_FILE="${BUNDLE_DIR}/manifests/bundle-manifest.json"
|
|
|
|
# Build file list with checksums
|
|
FILES_JSON="[]"
|
|
while IFS= read -r -d '' file; do
|
|
rel_path="${file#$BUNDLE_DIR/}"
|
|
if [[ "$rel_path" != "manifests/bundle-manifest.json" ]]; then
|
|
sha=$(sha256sum "$file" | cut -d' ' -f1)
|
|
size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo "0")
|
|
FILES_JSON=$(echo "$FILES_JSON" | jq --arg name "$rel_path" --arg sha "$sha" --arg size "$size" \
|
|
'. + [{"name": $name, "sha256": $sha, "size": ($size | tonumber)}]')
|
|
fi
|
|
done < <(find "$BUNDLE_DIR" -type f -print0 | sort -z)
|
|
|
|
cat > "$MANIFEST_FILE" <<EOF
|
|
{
|
|
"bundle": {
|
|
"name": "stellaops-orchestrator",
|
|
"version": "${VERSION}",
|
|
"channel": "${CHANNEL}",
|
|
"createdAt": "${TIMESTAMP}",
|
|
"components": [
|
|
{
|
|
"name": "orchestrator-web",
|
|
"type": "container",
|
|
"image": "registry.stella-ops.org/stellaops/orchestrator-web:${VERSION}"
|
|
},
|
|
{
|
|
"name": "orchestrator-worker",
|
|
"type": "container",
|
|
"image": "registry.stella-ops.org/stellaops/orchestrator-worker:${VERSION}"
|
|
},
|
|
{
|
|
"name": "orchestrator-postgres",
|
|
"type": "infrastructure",
|
|
"image": "docker.io/library/postgres:16-alpine"
|
|
}
|
|
]
|
|
},
|
|
"files": ${FILES_JSON}
|
|
}
|
|
EOF
|
|
|
|
# Checksum the manifest itself
|
|
sha256sum "$MANIFEST_FILE" | cut -d' ' -f1 > "${MANIFEST_FILE}.sha256"
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Stage 4: Copy documentation
|
|
# ------------------------------------------------------------------------------
|
|
echo "[orchestrator-airgap] Copying documentation..."
|
|
|
|
# Module architecture
|
|
if [[ -f "$REPO_ROOT/docs/modules/orchestrator/architecture.md" ]]; then
|
|
cp "$REPO_ROOT/docs/modules/orchestrator/architecture.md" \
|
|
"${BUNDLE_DIR}/docs/architecture.md"
|
|
fi
|
|
|
|
# GA checklist
|
|
if [[ -f "$REPO_ROOT/ops/orchestrator/GA_CHECKLIST.md" ]]; then
|
|
cp "$REPO_ROOT/ops/orchestrator/GA_CHECKLIST.md" \
|
|
"${BUNDLE_DIR}/docs/GA_CHECKLIST.md"
|
|
fi
|
|
|
|
# Quick deployment guide
|
|
cat > "${BUNDLE_DIR}/docs/DEPLOY.md" <<'DEPLOY_EOF'
|
|
# Orchestrator Air-Gap Deployment Guide
|
|
|
|
## Prerequisites
|
|
|
|
- Docker or containerd runtime
|
|
- Kubernetes 1.28+ (for Helm deployment) or Docker Compose
|
|
- PostgreSQL 16+ (included as container or external)
|
|
|
|
## Quick Start (Docker)
|
|
|
|
1. Load images:
|
|
```bash
|
|
for img in images/*.oci.tar.gz; do
|
|
gunzip -c "$img" | docker load
|
|
done
|
|
```
|
|
|
|
2. Configure secrets:
|
|
```bash
|
|
cp configs/secrets.env.example secrets.env
|
|
# Edit secrets.env with your values
|
|
```
|
|
|
|
3. Start services:
|
|
```bash
|
|
docker compose -f docker-compose.orchestrator.yaml up -d
|
|
```
|
|
|
|
## Helm Deployment
|
|
|
|
1. Import images to registry:
|
|
```bash
|
|
for img in images/*.oci.tar.gz; do
|
|
crane push "$img" your-registry.local/stellaops/$(basename "$img" .oci.tar.gz)
|
|
done
|
|
```
|
|
|
|
2. Install chart:
|
|
```bash
|
|
helm upgrade --install stellaops ./stellaops \
|
|
-f configs/values-orchestrator.yaml \
|
|
--set global.imageRegistry=your-registry.local
|
|
```
|
|
|
|
## Verification
|
|
|
|
Check health endpoints:
|
|
```bash
|
|
curl http://localhost:8080/healthz
|
|
curl http://localhost:8080/readyz
|
|
```
|
|
DEPLOY_EOF
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Stage 5: Create final tarball
|
|
# ------------------------------------------------------------------------------
|
|
echo "[orchestrator-airgap] Creating final tarball..."
|
|
|
|
TARBALL="${BUNDLE_DIR}.tar.gz"
|
|
tar -C "$(dirname "$BUNDLE_DIR")" -czf "$TARBALL" "$(basename "$BUNDLE_DIR")"
|
|
|
|
# Checksum the tarball
|
|
sha256sum "$TARBALL" | cut -d' ' -f1 > "${TARBALL}.sha256"
|
|
|
|
echo "[orchestrator-airgap] Bundle created successfully:"
|
|
echo " Tarball: ${TARBALL}"
|
|
echo " SHA256: $(cat "${TARBALL}.sha256")"
|
|
echo " Size: $(du -h "$TARBALL" | cut -f1)"
|