Compare commits

..
Author SHA1 Message Date
David Heinemeier Hansson a3f5469841 Update the tagline to Beautiful, Fun & Agentic
Matches omarchy.org and the omarchy repo. The centring math keys off the string length, so the shorter line stays centred.
2026-09-01 11:51:51 +02:00
Ryan Hughes d6e4060d56 Add omarchy-iso-test-stop to shut down the kept-running VM
Graceful guest poweroff over SSH first, escalating to TERM then KILL;
--kill skips straight to force. Finds the newest live run by its pidfile
and confirms before acting when run from a terminal, since a harness
still driving the VM dies with it.
2026-08-30 18:33:17 -04:00
Ryan Hughes 100a41f7fd Type the console bootstrap blind and gate it on SSH
A VNC viewer that resizes the guest framebuffer makes the console font
unreadable to OCR, which stranded the bootstrap at its login-prompt gate.
Getty is reliably on the spare TTY and SSH coming up is the real success
signal, so type the login blind and retry the whole sequence up to three
times; a failed pass just leaves getty re-prompting.

Also print the resize-safe viewer invocation whenever a VM starts: a
resize during early boot can wedge virtio-gpu before the system is up.
2026-08-30 18:33:17 -04:00
Ryan Hughes f9be60b24b Name the released ISO after its full candidate version
--rc appended a bare -rc suffix regardless of the version passed, so
every candidate of a train uploaded over the same omarchy-X.Y.Z-rc.iso
and nobody could tell rc1 from rc2 by name or URL. A version that
already numbers its candidate (4.0.2rc2) is the complete artifact name;
the suffix remains for unnumbered candidate builds.
2026-08-30 17:15:42 -04:00
Ryan Hughes 222a742daf Merge pull request #136 from omacom/tagline-beautiful-fun
Change the greeter tagline to "Beautiful, Fun & Opinionated"
2026-08-30 17:03:17 -04:00
Ryan Hughes 9900e63bd4 Merge pull request #139 from omacom/fix/iso-test-ocr-bindings-vnc
Fix omarchy-iso-test staleness against 4.0.2-rc and add VNC visibility
2026-08-30 16:57:08 -04:00
Ryan Hughes 37fd3c4305 Sync the whole test suite and run it in the guest
--sync-omarchy now pushes all of test/ (the security coverage lives in
shell.d as well as acceptance.d), and the acceptance run gets
OMARCHY_ACCEPTANCE_SUDO_PASSWORD so the suite's opt-in sshd hardening
exercise can run against the throwaway VM.

--sync-all additionally runs the CLI and shell suites inside the guest
from the synced tree. The acceptance suite always runs against
/usr/share/omarchy either way: VM runs verify the finished product, and
qs matches shell instances by config path, so pointing the suite at the
synced tree just reads the session shell as "not running".
2026-08-30 16:45:35 -04:00
Ryan Hughes e89c0796f7 Serve a localhost VNC display from test VMs
Runs stay headless, but a viewer on 127.0.0.1:5905 can watch the driven
install live or inspect the acceptance VM left up by --keep-running.
2026-08-30 15:30:37 -04:00
Ryan Hughes 0b2739a55d Match the shortcut smoke to the menu-first bindings
Omarchy now binds Super+Space to the Omarchy menu and Super+Alt+Space to
the apps menu, and the omarchy.launcher plugin is gone (the apps menu
replaced it). The smoke table still expected the old launcher binding, so
both chords failed against current builds. Both surfaces open under the
omarchy-menu layer namespace.
2026-08-30 15:30:31 -04:00
Ryan Hughes 9a7325e3a6 Stop OCR-waiting on the unencrypted install confirm
Tesseract cannot read the highlighted "Yes, install without encryption"
button under any preprocessing, so waiting for that text always timed out
and killed unencrypted installs at the disk warning. Capture the screen and
continue instead; a Ctrl+C toggle that failed to apply still surfaces when
the unexpectedly encrypted install stalls at the LUKS prompt on first boot.
2026-08-30 15:30:21 -04:00
Ryan Hughes bba81a6ec5 End the release by printing the URL, checksum, and local path
The artifacts a release exists to hand out — the download URL and the
checksum to verify it with — were buried in rclone transfer noise or
absent entirely. Print them together as the final output, after the
upload has actually succeeded (set -e means reaching this line is the
success path).
2026-08-30 14:20:08 -04:00
Ryan Hughes 6b5ce68f25 Run docker through sudo when the socket refuses the user
The docker group is root-equivalent, so Omarchy operators stay out of
it on purpose — which left omarchy-iso-make unable to reach the daemon
at all. Fall back to sudo docker when a plain client call cannot reach
the socket; the script already prompts for interactive sudo elsewhere.
DOCKER_ARGS (including HOST_UID/HOST_GID) are assembled before the
escalation, so output ownership is unchanged.
2026-08-30 14:19:21 -04:00
Ryan Hughes 02f8d1cf50 Stop the ISO release when a step fails
omarchy-iso-release ran every step unconditionally: when docker denied
the build, it fell through to signing and uploading the newest stale
*-rc.iso from release/ under the new release's name — a six-month-old
ISO shipped as omarchy-4.0.2-rc.iso. set -e ends the release at the
first failed step, and also aborts when release/ holds no ISO for the
ref at all.
2026-08-30 14:15:01 -04:00
Ryan Hughes d2e764d317 RC ISO installs from the rc package channel
pacman-online-rc.conf's [omarchy] repo moves from pkgs.omarchy.org/edge to
pkgs.omarchy.org/rc: an RC ISO now bundles and installs the release-candidate
packages against the package set stable users will get, instead of edge
churn.
2026-08-26 01:37:17 -04:00
6 changed files with 193 additions and 43 deletions

No files matched your search

+9 -1
View File
@@ -158,7 +158,15 @@ else
DOCKER_ARGS+=(--pull=always)
fi
docker run "${DOCKER_ARGS[@]}" archlinux/archlinux:latest /$BUILD_SCRIPT
# The docker group is root-equivalent, so Omarchy operators stay out of it on
# purpose. When the socket refuses the calling user, run the client through
# sudo instead of requiring group membership.
DOCKER=(docker)
if ! docker version &>/dev/null; then
DOCKER=(sudo docker)
fi
"${DOCKER[@]}" run "${DOCKER_ARGS[@]}" archlinux/archlinux:latest /$BUILD_SCRIPT
latest_iso=$(\ls -t "$BUILD_RELEASE_PATH"/*.iso | head -n1)
iso_ref="${latest_iso%.*}-$OMARCHY_ISO_REF.iso"
+19
View File
@@ -1,5 +1,10 @@
#!/bin/bash
# A failed step must stop the release: without this, a make that dies (say,
# docker permission denied) falls through to signing and uploading whatever
# stale ISO release/ already holds, under the new release's name.
set -e
MAKE_ARGS=()
ISO_REF=quattro
RELEASE_SUFFIX=""
@@ -37,6 +42,14 @@ if [[ -z "${BUILD_VERSION-}" ]]; then
exit 1
fi
# A version that already numbers its candidate (4.0.2rc2) is the complete
# artifact name; the bare -rc suffix exists only for an unnumbered candidate.
# Without this, every candidate of a train uploads over the same
# omarchy-X.Y.Z-rc.iso and testers cannot tell which rc they are holding.
if [[ $BUILD_VERSION == *rc* ]]; then
RELEASE_SUFFIX=""
fi
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
BUILD_RELEASE_PATH="$BUILD_ROOT/release"
@@ -70,3 +83,9 @@ fi
echo -e "\e[32mSHA256 for $ISO_REF ISO: $iso_sha\e[0m"
omarchy-iso-upload "$release_iso"
# The whole point of a release is handing someone this pair.
echo -e "\e[32mReleased $ISO_REF ISO $BUILD_VERSION\e[0m"
echo " URL: https://iso.omarchy.org/${release_iso##*/}"
echo " SHA256: $iso_sha"
echo " Local: $release_iso"
+62 -40
View File
@@ -18,9 +18,10 @@
# first boot runs omarchy-provision-owner and creates it there
# --reuse-base Skip the install phase if a base image already exists
# --install-only Stop after producing the installed base image
# --sync-omarchy DIR Push DIR's test/acceptance into the guest before running
# --sync-omarchy DIR Push DIR's test suite into the guest before running
# (defaults to OMARCHY_PATH when the suite is present)
# --sync-all DIR Push all of DIR (bin/config/shell/test) into the guest
# and also run the CLI and shell suites there
# --port PORT Host port forwarded to guest SSH (default 2222)
# --memory MB VM memory (default 8192)
# --timeout SECS Install timeout (default 2400)
@@ -201,6 +202,11 @@ start_vm() {
local disk="$1" serial="$2"
shift 2
# A viewer that resizes the guest display breaks the console driving: OCR
# cannot read the shrunken font, and a resize during early boot can wedge
# virtio-gpu before the system comes up.
log "Watch (do not resize): vncviewer -RemoteResize=0 127.0.0.1:5905"
qemu-system-x86_64 \
-cpu host -enable-kvm -machine q35,accel=kvm \
-smp "$(nproc)" \
@@ -211,6 +217,7 @@ start_vm() {
-device virtio-blk-pci,drive=drive0,bootindex=1 \
-device virtio-vga \
-display none \
-vnc 127.0.0.1:5 \
-usb -device usb-tablet \
-netdev user,id=net0,hostfwd=tcp:127.0.0.1:$SSH_PORT-:22 \
-device virtio-net-pci,netdev=net0 \
@@ -400,7 +407,7 @@ guest_layer_absent() {
}
cleanup_shortcut_state() {
ssh_session 'for plugin in omarchy.launcher omarchy.menu omarchy.emojis omarchy.clipboard omarchy.audio; do
ssh_session 'for plugin in omarchy.menu omarchy.emojis omarchy.clipboard omarchy.audio; do
omarchy-shell shell hide "$plugin" >/dev/null 2>&1 || true
done
while read -r address; do
@@ -577,8 +584,8 @@ shortcut_smoke_phase() {
local status=0
local name chord namespace plugin
local shortcuts='launcher|meta_l-spc|omarchy-launcher|omarchy.launcher
menu|meta_l-alt-spc|omarchy-menu|omarchy.menu
local shortcuts='menu|meta_l-spc|omarchy-menu|omarchy.menu
apps-menu|meta_l-alt-spc|omarchy-menu|omarchy.menu
emojis|meta_l-ctrl-e|omarchy-emojis|omarchy.emojis
clipboard|meta_l-ctrl-v|omarchy-clipboard|omarchy.clipboard
audio-panel|meta_l-ctrl-a|omarchy-keyboard-panel|omarchy.audio'
@@ -631,7 +638,11 @@ drive_configurator() {
capture_console "success-installer-04-prepare-disk-warning-encrypted"
if ! $ENCRYPT; then
press ctrl-c
wait_for_screen "without encryption" 30
# The toggled confirm ("Yes, install without encryption") is a highlighted
# button OCR cannot read, so don't wait for it. A failed toggle still
# surfaces: the encrypted install stalls at the LUKS prompt on first boot
# and SSH never comes up.
sleep 2
capture_console "success-installer-05-prepare-disk-warning-unencrypted"
fi
press ret
@@ -695,7 +706,11 @@ drive_configurator() {
capture_console "success-installer-12-disk-warning-encrypted"
if ! $ENCRYPT; then
press ctrl-c # toggles the confirm to the unencrypted flow
wait_for_screen "without encryption" 30
# The toggled confirm ("Yes, install without encryption") is a highlighted
# button OCR cannot read, so don't wait for it. A failed toggle still
# surfaces: the encrypted install stalls at the LUKS prompt on first boot
# and SSH never comes up.
sleep 2
capture_console "success-installer-13-disk-warning-unencrypted"
fi
press ret
@@ -875,40 +890,39 @@ EOF
(cd "$BASE_DIR/www" && exec python3 -m http.server "$HTTP_PORT" --bind 127.0.0.1 >/dev/null 2>&1) &
HTTP_PID=$!
local waited=0
while true; do
# Type the login blind: a VNC viewer that resizes the framebuffer makes the
# console font unreadable to OCR, and the real gate is SSH coming up anyway.
# Getty re-prompts after a failed login, so a whole-sequence retry recovers
# from typing before the prompt was ready.
sleep 15 # give first boot time to reach getty
local attempt
for attempt in 1 2 3; do
press ctrl-alt-f3
sleep 8
press ret # settle a half-typed prompt from a previous attempt
sleep 2
type_text "$GUEST_USER"
capture_console "success-first-boot-03-console-username"
press ret
sleep 3
type_text "$GUEST_PASSWORD"
press ret
sleep 4
ocr_screen | grep -qi "login:" && break
((waited += 8))
type_text "curl -fsS http://10.0.2.2:$HTTP_PORT/bootstrap -o /tmp/bs && bash /tmp/bs"
capture_console "success-first-boot-05-bootstrap-command"
press ret
if ((waited >= 300)); then
capture_console "failure-first-boot-console-timeout"
echo "Timed out waiting for a console login prompt" >&2
return 1
if wait_for_ssh 120 "failure-first-boot-ssh-timeout-$attempt"; then
capture_console "success-first-boot-06-bootstrap-complete"
kill "$HTTP_PID" 2>/dev/null || true
HTTP_PID=""
press ctrl-alt-f1
return 0
fi
sleep 4
done
type_text "$GUEST_USER"
capture_console "success-first-boot-03-console-username"
press ret
wait_for_screen "Password" 60
type_text "$GUEST_PASSWORD"
capture_console "success-first-boot-04-console-password"
press ret
sleep 3
type_text "curl -fsS http://10.0.2.2:$HTTP_PORT/bootstrap -o /tmp/bs && bash /tmp/bs"
capture_console "success-first-boot-05-bootstrap-command"
press ret
wait_for_ssh 360 "failure-first-boot-ssh-timeout"
capture_console "success-first-boot-06-bootstrap-complete"
kill "$HTTP_PID" 2>/dev/null || true
HTTP_PID=""
press ctrl-alt-f1
echo "Console bootstrap did not produce SSH access after 3 attempts" >&2
return 1
}
# Get a freshly booted system to a running Hyprland session, typing at the
@@ -1058,17 +1072,14 @@ sync_omarchy() {
log "Syncing $SYNC_DIR into the guest"
tar -C "$SYNC_DIR" --exclude .git -cf - . | ssh_guest "mkdir -p $target && tar -C $target -xf -"
else
log "Syncing $SYNC_DIR/test/acceptance into the guest"
tar -C "$SYNC_DIR/test" -cf - acceptance acceptance.d | ssh_guest "mkdir -p $target/test && tar -C $target/test -xf -"
log "Syncing $SYNC_DIR/test into the guest"
tar -C "$SYNC_DIR" -cf - test | ssh_guest "mkdir -p $target && tar -C $target -xf -"
fi
}
acceptance_phase() {
log "Booting acceptance VM from base image overlay"
local guest_omarchy_path="/usr/share/omarchy"
$SYNC_ALL && guest_omarchy_path=".local/share/omarchy"
qemu-img create -f qcow2 -b "$BASE_DISK" -F qcow2 "$RUN_DIR/run.qcow2" >/dev/null
start_vm "$RUN_DIR/run.qcow2" "$RUN_DIR/serial.log"
@@ -1078,8 +1089,19 @@ acceptance_phase() {
local status=0
shortcut_smoke_phase || status=1
# The synced tree is a full checkout under --sync-all; run the host-style
# CLI and shell suites in the guest too before the graphical acceptance
# run. They derive their root from their own location in the synced tree.
if $SYNC_ALL; then
log "Running CLI and shell suites in the guest"
ssh_guest "bash .local/share/omarchy/test/all" || status=$?
fi
# The product under test is always the installed tree — VM runs verify the
# finished product, never a dev-linked checkout.
log "Running acceptance suite"
ssh_guest "OMARCHY_PATH=$guest_omarchy_path OMARCHY_ACCEPTANCE_DIR=/tmp/omarchy-acceptance bash .local/share/omarchy/test/acceptance" || status=$?
ssh_guest "OMARCHY_PATH=/usr/share/omarchy OMARCHY_ACCEPTANCE_DIR=/tmp/omarchy-acceptance \
OMARCHY_ACCEPTANCE_SUDO_PASSWORD=$GUEST_PASSWORD bash .local/share/omarchy/test/acceptance" || status=$?
log "Collecting artifacts into $RUN_DIR"
ssh_guest "tar -C /tmp -cf - omarchy-acceptance 2>/dev/null" | tar -C "$RUN_DIR" -xf - || true
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
#
# Stop the VM a `omarchy-iso-test --keep-running` run left behind: graceful
# guest poweroff over SSH first, escalating to TERM then KILL. The base image
# is untouched either way; only the throwaway run overlay is affected.
#
# Usage: omarchy-iso-test-stop [--kill] [--port PORT]
#
# --kill Skip the graceful poweroff and kill the VM immediately
# --port PORT Host port forwarded to guest SSH (default 2222)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
GUEST_USER="omarchy"
GUEST_PASSWORD="omarchy"
SSH_PORT=2222
GRACEFUL=true
while (($#)); do
case "$1" in
--kill) GRACEFUL=false ;;
--port) SSH_PORT="$2"; shift ;;
-h | --help)
sed -n '3,10p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
shift
done
# The newest run dir whose qemu is still alive is the VM to stop.
PID=""
PIDFILE=""
while IFS= read -r candidate; do
pid=$(<"$candidate")
if kill -0 "$pid" 2>/dev/null; then
PID="$pid"
PIDFILE="$candidate"
break
fi
done < <(ls -t "$REPO_DIR"/test-runs/*/runs/*/qemu.pid 2>/dev/null)
if [[ -z $PID ]]; then
echo "No running omarchy-iso-test VM found." >&2
exit 1
fi
BASE_DIR="$(dirname "$(dirname "$(dirname "$PIDFILE")")")"
SSH_KEY="$BASE_DIR/id_ed25519"
# A harness that is still driving this VM dies with it; make sure that is
# what the caller wants before pulling anything down.
if [[ -t 0 ]]; then
if pgrep -f "bin/omarchy-iso-test .*\.iso" >/dev/null; then
echo "Warning: an omarchy-iso-test run appears to be in progress."
fi
if ! gum confirm "Stop test VM (pid $PID, ${PIDFILE#$REPO_DIR/})?"; then
exit 0
fi
fi
echo "Stopping test VM (pid $PID, ${PIDFILE#$REPO_DIR/})"
if $GRACEFUL && [[ -f $SSH_KEY ]]; then
ssh -i "$SSH_KEY" -p "$SSH_PORT" \
-o BatchMode=yes \
-o IdentitiesOnly=yes \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o ConnectTimeout=5 \
-o LogLevel=ERROR \
"$GUEST_USER@127.0.0.1" \
"echo $GUEST_PASSWORD | sudo -S systemctl poweroff" >/dev/null 2>&1 || true
waited=0
while kill -0 "$PID" 2>/dev/null && ((waited < 15)); do
sleep 1
((waited += 1))
done
fi
if kill -0 "$PID" 2>/dev/null; then
kill "$PID" 2>/dev/null || true
sleep 1
fi
if kill -0 "$PID" 2>/dev/null; then
kill -9 "$PID" 2>/dev/null || true
sleep 1
fi
if kill -0 "$PID" 2>/dev/null; then
echo "VM (pid $PID) refused to die." >&2
exit 1
fi
echo "VM stopped; port $SSH_PORT is free."
+1 -1
View File
@@ -122,7 +122,7 @@ greeter() {
printf '\033[%d;1H' "$logo_row"
gum style --foreground 2 --padding "0 0 0 $PADDING_LEFT" "$(<"$LOGO_PATH")"
tagline="Beautiful, Fun & Opinionated Linux by DHH"
tagline="Beautiful, Fun & Agentic Linux by DHH"
tpad=$(((cols - ${#tagline}) / 2)); (( tpad < 0 )) && tpad=0
printf '\033[%d;%dH%s' "$tagline_row" "$((tpad + 1))" "$tagline"
+1 -1
View File
@@ -27,7 +27,7 @@ Include = /etc/pacman.d/mirrorlist
[omarchy]
SigLevel = Optional TrustAll
Server = https://pkgs.omarchy.org/edge/$arch
Server = https://pkgs.omarchy.org/rc/$arch
[arch-mact2]
Server = https://mirror.funami.tech/arch-mact2/os/x86_64