No description
Find a file
sam 77a2ad1e60 Take the mirrorlists from their packages, never from the host
pacstrap copies the host's mirrorlist into the target, and step 4a
preferred the host's copy over the package's when it had one. Neither works
from a host that is not Arch, and both leave the target holding a file no
package owns.

pacstrap -M turns the copy off; step 4a uncomments the first five servers
of the file pacman-mirrorlist installed. Arch comments out all 425 for an
installer to choose from, and a file of nothing but comments leaves core,
extra and multilib with no server at all.
2026-08-27 11:44:11 +02:00
bin Take the mirrorlists from their packages, never from the host 2026-08-27 11:44:11 +02:00
conf Bring the README up to the four-command flow 2026-08-27 00:45:01 +02:00
hooks Let the hardware scripts install what the machine needs 2026-08-27 00:37:44 +02:00
lib Shim vercmp as well 2026-08-27 10:52:36 +02:00
patches Drop the Development menu by key, not by patch 2026-08-27 09:44:20 +02:00
vendor Shim vercmp as well 2026-08-27 10:52:36 +02:00
.gitignore Ship pacman-static with the repo 2026-08-27 01:44:46 +02:00
README.md Bring the README up to the four-command flow 2026-08-27 00:45:01 +02:00

Omarchy 4.0.0 on a CachyOS base

Installing Omarchy 4.0.0 on a CachyOS system, without using the ISO installer.

Why adding the CachyOS repos to Omarchy doesn't work

Omarchy doesn't use the Arch mirrors. The pacman.conf its installer writes points core, extra and multilib at its own versioned mirrors:

[core]     → https://stable-mirror.omarchy.org/$repo/os/$arch
[extra]    → https://stable-mirror.omarchy.org/$repo/os/$arch
[omarchy]  → https://pkgs.omarchy.org/stable/$arch

Those are snapshots promoted upstream, while CachyOS rolls with current Arch: two timelines of the same repos. Putting them together, with CachyOS on top, pulls rolling glibc and gcc underneath a userspace built against the snapshot — the partial upgrade that breaks Arch.

This project inverts the hierarchy: CachyOS is the base, and only the delta comes from Omarchy — the 19 packages CachyOS doesn't carry, installed from files with pacman -U.

The install turned out to be practical because the Omarchy 4.0.0 snapshot and the CachyOS repos share the same glibc (2.44+r24+g16be1518495f-1, same commit), and the highest GLIBC_ symbol any Omarchy binary needs is 2.39.

How the official ISO installs (and what is reproduced here)

1. archinstall partitions
2. pacstrap from the [offline] repo — file:///var/cache/omarchy/mirror/offline
   (1247 packages inside the ISO, no network)
3. copies the live /etc/pacman.conf into the target
4. arch-chroot → omarchy-apply-system  (install/config, hardware, login, post-install)
5. arch-chroot → omarchy-provision-user (install/user)
6. post-install/pacman.sh rewrites /etc/pacman.conf with the Omarchy mirrors
7. Limine as the bootloader

Steps 1, 2 (partitioning) and 7 (bootloader) are left to whoever installs. The offline repo is rebuilt from the CachyOS repos instead of the ISO, and step 6 is neutralised.

Starting on another machine

git clone https://git.snix.me/omarchy/cachyos.git
cd cachyos

Then work through the procedure below. Nothing is tied to a particular machine: the package list comes from an Omarchy release, and the drivers a machine needs are installed by Omarchy's own hardware scripts, from inside the target, in step 4f.

Four commands, in order:

./bin/list-packages -w                       # write packages.txt
sudo ./bin/build-repo                        # download it into /repo
sudo ./bin/bootstrap                         # pacstrap the target
sudo ./bin/install-omarchy-cachyos -u <name> # configure it

Each leaves a file the next one reads, and both are yours to edit in between: packages.txt says what to download, bootstrap-packages.txt what to install. Commenting a line out of the second keeps the package in the repo, one pacman -S away, without putting it on the target.

The kernel is a choice, not something to detect, so it is a variable:

sudo ./bin/build-repo                        # linux-cachyos, ZFS root
sudo ./bin/build-repo -k                     # which kernels there are
sudo ./bin/build-repo -k linux-cachyos-lts   # a different one
sudo ./bin/build-repo --zfs                  # ZFS root
sudo ./bin/build-repo --no-zfs               # not a ZFS root

Whether the kernel's -zfs module, zfs-utils and cachyos-zfs come along is asked rather than detected: the target disk is usually not formatted yet when the repo is built, and this machine's filesystem says nothing about it. --zfs and --no-zfs answer in advance; with neither, and no terminal to ask on, they are included -- ~350 MB wasted if unneeded, against a target that will not boot if they are missing.

--repo <dir> builds somewhere other than /repo; bootstrap --repo and install-omarchy-cachyos --repo read it back. bootstrap works out which kernel to install by looking at the repo -- a kernel is the package with a -headers of its own -- so the choice made here does not have to be repeated.

Procedure

1. Prerequisites

  • A working CachyOS system (it supplies the repos and mirrors)
  • The Omarchy 4.0.0 ISO mounted, to extract the packages only Omarchy has
  • A partition or dataset for the new system

Running from something other than Arch

The scripts need pacman, repo-add, pacstrap and arch-chroot. The last three are bash and are checked in under vendor/; for pacman, build the pacman-static PKGBUILD and drop the binary in vendor/bin/:

cp pacman-static-*/usr/bin/pacman-static vendor/bin/

build-repo and install-omarchy-cachyos pick it up on their own when the host has no pacman of its own -- PACMAN_STATIC=/path/to/binary overrides the search. On an Arch host none of this applies.

Still needed from the host: bsdtar (libarchive) and chroot, mount, unshare (util-linux). On Alpine: apk add libarchive-tools util-linux.

Compile it for a CPU that will actually run it -- a -march with AVX-512 dies with SIGILL on anything that lacks it, which includes every Zen 2/3 and Intel's hybrid-core parts.

2. The Omarchy-only packages

The 19 packages only [omarchy] carries are declared in the generated config like every other repo, with SigLevel = Optional TrustAll, so pacman -Sw fetches them in the same transaction as the rest. No ISO, and no keyring for the isolated dbpath to be missing.

3. Refreshing the list for a newer Omarchy

list-packages -w rewrites packages.txt from a release, reading the repo databases over HTTP to group each name under the repo that serves it:

./bin/list-packages -w            # the latest stable release
./bin/list-packages -w -t v4.0.1  # a particular one

The two things you decided are written onto the lines that carry them, so regenerating keeps them: a package commented out by hand stays commented, and a -git/-bin package answered for once is not asked about again. An existing packages.txt is never rewritten without asking first; --regenerate starts from scratch instead.

4. Build the local repo

sudo ./bin/build-repo

Downloads everything left active in packages.txt with every dependency -- around 850 files, ~2.3 GB -- then splits them into one local repo per source repo, keeping the original names:

/repo/
  cachyos-v3/   cachyos-core-v3/   cachyos-extra-v3/   cachyos/
  core/         extra/             multilib/           omarchy/

The split is what makes package origins survive the install. With a single merged repo every package would be recorded as coming from that one name; here the target's database says cachyos-extra-v3 or omarchy, and that stays accurate once the real repos are declared. The 19 packages only [omarchy] has go into /repo/omarchy, named to match the upstream repo added later.

It uses an isolated --dbpath rather than a plain pacman -Sw, because -Sw resolves the transaction against the installed system: an existing linux-cachyos-zfs blocks downloading a newer kernel, and poppler can resolve to a different repo than poppler-glib, breaking their exact dependency.

On the way out it copies packages.txt to bootstrap-packages.txt, asking first if one is already there. That copy is what the pacstrap installs, so the repo can hold more than the target takes.

5. Partition, mount, pacstrap

# mount the destination on /mnt (your choice: btrfs, ext4, ZFS...)

sudo ./bin/bootstrap
sudo genfstab -U /mnt >> /mnt/etc/fstab

bootstrap reads the kernel out of the repo, adds the ZFS module if build-repo was asked for one, prints every package it is about to install grouped by repo, and waits for a yes. --dry-run prints the pacstrap command instead of running it.

conf/pacman-install.conf declares all eight local repos with SigLevel = Never, in the same order as conf/pacman-target.conf. pacstrap resolves across every one of them in a single transaction, so the Omarchy packages install alongside the rest instead of needing a separate pacman -U pass.

The config it actually uses is generated, with HookDir pointed at conf/hooks-off/: the kernel, linux-firmware and every dkms package trigger 90-mkinitcpio-install, and a pacstrap would otherwise rebuild the initramfs and the UKI a dozen times, each one thrown away by the next. Nothing builds one at any point -- see step 7.

6. Run the setup

sudo ./bin/install-omarchy-cachyos -u <name>

pacman.conf into the target, a check that the Omarchy packages arrived, patching the scripts the omarchy package installed, user creation, then omarchy-apply-system and omarchy-provision-user.

Before those two it syncs the target's databases and puts a wrapper in front of omarchy-pkg-add (step 4f). Omarchy's install/hardware/*.sh run inside the target, detect the machine and ask for the drivers it needs; without synced databases every one of those calls dies with target not found. The repos are already in the right order by then, CachyOS ahead of Arch, so a driver resolves to the CachyOS build on its own. The wrapper names the repo each package comes from, logs them to /var/log/omarchy-hardware-packages, honours a skip list at /usr/local/share/omarchy-pkg-skip, and drops to a shell in the target when pacman fails -- the install carries on when that shell exits.

The patches apply straight to the target, so no Omarchy checkout is needed -- this repo is enough.

7. Bootloader, initramfs, locale

Left to whoever installs, and the initramfs is among them: no step here builds one, since every build before the fstab and the bootloader exist would be wrong.

sudo arch-chroot /mnt mkinitcpio -P

limine ends up installed (it's a hard dependency of the omarchy package) but is not configured.

8. First boot

sudo pacman -S brave-bin
xdg-settings set default-web-browser brave-browser.desktop

Without a default browser among the ones omarchy-launch-webapp knows how to drive (brave, chrome, edge, opera, vivaldi, helium), Omarchy's web apps won't launch.

The patches

patches/omarchy-cachyos.patch touches 5 files. Every change answers a real failure seen during the install.

File Change Why
install/post-install/pacman.sh disables the pacman.conf cp -f it would rewrite the target config with the Omarchy mirrors, mixing the two timelines
install/config/all.sh disables snapper.sh snapper isn't in the package set and it targets btrfs; the snapper create-config line has no || true, so set -e halts the install
install/config/all.sh disables firewall.sh ufw dropped from the set; the script calls it unguarded
install/config/enable-services.sh disables docker.socket docker dropped from the set
install/user/all.sh disables chromium.sh chromium dropped; it would create empty NativeMessagingHosts dirs for eight browsers
bin/omarchy-provision-user picks the first browser present xdg-settings set default-web-browser chromium.desktop exits non-zero without chromium, and set -e aborts finalisation before omarchy-done mark

They are applied to the target in step 4c, split by where the package puts the files: patches/install.patch under /usr/share/omarchy, patches/bin.patch under /usr. Re-running the install is safe -- an already-applied patch is recognised and skipped.

A patch that no longer applies stops the install. That means upstream has touched the same lines, and the patch needs rewriting for the new Omarchy version; carrying on would leave the target half-patched.

Boot hooks

install-omarchy-cachyos temporarily masks 60-mkinitcpio-remove.hook and 90-mkinitcpio-install.hook during steps 4-6, the way the ISO orchestrator does: the hardware scripts install firmware and modules, and each one would rebuild the initramfs for every kernel — builds that are always thrown away. An EXIT trap restores them even on failure.

conf/pacman-target.conf instead removes limine's four hooks permanently, via NoExtract:

NoExtract = usr/share/libalpm/hooks/*limine*

With limine not used as the bootloader, those hooks would fail on every kernel upgrade with FAT32 boot partition not found. The glob leaves the two mkinitcpio hooks alone, which are needed.

Listing what gets installed

bin/list-packages answers the question at three levels of detail:

./bin/list-packages             # 148, annotated
./bin/list-packages -l          # 148, names only
./bin/list-packages -t          # which releases there are
./bin/list-packages -t v4.0.0   # a specific one
./bin/list-packages -d          # plus every dependency
./bin/list-packages -r          # each name prefixed with its repo
./bin/list-packages -s          # how many per repo, and how much they weigh
./bin/list-packages -dlr        # short options bundle
cachyos-extra-v3        81    1.5 GiB
extra                   34    778 MiB
omarchy                 19    326 MiB
...
total                  148    2.9 GiB

The default output is a package a line with its size and description, which is what makes it worth reading before writing anything:

aether                             #  18.9 MiB  Desktop theming application...
alsa-utils                         #   2.6 MiB  Advanced Linux Sound Archite...
asdcontrol                         #    30 KiB  Control brightness on Apple ...

It reads omarchy-base.packages from a release tag on GitHub, the newest unless -t names one. -b adds the ten the ISO installs before it reaches that list -- seven are in it already, and of the rest base is on the pacstrap command line, efibootmgr arrives as a dependency of limine, and base-devel only exists to build AUR packages.

This is what Omarchy selects, not necessarily what gets installed: the lines you commented out of packages.txt are the difference, and diff shows it:

diff <(./bin/list-packages -l | sort) \
     <(sed -e 's/#.*//' -e 's/[[:space:]]*$//' packages.txt | grep . | sort)

Sizes and descriptions come from the repo databases, which takes a few seconds; -l skips them. -d resolves dependencies and needs pacman; everything else only needs curl.

The package set

packages.txt starts as the 147 names in omarchy-base.packages plus the ten the ISO installs ahead of it. What this repo dropped from that, ~1.25 GB worth, is a decision per machine rather than part of the repo: chromium, libreoffice-fresh, docker and friends, ufw, obs-studio, obsidian, mpv, localsend, dotnet-runtime.

Added on top are packages Omarchy's lists don't declare because the official install gets them elsewhere:

  • sudo — archinstall installs it as part of the base system, but omarchy-settings drops three files into /etc/sudoers.d/ and several install/ scripts invoke it
  • vulkan-intel, intel-media-driver, libvpl, vpl-gpu-rt, intel-lpmd, thermald, sof-firmware — installed conditionally by the hardware scripts via omarchy-pkg-add, which fails with target not found in a chroot with no synced databases. Without sof-firmware, Intel audio exposes only a Dummy Output.
  • webkit2gtk-4.1, gtk4-layer-shell — dependencies of aether, tensaku and hyprland-preview-share-picker
  • perl, pipewire, cachyos-keyring, linux-cachyos-zfs, zfs-utils, cachyos-zfs

The Omarchy packages come from the [omarchy] local repo during the pacstrap, so pacman resolves their dependencies itself.

The 19 Omarchy packages

They come from the [omarchy] local repo built out of the ISO, so pacman records their origin properly. Until the upstream repo is declared there is nothing to upgrade them from, so they stay at the ISO's version. The upstream [omarchy] repo 404s on the stable channel while 4.0.0 is in alpha; conf/pacman-target.conf documents how to add it (last, never ahead of the CachyOS repos).

Notes

  • snapper ends up installed (hard dependency of omarchy) but snapper.sh stays disabled: on a ZFS root, snapshots are zfs snapshot. On btrfs the line in install/config/all.sh can be re-enabled.
  • The Omarchy repo doesn't list sudo, pipewire or perl among its packages: they look resolvable when you query pacman -Si from a system that already has them. The same applies to the 19 Omarchy packages' dependencies.

Tracking where packages came from

pacman does record it, in the local database's %INSTALLED_DB% field -- pacman -Qi prints it as "Installed From". But it only fills that field for packages it actually downloads, so a pacstrap over a populated target leaves it empty for everything it skips. On this install that was 686 packages out of 845, including most dependencies.

Two pieces close the gap, one per phase.

After the pacstrap, pactrack-from-repo reads the answer off the local repos: /repo/<name>/ holds exactly the files that went in, and the directory name is the repo name. Matching <pkgname>-<version> against those files is exact -- no lookup, nothing to drift. install-omarchy-cachyos runs it in step 4e:

sudo pactrack-from-repo --write-db /mnt

It writes /var/lib/pactrack/origins (name<TAB>repo), and with --write-db also fills in the empty %INSTALLED_DB% fields, so pacman -Qi answers for every package. It never overwrites a value pacman wrote: that one records what happened, while this one is inferred from which repo holds the file today.

From the first boot, the hook takes over. There %INSTALLED_DB% is reliable and pacman fills it in for dependencies too, so the hook simply copies it into the file after every transaction:

hooks/95-pactrack.hook  ->  /etc/pacman.d/hooks/
hooks/pactrack-hook           ->  /usr/local/bin/

Keeping the file alongside pacman's own field is the point: if the field is ever lost, --write-db puts it back.

To read it:

pactrack              # every package, grouped by repo
pactrack omarchy      # only what came from the omarchy repo
pactrack -c           # per-repo counts
pactrack -u           # packages the file has never seen

pactrack omarchy is the useful one here: it lists the 19 packages that do not come from CachyOS.

A package installed with pacman -U from a file has no source repo and is recorded as local -- accurate, since it came from none. The 19 Omarchy packages avoid that by coming from the [omarchy] local repo during the pacstrap.

Adding the upstream [omarchy] repo

The repo carries 202 packages, 25 of which also exist in CachyOS/Arch. On every one of those overlaps CachyOS is at an equal or newer version, so declaring it last is enough to keep CachyOS in charge:

sudo tee -a /etc/pacman.conf > /dev/null <<'EOF'

# Last, and without Upgrade: the repo is a source to pull from explicitly,
# never one that decides during a -Syu.
[omarchy]
SigLevel = Optional TrustAll
Usage = Sync Search Install
Server = https://pkgs.omarchy.org/stable/$arch
EOF

Usage without Upgrade keeps it out of -Syu resolution entirely: no CachyOS package can be replaced by an Omarchy build. The Omarchy packages are then upgraded by naming them, which is what pactrack is there to answer:

sudo pacman -S $(pactrack omarchy)