Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,13 @@ port. The `dtc` mirror should be reverted once kernel.org returns.
2. Open the DMG and drag **Try Omarchy** to **Applications**.
3. Launch **Try Omarchy** from Applications.

Every launch begins at the start menu. While that menu is open, Try Omarchy behaves like a regular Mac app with standard Quit, Close Window, and Minimize commands; after the VM starts, that native app chrome steps aside for Omarchy. **Immersive** is on by default, so Omarchy opens Full Screen with the Mac menu bar and Dock hidden. Turn it off to open a resizable window; if you later enter Full Screen, the Mac menu bar and Dock remain available at the screen edges. Whenever the Omarchy window is focused, Command belongs to the guest as Super in either mode; Accessibility permission lets system shortcuts such as Command-Space reach it before macOS. Microphone and camera access are optional. The first launch takes longer while the app prepares Linux and starts Omarchy's account provisioning.
By default, every launch begins at the start menu. Enable **Start automatically** to skip this menu on subsequent launches and start Omarchy using your saved settings. Hold **Option** while opening the app to show the menu again and change settings or turn automatic startup off. Reset requests still show the confirmation flow. Startup checks still show any required recovery or error dialogs.

While that menu is open, Try Omarchy behaves like a regular Mac app with standard Quit, Close Window, and Minimize commands; after the VM starts, that native app chrome steps aside for Omarchy. **Immersive** is on by default, so Omarchy opens Full Screen with the Mac menu bar and Dock hidden. Turn it off to open a resizable window; if you later enter Full Screen, the Mac menu bar and Dock remain available at the screen edges. Whenever the Omarchy window is focused, Command belongs to the guest as Super in either mode; Accessibility permission lets system shortcuts such as Command-Space reach it before macOS. Microphone and camera access are optional. The first launch takes longer while the app prepares Linux and starts Omarchy's account provisioning.

Inside Omarchy, choose **Setup → Try Omarchy Settings**, search for **Try Omarchy Settings**, or run `omarchy-native-settings` to reopen the Mac settings window. You can change automatic startup, permissions, CPU, memory, sharing, port forwarding, and immersive mode here. CPU, memory, sharing, ports, and immersive mode are saved for the next launch; **Restart Try Omarchy…** shuts down Linux and starts a new VM process to apply them. Save your work first. A disposable VM keeps its disk across this restart until you close the app.

For VM location and reset, choose **Shut down to manage…**. The settings window stays open even with automatic startup enabled; reset still asks for confirmation. **Done** or closing the running settings window returns to Omarchy without stopping it. Existing VMs [receive settings access automatically](guest/README.md#settings-access-from-an-existing-vm) when launched with the updated app, without a reset or manual installation.

Restarting from inside Omarchy reboots the guest in the same Try Omarchy app.
Shutting down Omarchy closes the app and leaves it closed.
Expand Down
17 changes: 8 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,14 @@ creates the account on first boot.
- The guest normally consumes upstream Arch Linux ARM packages. Hyprland is the
documented exception: an upstream package is reproducibly rebuilt with a
guarded rounded-border coverage patch for the VM graphics path, then held in
the guest's immutable local repository. While that pin still needs
`libaquamarine.so=13`, the factory rebuilds `aquamarine 0.14.0-2` from the
reviewed Arch PKGBUILD and upstream tarball, then rebuilds Hyprtoolkit
against that library. Both packages are provided by the disposable builder
repository and held alongside Hyprland on guest `IgnorePkg`; mixing the
newer mirror Hyprtoolkit with the older aquamarine cannot resolve. Source
and library hashes are verified, and build paths are remapped for repeatable
output. The ABI builder must pass from an empty cache before refreshing the
transaction lock.
the guest's immutable local repository. The factory rebuilds
`aquamarine 0.15.1-1` from a reviewed Arch-derived PKGBUILD and upstream
tarball, then rebuilds Hyprtoolkit against its `libaquamarine.so=14` ABI,
matching Hyprland 0.56.2. Both packages are provided by the disposable
builder repository and held alongside Hyprland on guest `IgnorePkg`.
Source and library hashes are verified, and build paths are remapped for
repeatable output. The factory uses an official HTTPS ARM mirror and checks
the complete transaction against its reviewed package lock before installing.
- The final Arch Linux ARM pacman files live under `/usr/share/try-omarchy/`.
An Omarchy-supported `pre-refresh-pacman` hook restores them after a channel
refresh writes its x86_64 templates to `/etc`; the upstream templates remain
Expand Down
33 changes: 33 additions & 0 deletions guest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,36 @@ The vendor service generates missing host keys on the writable guest disk. A
persistent VM therefore keeps its identity across restarts and app updates,
while a Factory Reset or a fresh ephemeral VM gets a new identity. The factory
image must never contain shared SSH host private keys.

## Settings access from an existing VM

New factory images include **Setup → Try Omarchy Settings** and a searchable
application entry. Both run `omarchy-native-settings`, which sends
`open-settings\n` through `/dev/virtio-ports/dev.tryomarchy.settings`. The Mac
app replies `opened\n` after presenting its window, or `unavailable\n` if it
cannot present settings. The command times out after three seconds and reports
errors through a desktop notification and stderr. The channel only opens the
settings UI; it does not accept preference values or other host commands.

The updated Mac app installs these entry points on existing disks at boot. A
separate read-only 9p share contains only the bundled settings installer and its
files. A systemd boot credential supplies a temporary service that installs
those files, reloads the udev rule, and unmounts the share. This uses systemd's
extra-unit credentials (available since version 256, included in the supported
factory guest) and leaves the guest's default boot target unchanged. Failure is
logged under `try-omarchy-settings.service` and does not prevent normal boot.
The service has a 20-second timeout and retries on the next launch.

Installation is idempotent. It does not reset the disk, upgrade Linux packages,
or require network access or a user `sudo` command. Existing user menu files
are preserved; those users can search for **Try Omarchy Settings** in the
application launcher. Accounts without a custom extension file also receive
**Setup → Try Omarchy Settings**. Home-directory operations run as that user.

The settings window saves CPU, memory, sharing, port forwarding, and immersive mode for the
next QEMU launch. **Restart Try Omarchy…** requests a clean Linux shutdown and
waits for QEMU to exit before starting a new process with the saved settings.
It never forces a shutdown on a timer. **Shut down to manage…** returns to the
native settings window without automatic startup so location and reset remain
accessible. A normal Linux reboot keeps the current QEMU process and therefore
does not apply these launch settings.
19 changes: 19 additions & 0 deletions guest/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@ for command in pacstrap arch-chroot curl git gzip python3 mke2fs mount umount re
command -v "$command" >/dev/null || fail "$command is required; use the supplied Arch builder container"
done

# Bootstrap Omarchy's package signing key from the same reviewed packaging
# commit as the builder. Fresh builds must not depend on a public keyserver.
(
cd "$guest_dir/keys"
sha256sum -c <<'KEYRING_SUMS'
15d6aac44df688165b2ea35fe0b23af239bbc66a6909c10a5c219e8d94b707de omarchy.gpg
ab0b688815444cffd48d15ca3597c77dbb364d59763c5784fca36691520f00fd omarchy-trusted
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 omarchy-revoked
KEYRING_SUMS
)
install -m 0644 "$guest_dir/keys/omarchy.gpg" "$guest_dir/keys/omarchy-trusted" \
"$guest_dir/keys/omarchy-revoked" /usr/share/pacman/keyrings/
pacman-key --populate omarchy

output=$(mkdir -p "$output" && cd "$output" && pwd)
work=$(mkdir -p "$work" && cd "$work" && pwd)
if [[ -z $source_dir ]]; then
Expand Down Expand Up @@ -223,6 +237,11 @@ python3 "$guest_dir/scripts/resolve-package-lock.py" \
ln -sfn /proc/self/fd/2 "$dev_root/stderr"
)

# Keep a fresh guest-local keypair while seeding the reviewed repository keys
# before the initial transaction verifies the keyring packages themselves.
pacman-key --gpgdir "$root/etc/pacman.d/gnupg" --init
pacman-key --gpgdir "$root/etc/pacman.d/gnupg" --populate archlinuxarm omarchy

# pacstrap reads configured CacheDir paths for host-cache mode only with -P.
# The copied builder config is replaced by configure-rootfs below. Archives
# remain under $work across failed staging roots and are still signature-checked.
Expand Down
Empty file added guest/keys/omarchy-revoked
Empty file.
1 change: 1 addition & 0 deletions guest/keys/omarchy-trusted
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
40DFB630FF42BCFFB047046CF0134EE680CAC571:4:
13 changes: 13 additions & 0 deletions guest/keys/omarchy.gpg
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEaLAsIhYJKwYBBAHaRw8BAQdAYmKmrWMlfpdIPh3QzvEMzzxdNd/kqDl/Bj8H
mCavKji0Gk9tYXJjaHkgPHBrZ3NAb21hcmNoeS5vcmc+iJAEExYKADgWIQRA37Yw
/0K8/7BHBGzwE07mgMrFcQUCaLAsIgIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIX
gAAKCRDwE07mgMrFcRF2AQCjjvjgju/kowkN69nenqqRnE+v2MrHmWh2wU0ugIt9
3gD/bXGugWc5J1vqoT+5aCnJAtfRRmHEfsrAiFgjduB+VA24OARosCwiEgorBgEE
AZdVAQUBAQdAJoU73WdgxE/pEYK5ofyRvQDs1IFRdfJHV9j4Jm+tuDUDAQgHiHgE
GBYKACAWIQRA37Yw/0K8/7BHBGzwE07mgMrFcQUCaLAsIgIbDAAKCRDwE07mgMrF
cYXsAQC5eBEGK0xKsGwtnDDVeNAapx32dpvLgJ94W4DtIpdLlwEA85cSsTiBMuGy
6kwYmHzqw7oFx3VYGXdMNl0SMKzvJA4=
=G7wU
-----END PGP PUBLIC KEY BLOCK-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"setup.try-omarchy": {
"icon": "",
"label": "Try Omarchy Settings",
"description": "Open the Mac app settings",
"action": "omarchy-native-settings",
"when": "test -w /dev/virtio-ports/dev.tryomarchy.settings"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SUBSYSTEM=="virtio-ports", ATTR{name}=="dev.tryomarchy.settings", GROUP="users", MODE="0660"
68 changes: 68 additions & 0 deletions guest/native-overlay/usr/local/bin/omarchy-native-settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Ask the Mac app to show settings over dev.tryomarchy.settings."""

import os
from pathlib import Path
import select
import subprocess
import sys
import time

PORT = Path("/dev/virtio-ports/dev.tryomarchy.settings")


def open_settings(descriptor, timeout=3.0):
"""Send one bounded request and wait for the Mac app to acknowledge it."""
deadline = time.monotonic() + timeout
pending = b"open-settings\n"
response = bytearray()
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError("The Mac app did not respond. Close and reopen Try Omarchy, then try again.")
readable, writable, _ = select.select(
[] if pending else [descriptor], [descriptor] if pending else [], [], remaining
)
try:
if writable:
written = os.write(descriptor, pending)
if written == 0:
raise OSError("The Mac settings connection closed.")
pending = pending[written:]
if readable:
data = os.read(descriptor, 64)
if not data:
raise OSError("The Mac settings connection closed.")
response.extend(data)
if b"\n" in response:
if response == b"opened\n":
return
raise OSError("The Mac app cannot open settings right now. Try again when Omarchy is running.")
if len(response) >= 64:
raise OSError("The Mac app sent an invalid settings response.")
except BlockingIOError:
continue


def main():
try:
descriptor = os.open(PORT, os.O_RDWR | os.O_NONBLOCK | os.O_CLOEXEC)
try:
open_settings(descriptor)
finally:
os.close(descriptor)
except (OSError, TimeoutError) as error:
message = str(error)
if isinstance(error, FileNotFoundError):
message = "This VM needs a version of the Try Omarchy Mac app with settings access."
print(f"Try Omarchy Settings: {message}", file=sys.stderr)
try:
subprocess.run(["notify-send", "Try Omarchy Settings", message], check=False, timeout=3)
except (OSError, subprocess.TimeoutExpired):
pass
return 1
return 0


if __name__ == "__main__":
sys.exit(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=Try Omarchy Settings
Comment=Open the Try Omarchy Mac app settings
Exec=omarchy-native-settings
TryExec=omarchy-native-settings
Icon=preferences-system
Terminal=false
Categories=Settings;
Keywords=Mac;VM;Startup;
50 changes: 25 additions & 25 deletions guest/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
"alsa-ucm-conf": "1.2.16.1-1",
"aom": "3.15.0-1",
"appstream": "1.2.0-1",
"aquamarine": "0.14.0-2",
"aquamarine": "0.15.1-1",
"archlinux-keyring": "20260909-1",
"archlinuxarm-keyring": "20240419-2",
"at-spi2-core": "2.60.6-1",
"at-spi2-core": "2.60.7-1",
"attr": "2.6.0-1",
"audit": "4.2.1-1",
"avahi": "1:0.9rc5-1",
"base": "3-3",
"bash": "5.3.15-1",
"bash": "5.3.20-1",
"bash-completion": "2.18.0-1",
"bat": "0.26.1-2",
"bat": "0.26.1-3",
"benchmark": "1.9.5-2",
"binutils": "2.46+r70+g155188ea10a7-1",
"bluez-libs": "5.87-2",
Expand Down Expand Up @@ -117,7 +117,7 @@
"gperftools": "2.18.1-1",
"gpgme": "2.2.0-1",
"gpgmepp": "2.2.0-1",
"gpm": "1.20.7.r38.ge82d1a6-6",
"gpm": "1.20.7.r38.ge82d1a6-7",
"graphene": "1.10.8-2.1",
"graphite": "1:1.3.15-1",
"grep": "3.12-2",
Expand All @@ -139,9 +139,9 @@
"gupnp": "1:1.6.10-1",
"gupnp-dlna": "0.12.0-5",
"gupnp-igd": "1.6.0-2",
"gvfs": "1.60.3-1",
"gvfs": "1.60.3-3",
"gzip": "1.14-2",
"harfbuzz": "14.4.0-1",
"harfbuzz": "14.5.0-1",
"hdf5": "2.2.0-1",
"hicolor-icon-theme": "0.18-1",
"hidapi": "0.15.0-1",
Expand All @@ -150,12 +150,12 @@
"hwloc": "2.14.0-1",
"hyprcursor": "0.1.13-7",
"hyprgraphics": "0.5.1-4",
"hyprland": "0.56.1-3",
"hyprland-guiutils": "0.2.2-2",
"hyprland": "0.56.2-3",
"hyprland-guiutils": "0.2.2-3",
"hyprlang": "0.6.8-5",
"hyprpicker": "0.4.7-4",
"hyprsunset": "0.4.0-3",
"hyprtoolkit": "0.5.4-6.1",
"hyprtoolkit": "0.5.4-6.2",
"hyprutils": "0.14.2-1",
"hyprwayland-scanner": "0.4.6-1",
"hyprwire": "0.3.1-3",
Expand All @@ -164,7 +164,7 @@
"imagemagick": "7.1.2.31-1",
"imath": "3.2.3-1",
"imv": "5.0.1-2",
"iniparser": "4.2.6-2",
"iniparser": "4.3.0-1",
"inotify-tools": "4.25.9.0-1",
"iproute2": "7.2.0-1",
"iptables": "1:1.8.13-1",
Expand All @@ -174,7 +174,7 @@
"jansson": "2.15.1-1",
"jasper": "4.2.9-1",
"jbigkit": "2.1-8",
"jemalloc": "1:5.3.1-3",
"jemalloc": "1:5.4.0-1",
"jq": "1.8.2-1",
"json-c": "0.19-1",
"json-glib": "1.10.8-1",
Expand All @@ -186,7 +186,7 @@
"lame": "4.0-1",
"lcms2": "2.19.1-1",
"leancrypto": "1.9.0-1",
"less": "1:704-1",
"less": "1:710-1",
"libadwaita": "1:1.9.4-1",
"libaec": "1.1.7-1",
"libarchive": "3.8.9-1",
Expand Down Expand Up @@ -266,7 +266,7 @@
"libimobiledevice": "1.4.0-2",
"libimobiledevice-glue": "1.3.2-1",
"libinih": "62-2",
"libinput": "1.31.3-1",
"libinput": "1.32.0-1",
"libiptcdata": "1.0.5-5",
"libisl": "0.28-1",
"libjpeg-turbo": "3.2.0-2",
Expand Down Expand Up @@ -309,7 +309,7 @@
"libopenmpt": "0.8.9-1",
"libosinfo": "1.12.0-3",
"libp11-kit": "0.26.5-1",
"libpcap": "1.10.7-1",
"libpcap": "1.11.0-1",
"libpciaccess": "0.19-1",
"libpgm": "5.3.128-4",
"libpipeline": "1.5.8-1",
Expand All @@ -330,7 +330,7 @@
"libsamplerate": "0.2.2-3",
"libsasl": "2.1.28-5",
"libseccomp": "2.6.0-1",
"libsecret": "0.21.7-1",
"libsecret": "0.21.8.2-1",
"libsixel": "1.10.5-1",
"libsm": "1.2.6-1",
"libsndfile": "1.2.2-4",
Expand Down Expand Up @@ -425,7 +425,7 @@
"man-db": "2.13.1-2",
"md4c": "0.5.3-1",
"mdadm": "4.6-2",
"mesa": "1:26.2.2-1",
"mesa": "1:26.2.3-1",
"minizip": "1:1.3.2-3",
"mkinitcpio": "42-1",
"mkinitcpio-busybox": "1.36.1-1",
Expand All @@ -436,7 +436,7 @@
"mpv": "1:0.41.0-6",
"msgpack-c": "7.0.1-1",
"mtdev": "1.1.7-1",
"mujs": "1.3.9-1",
"mujs": "1.3.10-1",
"muparser": "2.3.5-2",
"nautilus": "50.3.1-1",
"ncurses": "6.6-2",
Expand Down Expand Up @@ -513,9 +513,9 @@
"qt6-wayland": "6.11.2-1",
"quickshell": "0.3.1-1",
"re2": "2:2025.11.05-6",
"readline": "8.3.003-1",
"readline": "8.3.6-1",
"ripgrep": "15.2.0-1",
"rpm-sequoia": "1.10.2-2",
"rpm-sequoia": "1.10.3-1",
"rpm-tools": "6.0.1-2",
"rubberband": "4.0.0-2",
"sbc": "2.2-1",
Expand Down Expand Up @@ -573,16 +573,16 @@
"upower": "1.91.4-1",
"util-linux": "2.42.3-1",
"util-linux-libs": "2.42.3-1",
"uwsm": "0.26.7-1",
"uwsm": "0.27.0-1",
"v4l-utils": "1.32.0-2",
"v4l2loopback-dkms": "0.15.4-2",
"vapoursynth": "79-1",
"vid.stab": "1.1.2-1",
"vmaf": "3.2.0-1",
"vmaf": "3.2.1-1",
"volume_key": "0.3.12-12",
"vulkan-icd-loader": "1.4.357.0-1",
"vulkan-mesa-implicit-layers": "1:26.2.2-1",
"vulkan-swrast": "1:26.2.2-1",
"vulkan-mesa-implicit-layers": "1:26.2.3-1",
"vulkan-swrast": "1:26.2.3-1",
"wayland": "1.26.0-1",
"wayland-protocols": "1.49-1",
"webrtc-audio-processing-1": "1.3-5",
Expand Down Expand Up @@ -619,7 +619,7 @@
"xorg-xwayland": "24.1.13-1",
"xorgproto": "2025.1-1",
"xvidcore": "1.3.7-4",
"xxhash": "0.8.3-1",
"xxhash": "0.8.4-1",
"xz": "5.8.4-1",
"yoga": "3.2.1-1",
"yyjson": "0.13.0-1",
Expand Down
2 changes: 1 addition & 1 deletion guest/pacman.aarch64.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ HoldPkg = pacman glibc
# installed modules and DKMS headers on that exact ABI. Hyprland is likewise
# held until upstream includes our rounded-border coverage backport; otherwise
# a routine full-system update can silently restore the defect. Hold the
# compatible aquamarine/Hyprtoolkit pair too: newer ALARM builds require a
# reviewed aquamarine/Hyprtoolkit pair too: later ALARM builds may require a
# different libaquamarine.so and abort the updater with mixed ABI requirements.
IgnorePkg = linux-aarch64 linux-aarch64-headers hyprland aquamarine hyprtoolkit
Architecture = auto
Expand Down
Loading
Loading