Skip to content

Commit 0753fe8

Browse files
committed
Automate sandbox services and release helpers
1 parent 9b9684a commit 0753fe8

37 files changed

Lines changed: 3003 additions & 126 deletions
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Smoke-test release helper scripts without building large images.
4+
#
5+
set -euo pipefail
6+
7+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
8+
cd "$ROOT"
9+
10+
shell_scripts=(
11+
scripts/release/secai-os-build-iso.sh
12+
scripts/release/secai-os-build-usb.sh
13+
scripts/release/secai-os-run-docker.sh
14+
)
15+
16+
for script in "${shell_scripts[@]}"; do
17+
echo "=== ${script} ==="
18+
test -f "$script"
19+
bash -n "$script"
20+
bash "$script" --help >/dev/null
21+
done
22+
23+
if command -v shellcheck >/dev/null 2>&1; then
24+
shellcheck -s bash "${shell_scripts[@]}" .github/scripts/check-release-installers.sh
25+
fi
26+
27+
bash scripts/release/secai-os-build-iso.sh \
28+
--dry-run \
29+
--tag v0.0.0 \
30+
--output-dir /tmp/secai-os-iso-smoke >/dev/null
31+
32+
bash scripts/release/secai-os-build-usb.sh \
33+
--dry-run \
34+
--tag v0.0.0 \
35+
--output-dir /tmp/secai-os-usb-smoke >/dev/null
36+
37+
bash scripts/release/secai-os-run-docker.sh \
38+
--dry-run \
39+
--tag v0.0.0 \
40+
--install-dir /tmp/secai-os-docker-smoke \
41+
--profile full-lab \
42+
--with-inference >/dev/null
43+
44+
pwsh_bin=""
45+
if command -v pwsh >/dev/null 2>&1; then
46+
pwsh_bin="pwsh"
47+
elif command -v powershell >/dev/null 2>&1; then
48+
pwsh_bin="powershell"
49+
fi
50+
51+
if [[ -n "$pwsh_bin" ]]; then
52+
# shellcheck disable=SC2016
53+
"$pwsh_bin" -NoProfile -Command \
54+
'$null = [scriptblock]::Create((Get-Content scripts/release/secai-os-run-docker.ps1 -Raw)); Write-Output "PowerShell parse OK"'
55+
"$pwsh_bin" -NoProfile -File scripts/release/secai-os-run-docker.ps1 \
56+
-DryRun \
57+
-Tag v0.0.0 \
58+
-InstallDir /tmp/secai-os-docker-ps-smoke \
59+
-Profile full-lab \
60+
-WithInference >/dev/null
61+
else
62+
echo "WARN: PowerShell not available; skipped .ps1 parse smoke"
63+
fi
64+
65+
echo "OK: release helper scripts passed smoke checks"

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ jobs:
134134
files/scripts/first-boot-check.sh \
135135
files/scripts/verify-release.sh
136136
137+
release-helper-smoke:
138+
name: Release Helper Script Smoke
139+
runs-on: ubuntu-latest
140+
permissions:
141+
contents: read
142+
steps:
143+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
144+
145+
- name: Validate release helper scripts
146+
run: bash .github/scripts/check-release-installers.sh
147+
137148
appsec-lint:
138149
name: Hadolint & Semgrep
139150
runs-on: ubuntu-latest
@@ -290,6 +301,11 @@ jobs:
290301
{ echo "FAIL: release.yml missing '${keyword}'"; exit 1; }
291302
echo "OK: release.yml contains '${keyword}'"
292303
done
304+
for helper in "secai-os-build-iso.sh" "secai-os-build-usb.sh" "secai-os-run-docker.sh" "secai-os-run-docker.ps1"; do
305+
grep -q "${helper}" .github/workflows/release.yml || \
306+
{ echo "FAIL: release.yml missing release helper '${helper}'"; exit 1; }
307+
echo "OK: release.yml publishes '${helper}'"
308+
done
293309
294310
# Verify build workflow has SBOM attestation
295311
test -f .github/workflows/build.yml || { echo "FAIL: build.yml missing"; exit 1; }

.github/workflows/release.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ jobs:
4242
"Python Test & Lint"
4343
"Security Regression Tests"
4444
"Hadolint & Semgrep"
45+
"Release Helper Script Smoke"
4546
"Dependency Vulnerability Audit"
4647
"Test Count Drift Check"
4748
"Documentation Validation"
@@ -400,6 +401,13 @@ jobs:
400401
path: dist/
401402
merge-multiple: true
402403

404+
- name: Stage release helper scripts
405+
run: |
406+
install -m 0755 scripts/release/secai-os-build-iso.sh dist/secai-os-build-iso.sh
407+
install -m 0755 scripts/release/secai-os-build-usb.sh dist/secai-os-build-usb.sh
408+
install -m 0755 scripts/release/secai-os-run-docker.sh dist/secai-os-run-docker.sh
409+
install -m 0644 scripts/release/secai-os-run-docker.ps1 dist/secai-os-run-docker.ps1
410+
403411
- name: Record release image digest
404412
run: |
405413
IMAGE_REF="ghcr.io/${{ github.repository }}"
@@ -451,6 +459,16 @@ jobs:
451459
'. + [$name]')
452460
done
453461
462+
# Collect release helper scripts
463+
RELEASE_SCRIPTS_JSON="[]"
464+
for helper in secai-os-build-iso.sh secai-os-build-usb.sh secai-os-run-docker.sh secai-os-run-docker.ps1; do
465+
[ -f "$helper" ] || continue
466+
HASH=$(sha256sum "$helper" | awk '{print $1}')
467+
RELEASE_SCRIPTS_JSON=$(echo "$RELEASE_SCRIPTS_JSON" | jq \
468+
--arg name "$helper" --arg sha256 "$HASH" \
469+
'. + [{"name": $name, "sha256": $sha256}]')
470+
done
471+
454472
# Read image digest
455473
IMAGE_DIGEST="unknown"
456474
if [ -f IMAGE_DIGEST ]; then
@@ -471,6 +489,7 @@ jobs:
471489
--argjson binaries "$BINARIES_JSON" \
472490
--argjson sboms "$SBOMS_JSON" \
473491
--argjson vex "$VEX_JSON" \
492+
--argjson release_scripts "$RELEASE_SCRIPTS_JSON" \
474493
--arg provenance_type "https://slsa.dev/provenance/v1" \
475494
--arg checksum_file "SHA256SUMS" \
476495
--arg signature_file "SHA256SUMS.sig" \
@@ -489,6 +508,7 @@ jobs:
489508
binaries: $binaries,
490509
sboms: $sboms,
491510
vex: $vex,
511+
release_scripts: $release_scripts,
492512
provenance: {
493513
type: $provenance_type,
494514
attested: true
@@ -577,6 +597,10 @@ jobs:
577597
dist/IMAGE_DIGEST
578598
dist/IMAGE_REF_PINNED
579599
dist/RELEASE_MANIFEST.json
600+
dist/secai-os-build-iso.sh
601+
dist/secai-os-build-usb.sh
602+
dist/secai-os-run-docker.sh
603+
dist/secai-os-run-docker.ps1
580604
dist/secai-os-*.iso.sig
581605
dist/secai-os-*-usb.raw.xz.sig
582606
dist/secai-os-*.qcow2.sig

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ GO_SERVICES := airlock registry tool-firewall gpu-integrity-watch mcp-firewall \
1616
policy-engine runtime-attestor integrity-monitor incident-recorder
1717

1818
SCRIPTS_LIBEXEC := $(wildcard files/system/usr/libexec/secure-ai/*.sh)
19-
SCRIPTS_FILES := $(wildcard files/scripts/*.sh) $(wildcard .github/scripts/*.sh)
19+
SCRIPTS_FILES := $(wildcard files/scripts/*.sh) $(wildcard .github/scripts/*.sh) \
20+
$(wildcard scripts/release/*.sh)
2021

2122
# ---------------------------------------------------------------------------
2223
# Targets

deploy/sandbox/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
# UI is the only service published to the host by default.
55
SECAI_UI_PORT=8480
66

7+
# Host-side sandbox controller used by the UI for profile/service automation.
8+
# It binds to 127.0.0.1 and accepts only token-authenticated allowlisted actions.
9+
SECAI_CONTROL_PORT=8498
10+
711
# Optional profiles:
812
# scripts/sandbox/start.* --with-inference
913
# scripts/sandbox/start.* --with-diffusion

deploy/sandbox/compose.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ services:
307307
SECURE_AI_DEPLOYMENT_MODE: sandbox
308308
SECURE_AI_DEPLOYMENT_PROVIDER: compose
309309
SECURE_AI_ASSURANCE_TIER: evaluation
310+
SANDBOX_CONTROL_URL: http://host.docker.internal:${SECAI_CONTROL_PORT:-8498}
311+
SANDBOX_CONTROL_TOKEN_PATH: /run/secrets/sandbox-control-token
310312
GUNICORN_WORKERS: 1
311313
GUNICORN_THREADS: 4
312314
GUNICORN_TIMEOUT: 60
@@ -316,10 +318,14 @@ services:
316318
- secai-state:/var/lib/secure-ai
317319
- secai-run:/run/secure-ai
318320
- ./runtime/config:/etc/secure-ai/config:ro
321+
- ./runtime/model-catalog.yaml:/etc/secure-ai/model-catalog.yaml:ro
319322
- ./runtime/service-token:/run/secrets/service-token:ro
323+
- ./runtime/control-token:/run/secrets/sandbox-control-token:ro
320324
tmpfs:
321325
- /tmp:rw,noexec,nosuid,nodev,size=64m
322326
- /run/secure-ai-ui:rw,noexec,nosuid,nodev,mode=0770,uid=65534,gid=65534,size=16m
327+
extra_hosts:
328+
- "host.docker.internal:host-gateway"
323329
ports:
324330
- "${SECAI_UI_PORT:-8480}:8480"
325331
healthcheck:

docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Proxy an outbound request through the Airlock.
107107
}
108108
```
109109
- **Error:** `429 Too Many Requests` -- rate limit exceeded
110-
- **Error:** `503 Service Unavailable` -- Airlock is disabled
110+
- **Error:** `503 Service Unavailable` -- Airlock policy is disabled in the current runtime profile
111111

112112
---
113113

docs/install/quickstart.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ Common flags:
180180
- `--with-airlock` / `-WithAirlock` turns on airlock-mediated outbound downloads in the sandbox runtime policy.
181181
- `--with-inference` / `-WithInference` and `--with-diffusion` / `-WithDiffusion` enable the heavier model-serving profiles.
182182

183+
The sandbox launcher now starts a loopback-only, token-authenticated host
184+
controller so the UI can apply these same profile changes from Settings and
185+
service-specific buttons without mounting the Docker socket into the UI
186+
container.
187+
183188
> **Security note:** This is a lower-assurance path than the full OS or VM image. The host kernel and container runtime can inspect container memory, mounted files, and network activity. Use it for evaluation and workflow testing, not sensitive workloads.
184189
185190
---

docs/install/sandbox.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ The helper script will:
5757

5858
1. Create `deploy/sandbox/.env` from the template if needed.
5959
2. Generate a per-stack service token in `deploy/sandbox/runtime/service-token`.
60-
3. Render a runtime policy/config overlay for the selected profiles.
61-
4. Build, harden, and wait for the sandbox services to become healthy.
60+
3. Generate a separate host-control token in `deploy/sandbox/runtime/control-token`.
61+
4. Start a loopback-only host controller used by the UI for profile/service automation.
62+
5. Render a runtime policy/config overlay for the selected profiles.
63+
6. Build, harden, and wait for the sandbox services to become healthy.
6264

6365
Then open:
6466

@@ -70,6 +72,13 @@ http://127.0.0.1:8480
7072

7173
The default stack starts the control plane only. Inference and diffusion are opt-in because they are heavier and usually need user-supplied model paths or extra runtime dependencies.
7274

75+
When the stack is started through `secai-sandbox.cmd` or `scripts/sandbox/start.*`,
76+
the UI can start these profiles for you from **Settings**, **Chat**, **Models**, or
77+
**Generate**. The UI does not receive the Docker socket; it calls a host-side
78+
controller on `127.0.0.1:${SECAI_CONTROL_PORT:-8498}` with a random bearer token
79+
mounted read-only into the UI container. The controller only accepts allowlisted
80+
profile actions.
81+
7382
**Enable local LLM inference**
7483

7584
1. Edit `deploy/sandbox/.env`.
@@ -128,6 +137,12 @@ example:
128137
bash scripts/sandbox/start.sh --with-search --with-airlock --with-inference
129138
```
130139

140+
On Windows, the convenience launcher also supports restart options:
141+
142+
```powershell
143+
.\secai-sandbox.cmd restart --with-search --with-inference
144+
```
145+
131146
## Stop The Stack
132147

133148
**Windows (one-command launcher from the repo root)**

docs/release-artifacts.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@
5151
"image_digest": {
5252
"files": ["IMAGE_DIGEST"],
5353
"description": "OCI image digest for this release"
54+
},
55+
"release_scripts": {
56+
"files": [
57+
"secai-os-build-iso.sh",
58+
"secai-os-build-usb.sh",
59+
"secai-os-run-docker.sh",
60+
"secai-os-run-docker.ps1"
61+
],
62+
"description": "Standalone helper scripts for building ISO/USB media and launching the Docker sandbox from a release"
5463
}
5564
},
5665
"optional": {

0 commit comments

Comments
 (0)