Skip to content

Commit 6135c05

Browse files
committed
Fix USB boot flow and harden boot compatibility
1 parent edd7fa9 commit 6135c05

16 files changed

Lines changed: 386 additions & 28 deletions

.github/workflows/release.yml

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,59 @@ jobs:
207207
name: iso-amd64
208208
path: dist/secai-os-*.iso*
209209

210+
build-usb-image:
211+
name: Build Portable USB Image
212+
needs: [preflight]
213+
runs-on: ubuntu-latest
214+
steps:
215+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
216+
217+
- name: Free disk space
218+
run: |
219+
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc
220+
sudo docker image prune -af
221+
222+
- name: Install image build dependencies
223+
run: |
224+
sudo apt-get update
225+
sudo apt-get install -y podman xz-utils
226+
227+
- name: Build portable USB image
228+
run: |
229+
mkdir -p dist
230+
sudo bash scripts/build-usb-image.sh \
231+
--image-ref "ghcr.io/secai-hub/secai_os:latest" \
232+
--output-dir ./dist \
233+
--version "${{ github.ref_name }}"
234+
235+
- name: Install cosign
236+
run: |
237+
COSIGN_VERSION="v2.4.3"
238+
curl -sSfL "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64" \
239+
-o /usr/local/bin/cosign
240+
chmod +x /usr/local/bin/cosign
241+
242+
- name: Sign portable USB image
243+
run: |
244+
USB_IMAGE=$(find dist -maxdepth 1 -name "secai-os-*-usb.raw.xz" -type f | head -1)
245+
if [ -z "$USB_IMAGE" ]; then
246+
echo "ERROR: portable USB image not found after build"
247+
find dist -maxdepth 2 -type f
248+
exit 1
249+
fi
250+
cosign sign-blob --yes \
251+
--key env://COSIGN_PRIVATE_KEY \
252+
--output-signature "${USB_IMAGE}.sig" \
253+
"$USB_IMAGE"
254+
env:
255+
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
256+
257+
- name: Upload portable USB artifact
258+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
259+
with:
260+
name: usb-amd64
261+
path: dist/secai-os-*-usb.raw.xz*
262+
210263
build-vm-images:
211264
name: Build VM Images (QCOW2 + OVA)
212265
needs: [preflight]
@@ -253,7 +306,7 @@ jobs:
253306
provenance:
254307
name: SLSA Provenance & Attestation
255308
runs-on: ubuntu-latest
256-
needs: [build-go, build-python, build-iso]
309+
needs: [build-go, build-python, build-iso, build-usb-image]
257310
steps:
258311
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
259312

@@ -365,11 +418,14 @@ jobs:
365418
366419
# Add install artifacts (conditionally present)
367420
INSTALL_JSON="{}"
368-
for artifact in secai-os-*.iso secai-os-*.qcow2 secai-os-*.ova; do
421+
for artifact in secai-os-*.iso secai-os-*.qcow2 secai-os-*.ova secai-os-*-usb.raw.xz; do
369422
[ -f "$artifact" ] || continue
370423
HASH=$(sha256sum "$artifact" | awk '{print $1}')
371424
SIZE=$(stat -c%s "$artifact" 2>/dev/null || stat -f%z "$artifact" 2>/dev/null || echo 0)
372425
TYPE="${artifact##*.}"
426+
if [[ "$artifact" == *.raw.xz ]]; then
427+
TYPE="usb_raw_xz"
428+
fi
373429
INSTALL_JSON=$(echo "$INSTALL_JSON" | jq \
374430
--arg type "$TYPE" --arg name "$artifact" \
375431
--arg sha256 "$HASH" --arg size "$SIZE" \
@@ -430,23 +486,24 @@ jobs:
430486
dist/IMAGE_REF_PINNED
431487
dist/RELEASE_MANIFEST.json
432488
dist/secai-os-*.iso.sig
489+
dist/secai-os-*-usb.raw.xz.sig
433490
dist/secai-os-*.qcow2.sig
434491
dist/secai-os-*.ova.sig
435492
generate_release_notes: true
436493
fail_on_unmatched_files: false
437494

438-
# ISO/QCOW2/OVA files are too large for GitHub Releases (>2GB limit).
495+
# Install media files are too large for GitHub Releases (>2GB limit).
439496
# Upload signatures only (above). The full images are available as
440497
# workflow artifacts or should be hosted on external storage.
441498
- name: Note on large artifacts
442499
if: ${{ !inputs.dry_run }}
443500
run: |
444501
echo "## Large Artifacts" >> "$GITHUB_STEP_SUMMARY"
445502
echo "" >> "$GITHUB_STEP_SUMMARY"
446-
echo "ISO/QCOW2/OVA files exceed GitHub Releases' 2GB limit." >> "$GITHUB_STEP_SUMMARY"
503+
echo "Install media files can exceed GitHub Releases' 2GB limit." >> "$GITHUB_STEP_SUMMARY"
447504
echo "Their cosign signatures (.sig) are included in the release." >> "$GITHUB_STEP_SUMMARY"
448505
echo "Full images are available as workflow artifacts (90-day retention)." >> "$GITHUB_STEP_SUMMARY"
449-
for f in dist/secai-os-*.iso dist/secai-os-*.qcow2 dist/secai-os-*.ova; do
506+
for f in dist/secai-os-*.iso dist/secai-os-*-usb.raw.xz dist/secai-os-*.qcow2 dist/secai-os-*.ova; do
450507
[ -f "$f" ] || continue
451508
SIZE=$(stat -c%s "$f" 2>/dev/null || echo 0)
452509
SIZE_MB=$((SIZE / 1048576))

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ dist/
3333
# OS
3434
.DS_Store
3535
Thumbs.db
36+
/.bluebuild-scripts_*

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ The setup wizard guides you through privacy profile selection, system verificati
6767
| Method | Time | Best For | Details |
6868
|--------|------|----------|---------|
6969
| **Bootstrap** (Recommended) | ~30 min | Real PC or VM | Install Fedora Silverblue, run script, reboot |
70+
| **Portable USB** | ~10 min | Run directly from a USB stick | Flash the release `*-usb.raw.xz` artifact to removable media |
7071
| **Build VM locally** | ~45 min | VirtualBox / VMware / KVM | `scripts/vm/build-qcow2.sh` builds a QCOW2 from the OCI image |
7172
| **Development** | ~10 min | Service development only | No OS features; see [dev guide](docs/install/dev.md) |
7273

@@ -204,18 +205,20 @@ Tagged releases (`v*`) are built by the [Release workflow](.github/workflows/rel
204205
| `IMAGE_DIGEST` | OCI image digest for this release |
205206
| `RELEASE_MANIFEST.json` | Machine-readable release manifest (binaries, SBOMs, provenance, build metadata) |
206207
| `secai-os-*.iso.sig` | Cosign signature for the bootable ISO |
208+
| `secai-os-*-usb.raw.xz.sig` | Cosign signature for the portable USB image |
207209

208210
Go services shipped as release binaries: `airlock`, `registry`, `tool-firewall`, `gpu-integrity-watch`, `mcp-firewall`, `policy-engine`, `runtime-attestor`, `integrity-monitor`, `incident-recorder`.
209211

210212
Python services (`ui`, `agent`, `quarantine`, `diffusion-worker`, `search-mediator`) are baked into the OCI image and do not ship as standalone binaries.
211213

212-
### Bootable ISO
214+
### Bootable Media
213215

214-
A signed bootable ISO is built by every tagged release using [build-container-installer](https://github.com/JasonN3/build-container-installer). The ISO exceeds GitHub's 2 GB release asset limit, so it is available as a **workflow artifact** (90-day retention) from the [Release workflow runs](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/release.yml). The cosign signature (`.iso.sig`) is published to the GitHub Release for verification.
216+
A signed bootable installer ISO is built by every tagged release using [build-container-installer](https://github.com/JasonN3/build-container-installer). Each release also includes a compressed portable USB image (`secai-os-*-usb.raw.xz`) built from the same bootc container so the OS can be flashed directly to a USB stick and run without first installing to the internal disk. Both artifacts are available as **workflow artifacts** (90-day retention) from the [Release workflow runs](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/release.yml), and their cosign signatures are published to the GitHub Release for verification.
215217

216-
To build a QCOW2 or OVA locally from the OCI image:
218+
To build portable USB or VM media locally from the OCI image:
217219

218220
```bash
221+
bash scripts/build-usb-image.sh # produces output/secai-os-<version>-x86_64-usb.raw(.xz)
219222
bash scripts/vm/build-qcow2.sh # produces output/secai-os.qcow2
220223
bash scripts/vm/build-ova.sh # produces output/secai-os.ova
221224
```

docs/install/quickstart.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ Get SecAI OS running in the fewest steps possible. Choose the path that fits you
77
| Method | Time | Difficulty | Best For |
88
|--------|------|-----------|----------|
99
| **Bootstrap** (Recommended) | ~30 min | Easy | Real PC or VM, full security |
10+
| **Portable USB** | ~10 min | Easy | Run directly from removable media without installing first |
1011
| **VM Build** | ~45 min | Moderate | Local evaluation in VirtualBox/VMware/KVM |
1112
| **Development** | ~10 min | Easy | Service development only (no OS features) |
1213

13-
> **Note on ISO/OVA/QCOW2:** The release pipeline builds a signed bootable ISO, but it exceeds GitHub's 2 GB release asset limit. Pre-built VM images (OVA/QCOW2) require build infrastructure not yet provisioned. For now, the bootstrap path below is the primary install method. See [Artifact Availability](#artifact-availability) for details.
14+
> **Note on release media:** The release pipeline builds both an installer ISO and a portable USB image (`*-usb.raw.xz`). Pre-built VM images (OVA/QCOW2) still require build infrastructure not yet provisioned. The bootstrap path remains the recommended production install, but the portable USB artifact is the right choice when you want to boot and evaluate directly from removable media. See [Artifact Availability](#artifact-availability) for details.
1415
1516
---
1617

@@ -160,11 +161,13 @@ make verify-release
160161
|----------|-------|--------|
161162
| **OCI image** | `ghcr.io/secai-hub/secai_os:latest` | Always available, cosign-signed |
162163
| **Go binaries + SBOMs** | [GitHub Releases](https://github.com/SecAI-Hub/SecAI_OS/releases/latest) | Always available |
163-
| **ISO** | Release workflow artifact (90-day retention) | Built in CI; too large (~4 GB) for GitHub Releases |
164+
| **Installer ISO** | Release workflow artifact (90-day retention) | Built in CI; intended for install-to-disk |
164165
| **ISO signature** | [GitHub Releases](https://github.com/SecAI-Hub/SecAI_OS/releases/latest) | `.iso.sig` file for verification |
166+
| **Portable USB image** | Release workflow artifact (90-day retention) | Built in CI as `secai-os-*-usb.raw.xz`; flash directly to removable media |
167+
| **Portable USB signature** | [GitHub Releases](https://github.com/SecAI-Hub/SecAI_OS/releases/latest) | `.raw.xz.sig` file for verification |
165168
| **QCOW2 / OVA** | `scripts/vm/build-qcow2.sh` / `build-ova.sh` | Build locally; CI build requires self-hosted KVM runner |
166169

167-
The ISO is produced by every tagged release and is available as a [workflow artifact](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/release.yml) with 90-day retention. Its cosign signature (`.iso.sig`) is published to GitHub Releases for verification. For permanent ISO hosting, an external storage solution is needed.
170+
The installer ISO and portable USB image are produced by every tagged release and are available as [workflow artifacts](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/release.yml) with 90-day retention. Their cosign signatures are published to GitHub Releases for verification. For permanent hosting, an external storage solution is still needed.
168171

169172
---
170173

docs/release-artifacts.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@
5656
"description": "Bootable ISO (requires isogenerator)",
5757
"required_when": "always (standard runner)"
5858
},
59+
"portable_usb": {
60+
"pattern": "secai-os-{version}-x86_64-usb.raw.xz",
61+
"signature": "secai-os-{version}-x86_64-usb.raw.xz.sig",
62+
"description": "Direct-flash portable USB image built from bootc-image-builder raw output",
63+
"required_when": "always (standard runner)"
64+
},
5965
"qcow2": {
6066
"pattern": "secai-os-{version}.qcow2",
6167
"signature": "secai-os-{version}.qcow2.sig",

docs/release-policy.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,13 @@ Each tagged release may include bootable install artifacts in addition to the OC
152152
|----------|--------|-------------|----------|
153153
| OCI image | Container | BlueBuild (build.yml) | Always |
154154
| ISO | Bootable installer | isogenerator (release.yml) | Always |
155+
| Portable USB | Direct-flash raw.xz | bootc-image-builder raw + xz (release.yml) | Always |
155156
| QCOW2 | KVM/QEMU disk image | build-qcow2.sh on KVM runner | When `vars.HAS_KVM_RUNNER` is set |
156157
| OVA | VirtualBox/VMware appliance | build-ova.sh on KVM runner | When `vars.HAS_KVM_RUNNER` is set |
157158

158159
All install artifacts are built from the same OCI image. After installation, the upgrade path is identical regardless of install method: `rpm-ostree upgrade`.
159160

160-
QCOW2 and OVA may be absent in releases if the repository does not have a self-hosted KVM runner configured. The ISO is always produced on standard GitHub runners.
161+
QCOW2 and OVA may be absent in releases if the repository does not have a self-hosted KVM runner configured. The installer ISO and portable USB image are produced on standard GitHub runners.
161162

162163
See [release-artifacts.json](release-artifacts.json) for the machine-readable specification of expected artifacts.
163164

docs/sample-release-bundle.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ v1.0.0/
5353
# Install artifacts (bootable images)
5454
secai-os-v1.0.0-x86_64.iso # Bootable ISO (from isogenerator)
5555
secai-os-v1.0.0-x86_64.iso.sig # cosign detached signature
56+
secai-os-v1.0.0-x86_64-usb.raw.xz # Portable USB image (direct-flash)
57+
secai-os-v1.0.0-x86_64-usb.raw.xz.sig # cosign detached signature
5658
secai-os-v1.0.0.qcow2 # QCOW2 disk image (optional — requires KVM build infra)
5759
secai-os-v1.0.0.qcow2.sig # cosign detached signature
5860
secai-os-v1.0.0.ova # OVA appliance (optional — requires KVM build infra)
@@ -64,8 +66,9 @@ v1.0.0/
6466
```
6567

6668
> **Note:** QCOW2 and OVA artifacts may be absent if the repository does not have
67-
> a self-hosted KVM runner. The ISO is always produced. See `docs/release-artifacts.json`
68-
> for the machine-readable artifact specification.
69+
> a self-hosted KVM runner. The installer ISO and portable USB image are produced
70+
> on standard runners. See `docs/release-artifacts.json` for the machine-readable
71+
> artifact specification.
6972
7073
## Image Digest
7174

files/scripts/verify-release.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ echo ""
336336
info "Step 5: Verifying install artifact signatures (if present)..."
337337

338338
install_artifacts_found=0
339-
for pattern in "secai-os-*.iso" "secai-os-*.qcow2" "secai-os-*.ova"; do
339+
for pattern in "secai-os-*.iso" "secai-os-*-usb.raw.xz" "secai-os-*.qcow2" "secai-os-*.ova"; do
340340
for artifact in $pattern; do
341341
[ -f "$artifact" ] || continue
342342
install_artifacts_found=$((install_artifacts_found + 1))
@@ -361,7 +361,7 @@ for pattern in "secai-os-*.iso" "secai-os-*.qcow2" "secai-os-*.ova"; do
361361
done
362362

363363
if [[ $install_artifacts_found -eq 0 ]]; then
364-
info "No install artifacts (ISO/QCOW2/OVA) found — skipping Step 5"
364+
info "No install artifacts (ISO/USB/QCOW2/OVA) found — skipping Step 5"
365365
record_check 5 "install_artifacts" "SKIP" "no install artifacts present"
366366
fi
367367

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[Unit]
2+
Description=Secure AI hardware-aware boot argument sync
3+
Documentation=https://github.com/SecAI-Hub/SecAI_OS
4+
After=local-fs.target
5+
Before=multi-user.target graphical.target
6+
ConditionPathExists=/usr/bin/rpm-ostree
7+
8+
[Service]
9+
Type=oneshot
10+
ExecStart=/usr/libexec/secure-ai/sync-boot-kargs.sh
11+
RemainAfterExit=yes
12+
13+
[Install]
14+
WantedBy=multi-user.target

files/system/usr/lib/systemd/system/secure-ai-firstboot.service

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@ ProtectHome=yes
1414
PrivateTmp=yes
1515

1616
# Kernel protection
17-
ProtectKernelTunables=yes
17+
# firstboot.sh intentionally adjusts a small set of boot-time sysctls
18+
# and may need to disable swap on the initial boot.
19+
ProtectKernelTunables=no
1820
ProtectKernelModules=yes
1921
ProtectKernelLogs=yes
2022
ProtectControlGroups=yes
2123
ProtectClock=yes
2224
ProtectHostname=yes
2325

24-
# Capability bounding — root oneshot, no special caps needed
25-
CapabilityBoundingSet=
26+
# Capability bounding — firstboot performs host setup (sysctl, swapoff, nft)
27+
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_NET_ADMIN CAP_SETGID CAP_SETUID CAP_SYS_ADMIN CAP_SYS_RESOURCE
2628

2729
# Syscall filtering
2830
SystemCallFilter=@system-service
29-
SystemCallFilter=~@privileged @mount @clock @debug @swap @reboot @module @cpu-emulation @obsolete
31+
SystemCallFilter=~@privileged @mount @clock @debug @reboot @module @cpu-emulation @obsolete
3032
SystemCallArchitectures=native
3133
SystemCallErrorNumber=EPERM
3234

0 commit comments

Comments
 (0)