Omarchy 4 on Arch Linux ARM
September 9, 2026 Β· View on GitHub
A native aarch64 virtual machine (HVF-accelerated, no emulation) running Arch Linux ARM + Hyprland with the configuration, themes and tooling of Omarchy 4 β built from macOS by a single script, with no manual steps in the UTM interface.

πͺπΈ The detailed guide and the write-up are in Spanish: EMPEZAR.md (how to run it) Β· ARTICULO.md (why it is built this way) Β· README.es.md. This page has everything you need to get going.
Why not just install Omarchy?
Not because it refuses to run β because the packages do not exist. Verified against primary sources on 2026-08-23:
| Check | Result |
|---|---|
stable-mirror.omarchy.org/core/os/aarch64/core.db | 404 (x86_64 β 200) |
install/post-install/pacman.sh | overwrites /etc/pacman.d/mirrorlist with stable-mirror.omarchy.org/$repo/os/$arch |
omarchy.org/install-bare | 404, removed |
omacom-io/omarchy-iso β plans/aarch64-support.md | ARM64 = planned, not implemented |
So the installer points pacman at a mirror with no aarch64 tree, and the first
pacman -Syu on ARM fails. The Omarchy tree itself is architecture-agnostic:
it's shell, Lua and QML.
A correction worth making, because it is repeated a lot: Omarchy 3.x had an
explicit architecture guard β install/preflight/guard.sh, line 25,
[[ $(uname -m) != "x86_64" ]] && abort. Quattro does not. The preflight/
directory is gone and uname -m appears zero times in the whole branch. The
blocker moved from "it refuses" to "there is no repo to install from", which is
a much smaller problem β and one that publishing ~25 aarch64 packages would
close.
This project builds the equivalent base β Arch Linux ARM + Hyprland β and applies the actual contents of the Omarchy repository on top.
The trap: the default branch is quattro, not master
git clone of basecamp/omarchy does not give you master (3.8.5) but the
default branch quattro (4.x). They are different products:
master (3.8.5) | quattro (4.x) | |
|---|---|---|
| Bar | waybar | quickshell (omarchy-shell) |
| Hyprland config | .conf | Lua (hyprland.lua, bootstrap.lua) |
| Distribution | scripts in ~/.local/share | pacman package in /usr/share/omarchy |
What is x86_64-only is the repository the package is published in, so on ARM
you cannot pacman -S omarchy and the files never land. Copy just the dotfiles
and OMARCHY_PATH goes unset, bashrc errors out, Hyprland cannot find
bootstrap.lua, and you get a bare compositor instead of a desktop.
stage3.sh reproduces by hand what that package would have installed, into
/usr/bin β the same place upstream uses.
Correction, 2026-09-04. This section used to say the package is
arch=('any'). That was true when it was written and it is not any more: on 2026-09-02,omacom-io/omarchy-pkgscommit4ed5f14changedomarchyandomarchy-settingstoarch=('x86_64' 'aarch64'), each with its owndepends_<arch>and apackage()that drops the x86 boot and memory stack on ARM. The conclusion is unchanged and re-verified the same day:pkgs.omarchy.org/stable/aarch64/omarchy.dbandstable-mirror.omarchy.org/core/os/aarch64/core.dbare both 404 while their x86_64 counterparts are 200. The recipe can be built for aarch64; the repository still does not exist.
build-omarchy-arm.shβ the one this README describes, and the one that produced the published image β reproduces that package by hand. An earlier version put them in/usr/local/bin, which seemed tidier but broke things: the tree hardcodes/usr/bin/omarchy-*in thirteen places, five of them.servicefiles./usr/local/binis still used, but only for the few ARM-specific wrappers that need to take precedence inPATH.
Most existing guides for Apple Silicon target Omarchy 3.x. This one targets 4.
Or skip the build
The image this produces is on the Internet Archive, sanitised and ready to import β no build, no Homebrew, no waiting:
https://archive.org/details/omarchy-arm-utm β download omarchy-arm-utm-v2.zip Β· 3.6 GB Β·
sha256 d712b243a6f05bf4β¦
The plain omarchy-arm-utm.zip next to it is the first release (6.5 GB). It
keeps the plain name so links and checksums published with it still resolve to
the exact bytes they were written for β which is the only reason the better file
is the one with -v2 in its name. Take the -v2: same desktop, 45% smaller,
and the shared clipboard works. VERSIONS.md on the item compares them.
shasum -a 256 -c omarchy-arm-utm-v2.zip.sha256
unzip omarchy-arm-utm-v2.zip
open *.utm
User omarchy, password omarchy (also root) β change it with passwd.
Quick start (build it yourself)
git clone https://github.com/ggalancs/omarchy-arm-utm.git
cd omarchy-arm-utm
./build-omarchy-arm.sh
Requirements: Apple Silicon Mac, Homebrew, UTM 4.7+, Xcode Command Line
Tools (for git and python3), ~40 GB free. No sudo needed. Outside its
working directory (~/omarchy-arm-build) it installs the Homebrew formulas you
are missing (qemu, expect, aria2), writes the .utm bundle into UTM's own
Documents/, and restarts UTM so it rescans that folder β asking first if you
have VMs running, or if a VM of that name is already registered.
It asks six values that it pre-fills from your Mac β timezone from
/etc/localtime, keyboard from macOS preferences, cores and RAM from sysctl β
so Enter accepts them, then three decisions (compile the tools? include OBS and
Pinta? prepare the image for distribution?) and a couple of follow-ups depending
on the last one. Add --yes to skip all of it; with no tty it never asks.
The script is a single self-contained file. It embeds the nineteen files
it needs β three install stages, the sanitiser, the repair harness, the
optional-app installer, the post-update hook, the clipboard agent, the SPICE
clipboard agent, the shared-folder mounter, the account tool, the GPU switch,
the Hyprland bootstrap check, the display switch, the local-build reporter, two
expect harnesses, the QEMU launcher, the .utm bundle writer and the two
READMEs that ship inside the image β and writes them out at startup. The VM
configuration is not on that list: it is generated from your answers rather
than embedded. You can copy just that file to another Mac.
Three things that file cannot carry with it, and that it will tell you about
rather than skip: scripts/check-alarm-satisfiable.py, the ten-second
pre-flight; checksums/base-images.sha256, the reviewed pins for the two base
images; and the documents that publish the image's sha256 (four of the six
quote it β README.es.md and dist/README.md link to the .sha256 file
instead, and are not compared against).
How long
Measured on an M3 Max, tools compiled, without OBS/Pinta. These are from before the local Hyprland build existed: a run that has to compile hyprtoolkit and hyprland itself adds roughly half an hour, and the first complete run with it took about two hours and a half end to end.
| Phase | Time | |
|---|---|---|
deps | host checks, installs qemu/expect/aria2 | ~10 s |
fetch | Alpine ISO + ALARM rootfs, sha256 and MD5 verified | 2 min |
prepare | package list, computed against Omarchy's live branch | ~10 s |
build | Alpine headless β partition β rootfs β three chroot stages | 40 min |
utm | writes the .utm bundle and registers it | 1 min |
verify | boots and checks inside the guest that the desktop is up | 4 min |
sanitize | copies the disk and strips identity, for distribution | 10 min |
package | compacts the qcow2, builds the bundle, zips it | 3 min |
76β83 minutes end to end with the defaults β tools, OBS Studio and Pinta,
which is what the published image carries β measured across two full runs on the
same M3 Max. The result is a 3.6 GB .zip, and the working directory peaks
at about 24 GB; the script asks for 40 GB free because APFS clones can push that
higher. Saying no to OBS and Pinta cuts roughly 45 minutes.
Every phase is resumable: --from build, --only package, --list.
What you get
- Arch Linux ARM aarch64,
linux-aarch64kernel, btrfs with@/@homesubvolumes, zstd compression, 1 GiB ESP, systemd-boot - Hyprland 0.56.2 with the full Omarchy 4 stack: quickshell (bar, menu, OSD and notification daemon), hyprlock, hypridle, hyprsunset, uwsm, xdg-desktop-portal-hyprland, SDDM with autologin and the Omarchy theme
- Dotfiles, themes and the 456
omarchy-*commands, in/usr/binas upstream's package does - 18 packages built for aarch64 that upstream does not ship for ARM.
Nine are Omarchy's own:
herdr,tensaku,omacalc,omacut,omawrite,ttfx,omarchy-nvim,tobi-tryandhyprland-preview-share-picker. Nine are AUR packages the desktop needs that declarex86_64only:aether,cliamp,mise,tzupdate,yaru-icon-theme,ttf-ia-writer,xdg-terminal-exec,ufw-dockerandyay - Optionally OBS Studio (no browser plugin β its CEF is x86-only) and Pinta (on Microsoft's official arm64 .NET)
qemu-guest-agentandspice-vdagentfor host integrationomarchy-updateworks, with a post-update hook that keeps the Omarchy checkout in sync and snapper snapshots before each update
When this was measured, omarchy-base.packages had 148 entries and 121
existed in Arch Linux ARM by name (123 once you substitute nvimβneovim and
ttf-jetbrains-mono-nerd-basicβttf-jetbrains-mono-nerd), with 17 of the rest
built from source. Upstream's list has moved since β it has 152 entries today β
so treat those as an illustration of the shape of the problem and not as a
count. The build prints the real list on every run, and the eighteen figure
above comes from the tool contract, which is checked rather than remembered.
Known issue in the published image
The image on the Internet Archive installs the omarchy-* commands into
/usr/local/bin. That was my choice β upstream's package uses /usr/bin β and
it turns out the tree hardcodes /usr/bin/omarchy-* in thirteen places, five of
them .service files. Two visible symptoms:
- "Update System" reappears on every login, even when everything is current.
enable-user-units.shfails (those five units point at binaries that aren't where it looks), andomarchy-provision-first-runonly marks first-run done if no step fails β so it repeats forever, re-sending the notification. - "Linux kernel has been updated. Reboot?" on every update. Unrelated cause:
omarchy-update-restartlooks for a package-owned/usr/lib/modules/<ver>/vmlinuz. Arch x86_64'slinuxships one; Arch Linux ARM'slinux-aarch64puts the image in/boot/Imageand ships novmlinuzthere, so the check can never be satisfied and rebooting never helps.
There was a third layer underneath both: the six user .service files were
never installed into /usr/lib/systemd/user/ at all. Upstream ships them in the
omarchy-settings package, which has no ARM build, and the first build did not
reproduce that step β so enable-user-units.sh could not have worked whatever
the paths were.
All three are fixed in omarchy-arm-utm-v2.zip. To repair a VM you already
have, run fixes/18-avisos-que-no-se-apagan.sh
inside it β no need to re-download. For the clipboard, run
fixes/19-portapapeles.sh the same way.
Run fixes/20-seguridad-y-servicios.sh
if your copy's sha256 is not d712b243a6f05bf4β¦. The image published under
that checksum has these five verified by invariants that stop the build if they
regress, so it needs nothing. Every image before it does. Auditing this build against Omarchy's own install/
scripts on 2026-09-04 turned up five differences, two of them about security:
the account was left in the docker group β which Omarchy refuses to grant,
because it is equivalent to passwordless root β and no firewall was ever
enabled, while the system this reproduces ships ufw turned on. It also
enables cups, avahi-daemon, power-profiles-daemon and systemd-resolved,
which upstream turns on and this build did not. Docker is not removed and
sudo docker keeps working. The build script is fixed as well.
What does not work
- Software rendering by default, under UTM 4.7. GPU clients map their
windows and never paint them there, so the image ships
LIBGL_ALWAYS_SOFTWARE=1and blur and shadows are off. Under UTM 5.0.x that bug is gone and the GPU works:omarchy-arm-gpu --on(and--offif anything renders black). The guest cannot tell which UTM is hosting it, which is why this is a command and not a default. - Ships at 1920x1200, changed at runtime with
omarchy-arm-display --retina/--default, measured on the packaged image under UTM 4.7.5. A hand edit of~/.config/hypr/monitors.luastill needs a restart; the command rewrites and reloads in one step, which is what makes it safe. - Single monitor.
Clipboard and shared folder
Clipboard sharing works. Why it did not work with the stock agent is worth knowing: the SPICE chain has three hops, not two:
SPICE client (UTM) βvirtioβ spice-vdagentd βunix socketβ session agent
The daemon talks to the host; the session agent only talks to the daemon. The
stock agent hands the clipboard to X11 β vdagent.c:421 calls
vdagent_clipboards_new(vdagent_display_get_x11(...)), and there is not a
single reference to wlr-data-control in its repo β so under Hyprland it dies
with "cannot open display" and the daemon has nobody to deliver to.
This image replaces the agent, not the daemon: omarchy-arm-vdagent speaks
the same protocol to vdagentd and uses wl-copy/wl-paste. It starts with
the session. Text only β no images, no files.
Two things that took a while to find:
spice-vdagentdneeds-X. Its "active seat0 session" check (vdagentd.c:746) fails with Hyprland launched from SDDM, and it then drops the clipboard silently.- One agent per session. If the stock one also starts,
vdagentddrops both: "multiple agents in one session".
And the non-obvious requirement: the VM must be open as a window in UTM.
Started via utmctl there is no SPICE client attached, so the channel exists
but carries nothing.
The shared folder
Mount it with omarchy-arm-share, which detects whichever mode you picked in
VM Settings β Sharing: VirtFS (9p, at /mnt/share) or SPICE WebDAV (via
spice-webdavd, already in the image). --status reports which one is live.
Keyboard on a Mac
macOS grabs Cmd before UTM sees it, so the VM ships with Alt and Super swapped
via altwin:swap_lalt_lwin:
| Mac key | In the VM |
|---|---|
| Option (β₯) | SUPER |
| Cmd (β) | ALT |
β₯+Space opens the Omarchy menu, β₯+Return a terminal, β₯+K the full keybinding list.
If the desktop comes up with no keybindings
Omarchy's whole session is loaded by one line at the end of
~/.config/hypr/hyprland.lua. Without it Hyprland starts with no binds
registered: nothing crashes, nothing is logged, SUPER does nothing.
omarchy-arm-hypr-check # says whether the line is there
omarchy-arm-hypr-check --fix # puts it back, keeps a copy, reloads
It also runs on login and is silent unless the line is missing. Do not
press SUPER+R or run uwsm stop first: both land on the SDDM greeter, which
asks for a password you may not be able to type from there.
Graphics: omarchy-arm-gpu
The image ships with software rendering forced on (LIBGL_ALWAYS_SOFTWARE=1),
because it is the setting that works on every host. If your UTM has GPU
acceleration available, turning it off is a large win:
omarchy-arm-gpu --on # use the GPU
omarchy-arm-gpu --off # back to software rendering
Log out and back in, then check with glxinfo -B: if it no longer says
llvmpipe, the GPU is doing the work. If the desktop misbehaves, --off
restores the shipped setting.
Changing the autologin user
If you rename the account or add another one, point autologin at it:
omarchy-arm-user # list the accounts on the machine
omarchy-arm-user someuser # set autologin to that account
omarchy-arm-user --ask # turn autologin OFF: SDDM will ask on every boot
With an account name it edits [Autologin] User= in
/etc/sddm.conf.d/autologin.conf and leaves Session= alone. --ask does not
pick an account β it removes that file, so the greeter asks for a username and
a password from the next boot. To go back, run it again with an account name.
The image ships UTC. Set yours with sudo timedatectl set-timezone <zone> (timedatectl list-timezones lists them). Earlier images carried the builder's own timezone, which is why the clock was wrong out of the box.
Proprietary apps
1Password, Obsidian, Typora, LocalSend and Google Chrome are not in the image β not because they do not work (they all have official ARM64 builds) but because shipping them would mean redistributing third-party binaries. The image carries an installer that fetches them from their official source, on your machine:
omarchy-arm-extras --list
omarchy-arm-extras # interactive menu
omarchy-arm-extras --all # everything missing
Spotify has no native ARM client, but the web app works β it needs Widevine,
which ships inside Google Chrome arm64 (omarchy-arm-extras chrome spotify-web).
Where the VM is written, and what is cleaned up
By default the builder writes its VM into UTM's own documents directory, which is how UTM finds it. Two environment variables change that:
DEST_DIR=/Volumes/External ./build-omarchy-arm.sh --yes # build without touching UTM
KEEP_INTERMEDIATE=1 ./build-omarchy-arm.sh --yes # keep the build VM afterwards
The VM the build works in is scaffolding: verify uses it and what ships is the
sanitized copy in dist/. A run that reaches the end deletes it, by the UUID it
recorded when it created it, so a VM you made yourself is never touched.
KEEP_VM=yes keeps it.
A run that stops at the checksum gate leaves it, deliberately: --from sanitize takes its source disk from exactly that VM, and the gate's message
tells you it is there. That is why several can pile up while an image is being
iterated on -- the fix is to finish the gate, not to delete earlier.
Your own apps: scripts/my-apps.sh
omarchy-arm-extras covers a fixed list. For anything else, copy
scripts/my-apps.sh into the VM, write a plain list of
package names, and run it.
./my-apps.sh --example > my-apps.txt # a starter list
./my-apps.sh --check my-apps.txt # check only, install nothing
./my-apps.sh my-apps.txt # check, then install
It exists for one reason specific to ARM: a lot of Arch software has no aarch64 build, and finding that out one package at a time costs an afternoon. The script resolves every name first β official repo, AUR, or nowhere β prints the verdict, and only then offers to install, so you never end up half done. Packages with no aarch64 build are named explicitly rather than failing mid-transaction.
To bake your apps into an image you hand to someone else, install them in the VM and repackage from the host:
./build-omarchy-arm.sh --from sanitize
That strips identity and credentials and produces a distributable .zip.
Ollama, LM Studio, and the community package repo
Neither is in the image, and until now neither was named anywhere here, which is why someone opened an issue asking for them to be packaged.
Ollama runs. ollama-bin in the AUR declares
arch=('x86_64' 'aarch64'), so yay builds and installs it inside the VM:
yay -S ollama-bin
LM Studio has an arm64 build that nobody has packaged. lmstudio.ai does
publish one:
https://installers.lmstudio.ai/linux/arm64/0.4.23-1/LM-Studio-0.4.23-1-arm64.AppImage
answers 200 today. The AUR package lmstudio-bin is not it: that PKGBUILD
declares arch=('x86_64') and the only thing it downloads is the x64 AppImage,
so yay -S lmstudio-bin cannot work here. Installing LM Studio on this image
means fetching the vendor's arm64 build by hand. Read off the PKGBUILD and the
vendor's installer host on 2026-09-07; check both again before believing this
page.
The community repo, in full.
omarchy-pkgs-aarch64
builds for aarch64 what upstream publishes only for x86_64. Both this page and
the README inside the image mention it; neither gave the stanza you need to
actually add it, which is most of the point. This is the one that project
publishes, appended to /etc/pacman.conf:
[omarchy-aarch64]
SigLevel = Optional TrustAll
Server = https://github.com/omarchy-mac/omarchy-pkgs-aarch64/releases/download/edge
sudo pacman -Sy
Optional TrustAll is what it sounds like: pacman will install those packages
without checking a signature, because there is none to check. It is the same
level Omarchy's own repo uses. If that is not a trade you want, this image
already builds the Omarchy packages from source, and anything else can be built
from its PKGBUILD with makepkg.
Things that were hard to find
- The ESP is mounted after extracting the rootfs. The ALARM tarball has
symlinks in
/bootand vfat cannot hold them. bootctl install --no-variables. The build VM's NVRAM does not travel to UTM, so booting relies on the fallback path\EFI\BOOT\BOOTAA64.EFI.- The
.utmbundle is written by hand.utmctlcannot create VMs and UTM only scansDocuments/at app launch.config.plistneeds all twelve top-level keys β they are decoded withdecode(), notdecodeIfPresent(). - The VARS half of aarch64 UEFI is
edk2-arm-vars.fd, notedk2-aarch64-vars.fd(which does not exist). - Seal Omarchy's migrations. A normal installer marks them all on
completion; without that,
omarchy-updatereplays ~80 historical migrations and dies on the first x86-only package. grep -rdoes not see symlink targets. After renaming the build user, a text sweep reported zero matches while 439 links dangled β including all 431omarchy-*commands. Usefind -type l -lname.- A distro's package list is a claim about its architecture. Filling gaps
from memory of the previous version reintroduced
mako,swayosd,walkerandelephant, which Omarchy 4 deliberately retires β andmakostealsorg.freedesktop.Notificationsfrom the shell over D-Bus. - A success message that depends on nothing. With
set -uo pipefailand no-e, four of the eight phases were structurally incapable of failing.
The full write-up, including the audit that found 37 defects in this very script, is in ARTICULO.md (Spanish).
Prior art, and where this fits
This is not the only attempt, and for bare-metal Apple Silicon it is not the best one. Verified 2026-08-23:
| Project | Target | |
|---|---|---|
| omarchy-mac/omarchy-mac | Omarchy 4 on Asahi Alarm, M1/M2 bare metal, one command, LUKS | 896β , active |
| omarchy-mac/omarchy-pkgs-aarch64 | The aarch64 pacman repo that upstream doesn't publish | the missing piece |
| jondkinney/armarchy | Omarchy 3.x on Asahi and VMs | 33β |
| Linux-for-Fydetab-Duo/imagebuild | Omarchy 4 on RK3588S hardware | new |
If you have an M1 or M2 and want Omarchy on the metal, use omarchy-mac. It is more mature than this and it gives you a real GPU.
This project covers what that one cannot:
- M3, M4 and whatever comes next. Asahi's installer supports M1 and M2 only; M3 is work in progress and M4 is not started. A VM works on any of them.
- Keeping macOS untouched. No partitioning, no reduced-security boot.
- External monitors. Asahi still lists USB-C displays and Thunderbolt as unsupported; in a VM the host handles the screens.
The trade is the one stated above: no GPU acceleration inside the VM.
Layout
build-omarchy-arm.sh the autonomous builder, with everything embedded
EMPEZAR.md how to run it (ES) β requirements, timings, troubleshooting
ARTICULO.md how it was figured out (ES)
provision/src/ stage1..3.sh, repair.sh, sanitize.sh, omarchy-arm-extras, hooks/
scripts/ qemu, expect harnesses, .utm bundle writer
fixes/ the 20 corrections found along the way, as a record
dist/README.md the README that ships inside the image
Status
Validated by a full from-scratch run on 2026-08-25: 8/8 phases, 76 minutes,
rc=0, from an empty working directory with --yes.
The guest-side verdict, read back over the serial console:
### H=1 Q=1 BINS=445 ROTOS=1 UNITS=7 VER=4 CLIP=5/5
VEREDICTO_OK
That is the output of the builder as it stood on 2026-08-25, kept as the
record of that run. Today's verdict line carries thirteen fields and the tokens
are in English: ### H= Q= BINS= BROKEN= UNITS= VER= DOCKERGRP= UFW= LDD= HYPRDEPS= REC= PACDB= CLIP= followed by VERDICT_OK.
All 18 packages build, herdr included: it comes from Omarchy's own PKGBUILD,
which declares aarch64 and fetches the official Zig 0.15.2 from ziglang.org
rather than relying on whatever the repos happen to ship.
The packaged image β not the intermediate VM β was then booted read-only
(qemu -snapshot) and checked from outside: generic user with the build account
gone, Hyprland and quickshell up, spice-vdagentd running with -X and the
clipboard agent alive, sshd disabled, no SSH host keys.
One item on that list was not actually checked, and this is the correction.
"No build-time paths inside the compiled binaries" was produced by a sweep that
could not fail: strings "$b" | grep -q "/home/$OLD" under set -o pipefail
returns 141 the moment it matches, so the branch that records a hit never ran.
It printed its green line on every image regardless. The sweep is fixed, it now
reads the file directly and covers /usr/bin and /usr/lib as well, and the
claim will be made again only when a run that can fail has passed.
The shared clipboard was then checked with real data, in both directions, on a VM booted in UTM: a unique token copied on the Mac read back identically inside the guest, and one copied inside the guest reached the Mac pasteboard in three seconds. It needs the VM open as a window β started headless there is no SPICE client attached, so the channel exists but carries nothing.
One caveat worth stating: that single warning is not zero.
Licence
MIT for this repository's code. Omarchy, Arch Linux ARM, Hyprland and the rest keep their own. Unofficial work, unaffiliated with Basecamp or the Omarchy project. Omarchy supports x86_64; when the aarch64 ISO they already have planned ships, this stops being necessary.