VM image: one file, named like the ISO
The image was a folder, vm/, holding base.qcow2 and what boots it. It is now one file next to the ISO, with the ISO's name and its own checksum file, the way Ubuntu, Debian, Arch, Fedora and CentOS publish theirs: edge/<version>/omarchy-edge-<version>.qcow2 stable/<version>/<run>/omarchy-<version>.qcow2 The image boots by itself through UEFI. What a direct boot needs, the kernel, initramfs, kernel arguments and vm-boot, is in an unpacked/ folder beside it, Ubuntu's name for the same thing. The firmware variables are no longer published: a boot with the firmware's defaults finds the disk. latest.json follows: vm.url, vm.sha256 and vm.size are the image, as url, sha256 and size are the ISO; vm.unpacked and vm.unpacked_files are the folder. vm-boot takes the image file. The publish job compares every file it uploaded, whatever the layout. README, tests and the workflow's header say the same.
This commit is contained in:
1 parent
2b8d012688
commit
f9d184f2bc
6 files changed
+136
-90
No files matched your search
@@ -12,8 +12,10 @@ name: Nightly ISO Build
|
|||||||
#
|
#
|
||||||
# https://nightly.omarchy.org/<channel>/latest.json
|
# https://nightly.omarchy.org/<channel>/latest.json
|
||||||
# https://nightly.omarchy.org/edge/<version>/omarchy-edge-<version>.iso
|
# https://nightly.omarchy.org/edge/<version>/omarchy-edge-<version>.iso
|
||||||
# https://nightly.omarchy.org/edge/<version>/vm/ base.qcow2 and what boots it
|
# https://nightly.omarchy.org/edge/<version>/omarchy-edge-<version>.qcow2
|
||||||
# https://nightly.omarchy.org/stable/<version>/<run>/vm/
|
# https://nightly.omarchy.org/edge/<version>/unpacked/ kernel, initramfs, vm-boot
|
||||||
|
# https://nightly.omarchy.org/stable/<version>/<run>/omarchy-<version>.qcow2
|
||||||
|
# https://nightly.omarchy.org/stable/<version>/<run>/unpacked/
|
||||||
#
|
#
|
||||||
# Nothing is published unless the ISO installed and the image passed its
|
# Nothing is published unless the ISO installed and the image passed its
|
||||||
# checks. A version's files are written once and never replaced: the CDN keeps
|
# checks. A version's files are written once and never replaced: the CDN keeps
|
||||||
@@ -263,17 +265,12 @@ jobs:
|
|||||||
echo "ok $1"
|
echo "ok $1"
|
||||||
}
|
}
|
||||||
# Every file of the version, the checksum files too.
|
# Every file of the version, the checksum files too.
|
||||||
for iso in "$dir"/*.iso; do
|
(cd "$dir" && find . -type f -printf '%P\n' | sort) >files.txt
|
||||||
[[ -f $iso ]] || continue
|
[ -s files.txt ] || { echo "::error::nothing to check under $dir"; exit 1; }
|
||||||
check "$SITE/$RELEASE/${iso##*/}" "$(cut -d" " -f1 "$iso.sha256")"
|
while read -r file; do
|
||||||
check "$SITE/$RELEASE/${iso##*/}.sha256" "$(sha256sum "$iso.sha256" | cut -d" " -f1)"
|
check "$SITE/$RELEASE/$file" "$(sha256sum "$dir/$file" | cut -d" " -f1)"
|
||||||
done
|
done <files.txt
|
||||||
while read -r sum name; do
|
echo "$(wc -l <files.txt) files published and checked"
|
||||||
check "$SITE/$RELEASE/vm/$name" "$sum"
|
|
||||||
done <"$dir/vm/SHA256SUMS"
|
|
||||||
check "$SITE/$RELEASE/vm/SHA256SUMS" "$(sha256sum "$dir/vm/SHA256SUMS" | cut -d" " -f1)"
|
|
||||||
published=$(find "$dir" -type f | wc -l)
|
|
||||||
echo "$published files published and checked"
|
|
||||||
|
|
||||||
- name: Point latest.json at it
|
- name: Point latest.json at it
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -44,11 +44,13 @@ The rest of this file covers building, testing and publishing the ISO.
|
|||||||
|
|
||||||
| What | Where | When |
|
| What | Where | When |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Release ISO, with `.sha256` and `.sig` | `https://iso.omarchy.org/omarchy-<version>.iso` | Each release |
|
| Release ISO | `https://iso.omarchy.org/omarchy-<version>.iso` | Each release |
|
||||||
| Edge ISO, with `.sha256` | `https://nightly.omarchy.org/edge/<version>/omarchy-edge-<version>.iso` | Every night |
|
| Edge ISO | `https://nightly.omarchy.org/edge/<version>/omarchy-edge-<version>.iso` | Every night |
|
||||||
| VM image of edge | `https://nightly.omarchy.org/edge/<version>/vm/` | Every night |
|
| VM image of edge | `https://nightly.omarchy.org/edge/<version>/omarchy-edge-<version>.qcow2` | Every night |
|
||||||
| VM image of the latest release | `https://nightly.omarchy.org/stable/<version>/<run>/vm/` | Once per release |
|
| VM image of the latest release | `https://nightly.omarchy.org/stable/<version>/<run>/omarchy-<version>.qcow2` | Once per release |
|
||||||
| `latest.json` | `https://nightly.omarchy.org/edge/latest.json` and `.../stable/latest.json` | With each of the three above |
|
| `latest.json` | `https://nightly.omarchy.org/edge/latest.json` and `.../stable/latest.json` | With each nightly build |
|
||||||
|
|
||||||
|
Every ISO and every VM image has a `.sha256` file next to it. The release ISO also has a `.sig` file. Each VM image has an `unpacked/` folder next to it.
|
||||||
|
|
||||||
On nightly.omarchy.org, a published file is never replaced. Only `latest.json` changes. It is written last, after every file of the version has been fetched back through the CDN and compared.
|
On nightly.omarchy.org, a published file is never replaced. Only `latest.json` changes. It is written last, after every file of the version has been fetched back through the CDN and compared.
|
||||||
|
|
||||||
@@ -78,47 +80,66 @@ A separate job publishes. It is the only job with the bucket's key, and it runs
|
|||||||
| `omarchy_version` | The version of the Omarchy package in the image. |
|
| `omarchy_version` | The version of the Omarchy package in the image. |
|
||||||
| `omarchy_commit` | Edge: the omarchy commit that package was built from. Stable: empty. |
|
| `omarchy_commit` | Edge: the omarchy commit that package was built from. Stable: empty. |
|
||||||
| `kernel` | The image's kernel release, as reported by `uname -r`. |
|
| `kernel` | The image's kernel release, as reported by `uname -r`. |
|
||||||
| `vm.url` | The folder the image's files are in, ending in `/`. |
|
| `vm.url`, `vm.sha256`, `vm.size` | The VM image: its URL, SHA-256 and size in bytes. |
|
||||||
| `vm.disk`, `vm.disk_sha256`, `vm.disk_size` | The disk image, `base.qcow2`: its URL, SHA-256 and size in bytes. |
|
| `vm.unpacked` | The URL of the image's `unpacked/` folder, ending in `/`. |
|
||||||
| `vm.files` | Every file in `vm.url`. |
|
| `vm.unpacked_files` | Every file in that folder. |
|
||||||
| `vm.user`, `vm.password` | The account for the desktop and `sudo`. |
|
| `vm.user`, `vm.password` | The account for the desktop and `sudo`. |
|
||||||
| `vm.ssh` | SSH authentication: keys only. Password authentication is off. |
|
| `vm.ssh` | SSH authentication: keys only. Password authentication is off. |
|
||||||
|
|
||||||
The version is also in the edge ISO's file name.
|
The version is also in the edge ISO's file name.
|
||||||
|
|
||||||
### A VM image's files
|
### VM images
|
||||||
|
|
||||||
| File | What it is |
|
|
||||||
| --- | --- |
|
|
||||||
| `base.qcow2` | The disk, a zstd-compressed qcow2. |
|
|
||||||
| `vmlinuz`, `initrd`, `cmdline` | The image's own kernel, initramfs and kernel arguments, for booting it directly without firmware or boot loader. |
|
|
||||||
| `OVMF_VARS.4m.fd` | The firmware variables with the installer's boot entry, for booting it through UEFI instead. |
|
|
||||||
| `vm-boot` | A script that boots the image with QEMU. |
|
|
||||||
| `SHA256SUMS` | Checksums of the files above. |
|
|
||||||
|
|
||||||
|
|
||||||
### Using a VM image
|
|
||||||
|
|
||||||
VM images are for testing. Users download the release ISO from iso.omarchy.org.
|
VM images are for testing. Users download the release ISO from iso.omarchy.org.
|
||||||
|
|
||||||
A VM image is Omarchy already installed. It boots in seconds, with no installer to go through. There is an image of the edge channel, rebuilt every night, and an image of the latest release.
|
A VM image is Omarchy already installed, as one `.qcow2` file. There is an image of the edge channel, rebuilt every night, and an image of the latest release. Every boot is a new machine, with a new machine ID and new SSH host keys.
|
||||||
|
|
||||||
Run these commands on a Linux machine with QEMU (`qemu-system-x86_64` and `qemu-img`), read and write access to `/dev/kvm`, and `ssh`, `curl` and `jq`:
|
The user is `omarchy` and the password is `omarchy`. SSH takes keys only.
|
||||||
|
|
||||||
|
#### Boot the image by itself
|
||||||
|
|
||||||
|
The image boots through UEFI with no other file. Run these commands on a Linux machine with QEMU, read and write access to `/dev/kvm`, `curl` and `jq`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
latest=https://nightly.omarchy.org/edge/latest.json # or .../stable/latest.json
|
latest=https://nightly.omarchy.org/edge/latest.json # or .../stable/latest.json
|
||||||
vm=$(curl -fsSL "$latest" | jq -r .vm.url)
|
image=$(curl -fsSL "$latest" | jq -r .vm.url)
|
||||||
for file in $(curl -fsSL "$latest" | jq -r '.vm.files[]'); do curl -fsSLO "$vm$file"; done
|
curl -fLO "$image"
|
||||||
|
curl -fsSLO "$image.sha256"
|
||||||
|
sha256sum -c "${image##*/}.sha256"
|
||||||
|
|
||||||
|
cp /usr/share/edk2/x64/OVMF_VARS.4m.fd vars.fd
|
||||||
|
qemu-system-x86_64 -enable-kvm -cpu host -smp 4 -m 4G \
|
||||||
|
-drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
|
||||||
|
-drive if=pflash,format=raw,file=vars.fd \
|
||||||
|
-drive file="${image##*/}",if=virtio,snapshot=on \
|
||||||
|
-device virtio-vga
|
||||||
|
```
|
||||||
|
|
||||||
|
The firmware paths are Arch's. `snapshot=on` keeps the downloaded image unchanged. The image is a 4 to 5 GB download.
|
||||||
|
|
||||||
|
#### Boot it faster, with SSH
|
||||||
|
|
||||||
|
The `unpacked/` folder next to each image holds what a direct boot needs:
|
||||||
|
|
||||||
|
| File | What it is |
|
||||||
|
| --- | --- |
|
||||||
|
| `vmlinuz`, `initrd`, `cmdline` | The image's own kernel, initramfs and kernel arguments. |
|
||||||
|
| `vm-boot` | A script that boots the image directly into its kernel and gives it an SSH key. |
|
||||||
|
| `SHA256SUMS` | Checksums of the files above. |
|
||||||
|
|
||||||
|
A direct boot skips the firmware and the boot loader: SSH answers after about 6 seconds instead of about 20. Download the folder next to the image, then run `vm-boot`. It also needs `qemu-img` and `ssh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unpacked=$(curl -fsSL "$latest" | jq -r .vm.unpacked)
|
||||||
|
for file in $(curl -fsSL "$latest" | jq -r '.vm.unpacked_files[]'); do curl -fsSLO "$unpacked$file"; done
|
||||||
sha256sum -c SHA256SUMS
|
sha256sum -c SHA256SUMS
|
||||||
chmod +x vm-boot
|
chmod +x vm-boot
|
||||||
|
|
||||||
./vm-boot . # boots, prints how to log in, runs until Ctrl-C
|
./vm-boot "${image##*/}" # boots, prints how to log in, runs until Ctrl-C
|
||||||
./vm-boot . uname -r # boots, runs the command over SSH, powers off
|
./vm-boot "${image##*/}" uname -r # boots, runs the command over SSH, powers off
|
||||||
```
|
```
|
||||||
|
|
||||||
The user is `omarchy` and the password is `omarchy`. SSH takes keys only, and `vm-boot` makes a new key for each boot.
|
`vm-boot` makes a new SSH key for each boot. It runs the machine on an overlay under `/var/tmp`, so the downloaded image is never changed, and deletes the overlay on exit. Set `VM_STATE_DIR` to put the overlay somewhere else.
|
||||||
|
|
||||||
Each boot starts a new machine on an overlay under `/var/tmp`, so the downloaded image is never changed. The overlay is deleted on exit. Set `VM_STATE_DIR` to put it somewhere else. The disk image is a 4 to 5 GB download.
|
|
||||||
|
|
||||||
To get the URL of the edge ISO, run `jq -r .url` on the same `latest.json`.
|
To get the URL of the edge ISO, run `jq -r .url` on the same `latest.json`.
|
||||||
|
|
||||||
|
|||||||
+18
-11
@@ -58,7 +58,7 @@ refused "a date in another form" "$plan" 2026-10-09 431 1 schedule v4.0.4 ""
|
|||||||
sealed() { # directory: what vm-seal leaves
|
sealed() { # directory: what vm-seal leaves
|
||||||
rm -rf "$1"; mkdir -p "$1"
|
rm -rf "$1"; mkdir -p "$1"
|
||||||
local file
|
local file
|
||||||
for file in base.qcow2 OVMF_VARS.4m.fd vmlinuz initrd cmdline; do echo "$file of ${1##*/}" >"$1/$file"; done
|
for file in base.qcow2 vmlinuz initrd cmdline; do echo "$file of ${1##*/}" >"$1/$file"; done
|
||||||
echo "sums of the seal" >"$1/SHA256SUMS"
|
echo "sums of the seal" >"$1/SHA256SUMS"
|
||||||
}
|
}
|
||||||
export SITE=https://nightly.example ISO_SITE=https://iso.example ISO_COMMIT=0123456789abcdef0123456789abcdef01234567
|
export SITE=https://nightly.example ISO_SITE=https://iso.example ISO_COMMIT=0123456789abcdef0123456789abcdef01234567
|
||||||
@@ -77,12 +77,14 @@ dir=$tmp/out/edge/2026.10.09.431
|
|||||||
field() { jq -r "$1" "$json"; }
|
field() { jq -r "$1" "$json"; }
|
||||||
|
|
||||||
equal "edge: files published" "$(cd "$tmp/out" && find . -type f | sort | paste -sd' ')" \
|
equal "edge: files published" "$(cd "$tmp/out" && find . -type f | sort | paste -sd' ')" \
|
||||||
"./edge/2026.10.09.431/omarchy-edge-2026.10.09.431.iso ./edge/2026.10.09.431/omarchy-edge-2026.10.09.431.iso.sha256 ./edge/2026.10.09.431/vm/OVMF_VARS.4m.fd ./edge/2026.10.09.431/vm/SHA256SUMS ./edge/2026.10.09.431/vm/base.qcow2 ./edge/2026.10.09.431/vm/cmdline ./edge/2026.10.09.431/vm/initrd ./edge/2026.10.09.431/vm/vm-boot ./edge/2026.10.09.431/vm/vmlinuz ./edge/latest.json"
|
"./edge/2026.10.09.431/omarchy-edge-2026.10.09.431.iso ./edge/2026.10.09.431/omarchy-edge-2026.10.09.431.iso.sha256 ./edge/2026.10.09.431/omarchy-edge-2026.10.09.431.qcow2 ./edge/2026.10.09.431/omarchy-edge-2026.10.09.431.qcow2.sha256 ./edge/2026.10.09.431/unpacked/SHA256SUMS ./edge/2026.10.09.431/unpacked/cmdline ./edge/2026.10.09.431/unpacked/initrd ./edge/2026.10.09.431/unpacked/vm-boot ./edge/2026.10.09.431/unpacked/vmlinuz ./edge/latest.json"
|
||||||
(cd "$dir" && sha256sum -c --quiet ./*.sha256) || fail "edge: the ISO's checksum file does not match the ISO"
|
(cd "$dir" && sha256sum -c --quiet ./*.sha256) || fail "edge: a checksum file does not match the file it is for"
|
||||||
(cd "$dir/vm" && sha256sum -c --quiet SHA256SUMS) || fail "edge: SHA256SUMS does not match the image"
|
equal "edge: the ISO and the image each have a checksum file" "$(cd "$dir" && ls ./*.sha256 | wc -l)" "2"
|
||||||
|
equal "edge: the image is the sealed disk" "$(cat "$dir/omarchy-edge-2026.10.09.431.qcow2")" "base.qcow2 of sealed-edge"
|
||||||
|
(cd "$dir/unpacked" && sha256sum -c --quiet SHA256SUMS) || fail "edge: SHA256SUMS does not match the unpacked files"
|
||||||
equal "edge: SHA256SUMS covers every file beside it" \
|
equal "edge: SHA256SUMS covers every file beside it" \
|
||||||
"$(awk '{ print $2 }' "$dir/vm/SHA256SUMS" | sort | paste -sd' ')" "$(cd "$dir/vm" && ls | grep -v SHA256SUMS | sort | paste -sd' ')"
|
"$(awk '{ print $2 }' "$dir/unpacked/SHA256SUMS" | sort | paste -sd' ')" "$(cd "$dir/unpacked" && ls | grep -v SHA256SUMS | sort | paste -sd' ')"
|
||||||
cmp -s "$dir/vm/vm-boot" "$ROOT/test/vm-image/vm-boot" || fail "edge: the published vm-boot is not the repository's"
|
cmp -s "$dir/unpacked/vm-boot" "$ROOT/test/vm-image/vm-boot" || fail "edge: the published vm-boot is not the repository's"
|
||||||
equal "edge: version" "$(field .version)" "2026.10.09.431"
|
equal "edge: version" "$(field .version)" "2026.10.09.431"
|
||||||
equal "edge: channel" "$(field .channel)" "edge"
|
equal "edge: channel" "$(field .channel)" "edge"
|
||||||
equal "edge: url" "$(field .url)" "https://nightly.example/edge/2026.10.09.431/omarchy-edge-2026.10.09.431.iso"
|
equal "edge: url" "$(field .url)" "https://nightly.example/edge/2026.10.09.431/omarchy-edge-2026.10.09.431.iso"
|
||||||
@@ -92,9 +94,12 @@ equal "edge: iso_commit" "$(field .iso_commit)" "$ISO_COMMIT"
|
|||||||
equal "edge: omarchy_version, once" "$(field .omarchy_version)" "4.0.0.r6818.gc352b62-2"
|
equal "edge: omarchy_version, once" "$(field .omarchy_version)" "4.0.0.r6818.gc352b62-2"
|
||||||
equal "edge: omarchy_commit" "$(field .omarchy_commit)" "c352b62"
|
equal "edge: omarchy_commit" "$(field .omarchy_commit)" "c352b62"
|
||||||
equal "edge: kernel" "$(field .kernel)" "7.2.8-5-omarchy-bore"
|
equal "edge: kernel" "$(field .kernel)" "7.2.8-5-omarchy-bore"
|
||||||
equal "edge: image address" "$(field .vm.disk)" "https://nightly.example/edge/2026.10.09.431/vm/base.qcow2"
|
equal "edge: image address" "$(field .vm.url)" "https://nightly.example/edge/2026.10.09.431/omarchy-edge-2026.10.09.431.qcow2"
|
||||||
equal "edge: image checksum is the disk's" "$(field .vm.disk_sha256)" "$(sha256sum "$dir/vm/base.qcow2" | cut -d' ' -f1)"
|
equal "edge: image checksum is the image's" "$(field .vm.sha256)" "$(sha256sum "$dir/omarchy-edge-2026.10.09.431.qcow2" | cut -d' ' -f1)"
|
||||||
equal "edge: every listed file is published" "$(field '.vm.files[]' | sort | paste -sd' ')" "$(cd "$dir/vm" && ls | sort | paste -sd' ')"
|
equal "edge: image size is the image's" "$(field .vm.size)" "$(stat -c %s "$dir/omarchy-edge-2026.10.09.431.qcow2")"
|
||||||
|
equal "edge: the ISO and the image differ only in their ending" "$(field .url | sed 's/\.iso$//')" "$(field .vm.url | sed 's/\.qcow2$//')"
|
||||||
|
equal "edge: unpacked folder" "$(field .vm.unpacked)" "https://nightly.example/edge/2026.10.09.431/unpacked/"
|
||||||
|
equal "edge: every unpacked file listed is published" "$(field '.vm.unpacked_files[]' | sort | paste -sd' ')" "$(cd "$dir/unpacked" && ls | sort | paste -sd' ')"
|
||||||
[[ $(field .built_at) =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:]{8}Z$ ]] || fail "edge: built_at is '$(field .built_at)'"
|
[[ $(field .built_at) =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:]{8}Z$ ]] || fail "edge: built_at is '$(field .built_at)'"
|
||||||
# What a consumer does with the ISO's name: the first run of three or more
|
# What a consumer does with the ISO's name: the first run of three or more
|
||||||
# dotted numbers is the version.
|
# dotted numbers is the version.
|
||||||
@@ -115,8 +120,10 @@ equal "stable: sha256 is the ISO's" "$(field .sha256)" "$(sha256sum "$tmp/releas
|
|||||||
equal "stable: version" "$(field .version)" "4.0.4"
|
equal "stable: version" "$(field .version)" "4.0.4"
|
||||||
equal "stable: omarchy_version" "$(field .omarchy_version)" "4.0.4-1"
|
equal "stable: omarchy_version" "$(field .omarchy_version)" "4.0.4-1"
|
||||||
equal "stable: a release names no commit" "$(field .omarchy_commit)" ""
|
equal "stable: a release names no commit" "$(field .omarchy_commit)" ""
|
||||||
equal "stable: image address has the run number" "$(field .vm.disk)" "https://nightly.example/stable/4.0.4/431/vm/base.qcow2"
|
equal "stable: image address has the run number" "$(field .vm.url)" "https://nightly.example/stable/4.0.4/431/omarchy-4.0.4.qcow2"
|
||||||
(cd "$tmp/out/stable/4.0.4/431/vm" && sha256sum -c --quiet SHA256SUMS) || fail "stable: SHA256SUMS does not match the image"
|
equal "stable: unpacked folder" "$(field .vm.unpacked)" "https://nightly.example/stable/4.0.4/431/unpacked/"
|
||||||
|
(cd "$tmp/out/stable/4.0.4/431" && sha256sum -c --quiet omarchy-4.0.4.qcow2.sha256) || fail "stable: the image's checksum file does not match it"
|
||||||
|
(cd "$tmp/out/stable/4.0.4/431/unpacked" && sha256sum -c --quiet SHA256SUMS) || fail "stable: SHA256SUMS does not match the unpacked files"
|
||||||
|
|
||||||
# Nothing half-made is described.
|
# Nothing half-made is described.
|
||||||
sealed "$tmp/sealed-short"; rm "$tmp/sealed-short/initrd"
|
sealed "$tmp/sealed-short"; rm "$tmp/sealed-short/initrd"
|
||||||
|
|||||||
@@ -13,14 +13,21 @@
|
|||||||
# Env: SITE (https://nightly.omarchy.org), ISO_SITE (https://iso.omarchy.org),
|
# Env: SITE (https://nightly.omarchy.org), ISO_SITE (https://iso.omarchy.org),
|
||||||
# ISO_COMMIT (the omarchy-iso commit, default: HEAD of this checkout).
|
# ISO_COMMIT (the omarchy-iso commit, default: HEAD of this checkout).
|
||||||
#
|
#
|
||||||
# out_dir gets <release>/vm/ with the image, its checksums and vm-boot, and
|
# out_dir gets, under <release>/:
|
||||||
# <channel>/latest.json. The edge ISO is published too, next to the image.
|
#
|
||||||
# The released one already has its place: latest.json points there and the
|
# omarchy-<...>.qcow2 and .qcow2.sha256 the VM image: one file, which boots
|
||||||
# ISO stays where it is.
|
# by itself through UEFI
|
||||||
|
# unpacked/ its kernel, initramfs and kernel
|
||||||
|
# arguments for booting it directly,
|
||||||
|
# vm-boot, and their SHA256SUMS
|
||||||
|
# omarchy-edge-<version>.iso and .sha256 edge only
|
||||||
|
#
|
||||||
|
# and <channel>/latest.json. The released ISO already has its place:
|
||||||
|
# latest.json points there and the ISO stays where it is.
|
||||||
#
|
#
|
||||||
# The ISO and the image are moved, not copied: they are gigabytes.
|
# The ISO and the image are moved, not copied: they are gigabytes.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
[[ $# -eq 7 ]] || { sed -n '2,13p' "$0" >&2; exit 2; }
|
[[ $# -eq 7 ]] || { sed -n '2,12p' "$0" >&2; exit 2; }
|
||||||
channel=$1 version=$2 release=$3 iso_file=$4 sealed=$5 packages=$6 out=$7
|
channel=$1 version=$2 release=$3 iso_file=$4 sealed=$5 packages=$6 out=$7
|
||||||
here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||||
site=${SITE:-https://nightly.omarchy.org}
|
site=${SITE:-https://nightly.omarchy.org}
|
||||||
@@ -30,8 +37,8 @@ fail() { echo "nightly-layout: $*" >&2; exit 1; }
|
|||||||
|
|
||||||
[[ $channel == edge || $channel == stable ]] || fail "'$channel' is not a channel"
|
[[ $channel == edge || $channel == stable ]] || fail "'$channel' is not a channel"
|
||||||
[[ -f $iso_file ]] || fail "no ISO at $iso_file"
|
[[ -f $iso_file ]] || fail "no ISO at $iso_file"
|
||||||
files=(base.qcow2 OVMF_VARS.4m.fd vmlinuz initrd cmdline)
|
unpacked=(vmlinuz initrd cmdline)
|
||||||
for file in "${files[@]}"; do
|
for file in base.qcow2 "${unpacked[@]}"; do
|
||||||
[[ -s $sealed/$file ]] || fail "the sealed image has no $file"
|
[[ -s $sealed/$file ]] || fail "the sealed image has no $file"
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -43,46 +50,52 @@ kernel=$(tail -n 1 "$packages")
|
|||||||
omarchy_commit=$(sed -nE 's/.*\.g([0-9a-f]+)-.*/\1/p' <<<"$omarchy")
|
omarchy_commit=$(sed -nE 's/.*\.g([0-9a-f]+)-.*/\1/p' <<<"$omarchy")
|
||||||
|
|
||||||
dir=$out/$release
|
dir=$out/$release
|
||||||
mkdir -p "$dir/vm" "$out/$channel"
|
mkdir -p "$dir/unpacked" "$out/$channel"
|
||||||
if [[ $channel == edge ]]; then
|
if [[ $channel == edge ]]; then
|
||||||
iso=omarchy-$channel-$version.iso
|
name=omarchy-$channel-$version
|
||||||
|
iso=$name.iso
|
||||||
iso_url=$site/$release/$iso
|
iso_url=$site/$release/$iso
|
||||||
mv "$iso_file" "$dir/$iso"
|
mv "$iso_file" "$dir/$iso"
|
||||||
(cd "$dir" && sha256sum "$iso" >"$iso.sha256")
|
(cd "$dir" && sha256sum "$iso" >"$iso.sha256")
|
||||||
iso_sha256=$(cut -d' ' -f1 "$dir/$iso.sha256") iso_size=$(stat -c %s "$dir/$iso")
|
iso_sha256=$(cut -d' ' -f1 "$dir/$iso.sha256") iso_size=$(stat -c %s "$dir/$iso")
|
||||||
else
|
else
|
||||||
iso=omarchy-$version.iso
|
name=omarchy-$version
|
||||||
|
iso=$name.iso
|
||||||
[[ ${iso_file##*/} == "$iso" ]] || fail "the released ISO is $iso, not ${iso_file##*/}"
|
[[ ${iso_file##*/} == "$iso" ]] || fail "the released ISO is $iso, not ${iso_file##*/}"
|
||||||
iso_url=$iso_site/$iso
|
iso_url=$iso_site/$iso
|
||||||
iso_sha256=$(sha256sum "$iso_file" | cut -d' ' -f1) iso_size=$(stat -c %s "$iso_file")
|
iso_sha256=$(sha256sum "$iso_file" | cut -d' ' -f1) iso_size=$(stat -c %s "$iso_file")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for file in "${files[@]}"; do mv "$sealed/$file" "$dir/vm/$file"; done
|
# The image has the ISO's name, so the two are told apart by their endings.
|
||||||
cp "$here/vm-boot" "$dir/vm/vm-boot"
|
image=$name.qcow2
|
||||||
files+=(vm-boot)
|
mv "$sealed/base.qcow2" "$dir/$image"
|
||||||
(cd "$dir/vm" && sha256sum "${files[@]}" >SHA256SUMS)
|
(cd "$dir" && sha256sum "$image" >"$image.sha256")
|
||||||
|
|
||||||
|
for file in "${unpacked[@]}"; do mv "$sealed/$file" "$dir/unpacked/$file"; done
|
||||||
|
cp "$here/vm-boot" "$dir/unpacked/vm-boot"
|
||||||
|
unpacked+=(vm-boot)
|
||||||
|
(cd "$dir/unpacked" && sha256sum "${unpacked[@]}" >SHA256SUMS)
|
||||||
|
|
||||||
latest=$out/$channel/latest.json
|
latest=$out/$channel/latest.json
|
||||||
jq -n \
|
jq -n \
|
||||||
--arg version "$version" --arg channel "$channel" --arg vm "$site/$release/vm" \
|
--arg version "$version" --arg channel "$channel" --arg release "$site/$release" --arg image "$image" \
|
||||||
--arg built_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
--arg built_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||||
--arg url "$iso_url" --arg sha256 "$iso_sha256" --argjson size "$iso_size" \
|
--arg url "$iso_url" --arg sha256 "$iso_sha256" --argjson size "$iso_size" \
|
||||||
--arg iso_commit "$iso_commit" --arg omarchy_version "$omarchy" --arg omarchy_commit "$omarchy_commit" \
|
--arg iso_commit "$iso_commit" --arg omarchy_version "$omarchy" --arg omarchy_commit "$omarchy_commit" \
|
||||||
--arg kernel "$kernel" \
|
--arg kernel "$kernel" \
|
||||||
--arg disk_sha256 "$(awk '$2 == "base.qcow2" { print $1 }' "$dir/vm/SHA256SUMS")" \
|
--arg vm_sha256 "$(cut -d' ' -f1 "$dir/$image.sha256")" --argjson vm_size "$(stat -c %s "$dir/$image")" \
|
||||||
--argjson disk_size "$(stat -c %s "$dir/vm/base.qcow2")" \
|
|
||||||
--args '{
|
--args '{
|
||||||
version: $version, channel: $channel, built_at: $built_at,
|
version: $version, channel: $channel, built_at: $built_at,
|
||||||
url: $url, sha256: $sha256, size: $size,
|
url: $url, sha256: $sha256, size: $size,
|
||||||
iso_commit: $iso_commit, omarchy_version: $omarchy_version, omarchy_commit: $omarchy_commit,
|
iso_commit: $iso_commit, omarchy_version: $omarchy_version, omarchy_commit: $omarchy_commit,
|
||||||
kernel: $kernel,
|
kernel: $kernel,
|
||||||
vm: {
|
vm: {
|
||||||
url: "\($vm)/", disk: "\($vm)/base.qcow2", disk_sha256: $disk_sha256, disk_size: $disk_size,
|
url: "\($release)/\($image)", sha256: $vm_sha256, size: $vm_size,
|
||||||
files: ($ARGS.positional + ["SHA256SUMS"]),
|
unpacked: "\($release)/unpacked/", unpacked_files: ($ARGS.positional + ["SHA256SUMS"]),
|
||||||
user: "omarchy", password: "omarchy",
|
user: "omarchy", password: "omarchy",
|
||||||
ssh: "keys only; vm-boot makes one for each boot"
|
ssh: "keys only; vm-boot makes one for each boot"
|
||||||
}
|
}
|
||||||
}' "${files[@]}" >"$latest"
|
}' "${unpacked[@]}" >"$latest"
|
||||||
|
|
||||||
# What is about to be published has to say what it should: one value in every
|
# What is about to be published has to say what it should: one value in every
|
||||||
# field, and checksums that are checksums.
|
# field, and checksums that are checksums.
|
||||||
@@ -93,6 +106,7 @@ jq -e --arg channel "$channel" '
|
|||||||
and (.url | test("^https://[^\n ]+\\.iso$")) and (.sha256 | sha) and .size > 0
|
and (.url | test("^https://[^\n ]+\\.iso$")) and (.sha256 | sha) and .size > 0
|
||||||
and (.iso_commit | test("^[0-9a-f]{40}$"))
|
and (.iso_commit | test("^[0-9a-f]{40}$"))
|
||||||
and (.omarchy_version | one_line) and (.omarchy_commit | test("^[0-9a-f]*$")) and (.kernel | one_line)
|
and (.omarchy_version | one_line) and (.omarchy_commit | test("^[0-9a-f]*$")) and (.kernel | one_line)
|
||||||
and (.vm.disk | test("^https://[^\n ]+/vm/base\\.qcow2$")) and (.vm.disk_sha256 | sha) and .vm.disk_size > 0
|
and (.vm.url | test("^https://[^\n ]+\\.qcow2$")) and (.vm.sha256 | sha) and .vm.size > 0
|
||||||
|
and (.vm.unpacked | test("^https://[^\n ]+/unpacked/$")) and (.vm.unpacked_files | length) == 5
|
||||||
' "$latest" >/dev/null || { cat "$latest" >&2; fail "latest.json does not say what it should"; }
|
' "$latest" >/dev/null || { cat "$latest" >&2; fail "latest.json does not say what it should"; }
|
||||||
cat "$latest"
|
cat "$latest"
|
||||||
+19
-8
@@ -1,11 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Boots a sealed Omarchy image (vm-seal) as a throwaway machine.
|
# Boots a sealed Omarchy image (vm-seal) as a throwaway machine.
|
||||||
#
|
#
|
||||||
# vm-boot <sealed_dir> boot, print how to log in, run until Ctrl-C
|
# vm-boot <image> boot, print how to log in, run until Ctrl-C
|
||||||
# vm-boot <sealed_dir> <command...> boot, run the command over SSH, power off;
|
# vm-boot <image> <command...> boot, run the command over SSH, power off;
|
||||||
# exits with the command's status
|
# exits with the command's status
|
||||||
#
|
#
|
||||||
# Nothing is written to sealed_dir: the machine runs on a copy-on-write overlay
|
# <image> is a downloaded disk, omarchy-<...>.qcow2, with vmlinuz, initrd and
|
||||||
|
# cmdline in the same directory as this script, as in a published "unpacked"
|
||||||
|
# folder. Or it is a directory as vm-seal leaves it, holding base.qcow2 and
|
||||||
|
# those three files.
|
||||||
|
#
|
||||||
|
# Nothing is written to the image: the machine runs on a copy-on-write overlay
|
||||||
# that is deleted on exit. Each boot gets a new SSH key and a new machine ID;
|
# that is deleted on exit. Each boot gets a new SSH key and a new machine ID;
|
||||||
# the image itself holds no key and its SSH takes no password.
|
# the image itself holds no key and its SSH takes no password.
|
||||||
#
|
#
|
||||||
@@ -14,9 +19,15 @@
|
|||||||
# kernel arguments), QEMU_EXTRA (more qemu arguments, split on spaces),
|
# kernel arguments), QEMU_EXTRA (more qemu arguments, split on spaces),
|
||||||
# VM_STATE_DIR (/var/tmp; holds the overlay, so it needs room to grow).
|
# VM_STATE_DIR (/var/tmp; holds the overlay, so it needs room to grow).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
[[ $# -ge 1 ]] || { sed -n '2,16p' "$0" >&2; exit 2; }
|
[[ $# -ge 1 ]] || { sed -n '2,21p' "$0" >&2; exit 2; }
|
||||||
base=$(realpath "$1"); shift
|
image=$(realpath "$1"); shift
|
||||||
for f in base.qcow2 vmlinuz initrd cmdline; do
|
if [[ -d $image ]]; then
|
||||||
|
base=$image disk=$image/base.qcow2
|
||||||
|
else
|
||||||
|
base=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) disk=$image
|
||||||
|
fi
|
||||||
|
[[ -s $disk ]] || { echo "vm-boot: there is no disk image at $disk" >&2; exit 1; }
|
||||||
|
for f in vmlinuz initrd cmdline; do
|
||||||
[[ -s $base/$f ]] || { echo "vm-boot: $base/$f is missing" >&2; exit 1; }
|
[[ -s $base/$f ]] || { echo "vm-boot: $base/$f is missing" >&2; exit 1; }
|
||||||
done
|
done
|
||||||
[[ -r /dev/kvm && -w /dev/kvm ]] || { echo "vm-boot: /dev/kvm is not usable" >&2; exit 1; }
|
[[ -r /dev/kvm && -w /dev/kvm ]] || { echo "vm-boot: /dev/kvm is not usable" >&2; exit 1; }
|
||||||
@@ -45,7 +56,7 @@ keys=$(printf 'd /home/omarchy/.ssh 0700 omarchy omarchy -\nf+ /home/omarchy/.ss
|
|||||||
ssh_opts=(-i "$state/id_ed25519" -p "$port" -o BatchMode=yes -o IdentitiesOnly=yes
|
ssh_opts=(-i "$state/id_ed25519" -p "$port" -o BatchMode=yes -o IdentitiesOnly=yes
|
||||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR)
|
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR)
|
||||||
|
|
||||||
qemu-img create -qf qcow2 -b "$base/base.qcow2" -F qcow2 "$state/run.qcow2"
|
qemu-img create -qf qcow2 -b "$disk" -F qcow2 "$state/run.qcow2"
|
||||||
t0=$(date +%s%N)
|
t0=$(date +%s%N)
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
qemu-system-x86_64 -cpu host -enable-kvm -machine q35,accel=kvm -smp "${SMP:-4}" -m "${MEM:-4G}" \
|
qemu-system-x86_64 -cpu host -enable-kvm -machine q35,accel=kvm -smp "${SMP:-4}" -m "${MEM:-4G}" \
|
||||||
|
|||||||
@@ -7,10 +7,9 @@
|
|||||||
#
|
#
|
||||||
# installed_dir holds base.qcow2, OVMF_VARS.4m.fd and id_ed25519, which is
|
# installed_dir holds base.qcow2, OVMF_VARS.4m.fd and id_ed25519, which is
|
||||||
# what test/integration leaves in test-runs/<iso>-integration once its install
|
# what test/integration leaves in test-runs/<iso>-integration once its install
|
||||||
# has passed. sealed_dir gets a standalone, zstd-compressed base.qcow2, the
|
# has passed. sealed_dir gets a standalone, zstd-compressed base.qcow2, which
|
||||||
# firmware variables that boot it through UEFI, and the UKI's vmlinuz, initrd
|
# boots by itself through UEFI, and the UKI's vmlinuz, initrd and cmdline for
|
||||||
# and cmdline for booting it directly. It holds no key: each boot is handed
|
# booting it directly. It holds no key: each boot is handed one (see vm-boot).
|
||||||
# one (see vm-boot).
|
|
||||||
#
|
#
|
||||||
# The image keeps the installer's user and password (GUEST_PASSWORD, default
|
# The image keeps the installer's user and password (GUEST_PASSWORD, default
|
||||||
# omarchy) for the desktop and sudo. SSH takes keys only.
|
# omarchy) for the desktop and sudo. SSH takes keys only.
|
||||||
@@ -145,8 +144,5 @@ tr -d '\0' <"$state/cmdline" | tr ' ' '\n' | { grep -vE '^(resume|resume_offset)
|
|||||||
|
|
||||||
# zstd clusters: QEMU reads them as they are, so the download boots as it is.
|
# zstd clusters: QEMU reads them as they are, so the download boots as it is.
|
||||||
qemu-img convert -c -O qcow2 -o compression_type=zstd -m 16 -W "$state/edit.qcow2" "$out/base.qcow2"
|
qemu-img convert -c -O qcow2 -o compression_type=zstd -m 16 -W "$state/edit.qcow2" "$out/base.qcow2"
|
||||||
# The variables hold the boot entry the installer registered; a firmware boot
|
( cd "$out" && sha256sum base.qcow2 vmlinuz initrd cmdline >SHA256SUMS )
|
||||||
# of this disk needs them, a direct kernel boot does not.
|
|
||||||
cp "$in/OVMF_VARS.4m.fd" "$out/OVMF_VARS.4m.fd"
|
|
||||||
( cd "$out" && sha256sum base.qcow2 OVMF_VARS.4m.fd vmlinuz initrd cmdline >SHA256SUMS )
|
|
||||||
echo "sealed: $out ($(du -sh "$out/base.qcow2" | cut -f1)); cmdline: $(cat "$out/cmdline")"
|
echo "sealed: $out ($(du -sh "$out/base.qcow2" | cut -f1)); cmdline: $(cat "$out/cmdline")"
|
||||||
Reference in new issue
Block a user