Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
82d8770
feat: pre-seed snap packages for offline live and installed system
May 2, 2026
f03e3bd
chore: untrack build output directory
May 2, 2026
06c43f0
feat: match Ubuntu 26.04 desktop snap/app set
May 2, 2026
4911d4c
feat: match ubuntu-desktop Flatpak app set
May 2, 2026
545fc83
fix: don't apt-get install snapd — snap is already in base image
May 2, 2026
a50fc43
fix: use find instead of ls glob for snap cache check
May 2, 2026
1b3c181
fix: add --network=host to container build for snap download
May 2, 2026
16bde68
test: add live smoke test and full e2e QEMU workflow
May 2, 2026
bf0fff2
fix(e2e): build bootc image from source instead of pulling from GHCR
May 3, 2026
c79c51a
fix(e2e): correct bootc repo org (hanthor not tuna-os)
May 3, 2026
d7cba67
fix(e2e): forward output_dir to e2e sub-recipes
May 3, 2026
709cb26
fix(e2e): continue-on-error for artifact upload; e2e test now passes
May 3, 2026
325e458
fix(e2e): locate fisherman dynamically from Flatpak store
May 3, 2026
f227564
fix(e2e): use static install script to avoid quoting issues; fix log …
May 3, 2026
ec4a34f
fix(e2e): use xfs filesystem; move recipe into e2e-install.sh
May 3, 2026
c0c0841
chore: note podman dependency in e2e-install.sh
May 3, 2026
2637def
ci: re-trigger e2e after pushing podman to bootc repo
May 3, 2026
74bf3e6
fix(ci): use extractions/setup-just@v3 instead of apt-installed just
May 3, 2026
02eb889
feat(e2e): full local e2e passes — ISO build → install → boot → bootc…
May 3, 2026
187b8ca
fix(ci): use sudo $(which just) — setup-just not in sudo PATH
May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
name: End-to-End ISO Test

# Tests the full Ubuntu 26.04 live ISO lifecycle:
# 1. Build the ISO (debug=1 so SSH is available)
# 2. Boot the live ISO in QEMU — wait for UBUNTU26_LIVE_READY marker
# 3. Run fisherman install via SSH (ext4, systemd-boot, no LUKS)
# 4. Boot the installed disk in QEMU — wait for login prompt
#
# Mirrors the local just recipes:
# just debug=1 e2e ubuntu-26.04
#
# Runs on every PR and weekly to catch regressions.

on:
pull_request:
branches: [main]
schedule:
- cron: '0 3 * * 2' # Tuesday 03:00 UTC
workflow_dispatch:

permissions:
contents: read
pull-requests: write
issues: write

jobs:
e2e:
name: ISO End-to-End
runs-on: ubuntu-24.04
timeout-minutes: 180

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Free disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
tool-cache: true

- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y \
podman mtools xorriso isomd5sum squashfs-tools \
qemu-system-x86 ovmf socat sshpass \
python3 -qq

- name: Setup Just
uses: extractions/setup-just@v3

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Checkout ubuntu-26.04-desktop-bootc
uses: actions/checkout@v4
with:
repository: hanthor/ubuntu-26.04-desktop-bootc
path: bootc-image

- name: Build base bootc image
run: |
cd bootc-image
sudo $(which just) build

- name: Build debug ISO
id: build
run: |
START=$(date +%s)
sudo $(which just) debug=1 output_dir=/var/tmp/iso-output compression=fast \
iso-sd-boot ubuntu-26.04
echo "duration=$(( $(date +%s) - START ))" >> "$GITHUB_OUTPUT"
ISO=/var/tmp/iso-output/ubuntu-26.04-live.iso
echo "iso_size=$(stat -c%s "$ISO" 2>/dev/null || echo 0)" >> "$GITHUB_OUTPUT"
ls -lh "$ISO"

- name: Boot live ISO (smoke test)
id: live_boot
run: |
START=$(date +%s)
sudo $(which just) output_dir=/var/tmp/iso-output test-live ubuntu-26.04
echo "duration=$(( $(date +%s) - START ))" >> "$GITHUB_OUTPUT"

- name: Full install end-to-end
id: e2e
run: |
START=$(date +%s)
# e2e-qemu runs against the already-built ISO (no rebuild)
sudo $(which just) \
output_dir=/var/tmp/iso-output \
e2e-qemu ubuntu-26.04
echo "duration=$(( $(date +%s) - START ))" >> "$GITHUB_OUTPUT"
continue-on-error: true

- name: Fix serial log permissions
if: always()
run: sudo chmod a+r /tmp/ubuntu-26.04-e2e-*.log 2>/dev/null || true

- name: Upload serial logs
if: always()
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: e2e-serial-logs
path: |
/tmp/ubuntu-26.04-e2e-live.log
/tmp/ubuntu-26.04-e2e-installed.log
if-no-files-found: warn
retention-days: 14

- name: Post PR comment
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
env:
LIVE_OUTCOME: ${{ steps.live_boot.outcome }}
E2E_OUTCOME: ${{ steps.e2e.outcome }}
BUILD_S: ${{ steps.build.outputs.duration }}
LIVE_S: ${{ steps.live_boot.outputs.duration }}
E2E_S: ${{ steps.e2e.outputs.duration }}
ISO_BYTES: ${{ steps.build.outputs.iso_size }}
with:
script: |
const live = process.env.LIVE_OUTCOME;
const e2e = process.env.E2E_OUTCOME;
const pass = live === 'success' && e2e === 'success';
const icon = pass ? '✅' : '❌';
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const fmtDur = s => s ? `${Math.floor(s/60)}m ${s%60}s` : 'N/A';
const fmtMiB = b => b ? `${(b/1024/1024).toFixed(0)} MiB` : 'N/A';
const body = [
`## ${icon} ISO End-to-End Test — ${pass ? 'PASSED' : 'FAILED'}`,
'',
'| Step | Result | Time |',
'|------|--------|------|',
`| ISO build | ${process.env.BUILD_S ? '✅' : '⏳'} | ${fmtDur(process.env.BUILD_S)} |`,
`| Live boot | ${live === 'success' ? '✅' : '❌'} ${live} | ${fmtDur(process.env.LIVE_S)} |`,
`| Install + reboot | ${e2e === 'success' ? '✅' : '❌'} ${e2e} | ${fmtDur(process.env.E2E_S)} |`,
`| ISO size | ${fmtMiB(process.env.ISO_BYTES)} | — |`,
'',
`Serial logs are attached to the [workflow run](${runUrl}).`,
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body,
});

- name: Job summary
if: always()
run: |
LIVE="${{ steps.live_boot.outcome }}"
E2E="${{ steps.e2e.outcome }}"
PASS=$( [[ "$LIVE" == "success" && "$E2E" == "success" ]] && echo "✅ PASSED" || echo "❌ FAILED" )
{
echo "## $PASS — ISO End-to-End Test"
echo ""
echo "| Step | Result | Time |"
echo "|------|--------|------|"
echo "| Build | ✅ | ${{ steps.build.outputs.duration }}s |"
echo "| Live boot | $LIVE | ${{ steps.live_boot.outputs.duration }}s |"
echo "| Install + reboot | $E2E | ${{ steps.e2e.outputs.duration }}s |"
} >> "$GITHUB_STEP_SUMMARY"

- name: Fail if e2e did not pass
if: steps.e2e.outcome != 'success'
run: |
echo "e2e test failed — check serial logs in artifacts"
exit 1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
output/
Loading
Loading