Keep an Omarchy workstation reproducible¶
Omarchy sets up Arch Linux as a complete desktop.
What you add on top (packages, dotfiles, a timer or two, something from the
AUR) is usually done by hand and remembered by nobody. This guide keeps
that layer in a recipe: a fresh Omarchy install plus sadame apply gives
the same machine, and sadame drift answers "did I break my laptop?" in
one command. It uses examples/12-omarchy-workstation.
The recipe¶
# A personal Arch or Omarchy workstation, applied locally:
#
# sudo sadame apply examples/12-omarchy-workstation
#
# Packages from the official repositories, dotfiles from files/ into your
# home directory, a systemd user timer, and an AUR package through yay.
# Run `sudo sadame drift ...` now and then: exit 0 means the laptop still
# matches the recipe.
raise "this example is for Arch Linux and Omarchy, not #{platform.description}" unless platform.family == "arch"
me = vars[:user]
raise "run it with sudo, or pass --var user=NAME" if me.nil? || me.empty? || me == "root"
home = "/home/#{me}"
# Packages: only what you want on top of Arch or Omarchy.
vars[:packages].each { |name| package name }
# Dotfiles. Directories are listed so that they belong to you, not root.
["#{home}/.config", "#{home}/.config/systemd", "#{home}/.config/systemd/user", "#{home}/.local",
"#{home}/.local/bin"].each do |path|
directory path, owner: me, mode: "0755"
end
file "#{home}/.gitconfig", template: "gitconfig.erb", owner: me, mode: "0644"
file "#{home}/.config/starship.toml", source: "starship.toml", owner: me, mode: "0644"
file "#{home}/.local/bin/update-system", source: "update-system", owner: me, mode: "0755"
# A user timer that empties ~/Downloads of files older than 30 days. Your
# systemd instance must run even when you are logged out, so lingering is
# turned on and the instance started.
exec "enable lingering for #{me}",
command: "loginctl enable-linger #{me}",
creates: "/var/lib/systemd/linger/#{me}"
exec "start #{me}'s systemd instance",
command: "systemctl start user@$(id -u #{me}).service",
not_if: "systemctl is-active --quiet user@$(id -u #{me}).service"
exec "reload #{me}'s systemd units",
command: "systemctl --user -M #{me}@ daemon-reload",
action: :nothing
file "#{home}/.config/systemd/user/clean-downloads.service",
source: "clean-downloads.service",
owner: me,
mode: "0644",
notifies: [:run, "exec[reload #{me}'s systemd units]", :immediately]
file "#{home}/.config/systemd/user/clean-downloads.timer",
source: "clean-downloads.timer",
owner: me,
mode: "0644",
notifies: [:run, "exec[reload #{me}'s systemd units]", :immediately]
service "clean-downloads.timer", user: me, enable: true, state: :running
# An AUR package. yay builds as you and asks sudo for pacman, so this needs
# passwordless sudo for pacman or a recent sudo timestamp. Skipped where
# yay is not installed, such as a plain Arch container.
vars[:aur_packages].each do |name|
exec "install #{name} from the AUR",
command: "runuser -u #{me} -- yay -S --needed --noconfirm #{name}",
only_if: "command -v yay",
not_if: "pacman -Q #{name}"
end
# Run with sudo: SUDO_USER is the user whose home directory is managed.
{
user: ENV["SUDO_USER"],
git_name: "Your Name",
git_email: "you@example.com",
packages: ["git", "htop", "ripgrep", "fd", "bat", "starship"],
aur_packages: ["visual-studio-code-bin"]
}
Copy the directory somewhere you keep under version control, edit
vars.rb and the files in files/ and templates/, and add your own.
What belongs in the recipe, and what does not¶
| In the recipe | Left to Omarchy |
|---|---|
| packages you install on top of Omarchy | the packages Omarchy installs |
| your dotfiles: git identity, shell prompt, editor settings | Omarchy's own configuration (Hyprland, Waybar, its themes) |
your scripts in ~/.local/bin |
Omarchy's scripts and its update mechanism |
| your systemd user units and timers | Omarchy's services |
| AUR packages you rely on | yay itself, which Omarchy ships |
The rule of thumb: manage what you would have to redo by hand after a reinstall, and leave the files Omarchy installs and updates alone. If the recipe managed them too, every Omarchy update would show up as drift, and every apply would undo the update. When you do change one of Omarchy's settings, make the change the way Omarchy's documentation describes for local changes, so its updates and your recipe do not overwrite each other, and manage only your part.
Running it¶
On a fresh install, the plan shows everything the recipe will add. Run it
with sudo: packages need root, and SUDO_USER tells the recipe whose
home directory to manage. The output below comes from an Arch Linux ARM
container on an ARM64 machine, where yay is missing, so the AUR step is
skipped; on Omarchy it runs.
$ sudo sadame plan examples/12-omarchy-workstation
sadame 0.5.1 plan on local (Arch Linux ARM)
+ package[git]
state: absent -> installed
+ package[htop]
state: absent -> installed
+ package[ripgrep]
state: absent -> installed
+ package[fd]
state: absent -> installed
+ package[bat]
state: absent -> installed
+ package[starship]
state: absent -> installed
+ directory[/home/alice/.config]
state: absent -> present
mode: 0755
owner: alice
+ directory[/home/alice/.config/systemd]
state: absent -> present
mode: 0755
owner: alice
+ directory[/home/alice/.config/systemd/user]
state: absent -> present
mode: 0755
owner: alice
+ directory[/home/alice/.local]
state: absent -> present
mode: 0755
owner: alice
+ directory[/home/alice/.local/bin]
state: absent -> present
mode: 0755
owner: alice
+ file[/home/alice/.gitconfig]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.gitconfig (desired)
@@ -0,0 +1,8 @@
+# Managed by sadame. Local changes will be overwritten.
+[user]
+ name = Your Name
+ email = you@example.com
+[init]
+ defaultBranch = main
+[pull]
+ rebase = true
+ file[/home/alice/.config/starship.toml]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.config/starship.toml (desired)
@@ -0,0 +1,5 @@
+# Managed by sadame.
+add_newline = false
+
+[directory]
+truncation_length = 3
+ file[/home/alice/.local/bin/update-system]
state: absent -> present
mode: 0755
owner: alice
--- /dev/null
+++ /home/alice/.local/bin/update-system (desired)
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Updates the system packages, then the AUR packages if yay is installed.
+set -e
+sudo pacman -Syu
+if command -v yay >/dev/null 2>&1; then
+ yay -Sua
+fi
! exec[enable lingering for alice]
command: loginctl enable-linger alice
(/var/lib/systemd/linger/alice does not exist)
! exec[start alice's systemd instance]
command: systemctl start user@$(id -u alice).service
(not_if exited non-zero)
+ file[/home/alice/.config/systemd/user/clean-downloads.service]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.config/systemd/user/clean-downloads.service (desired)
@@ -0,0 +1,6 @@
+[Unit]
+Description=Remove files older than 30 days from ~/Downloads
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/find %h/Downloads -type f -mtime +30 -delete
notifies: run exec[reload alice's systemd units] (immediately)
+ file[/home/alice/.config/systemd/user/clean-downloads.timer]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.config/systemd/user/clean-downloads.timer (desired)
@@ -0,0 +1,9 @@
+[Unit]
+Description=Clean ~/Downloads weekly
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+
+[Install]
+WantedBy=timers.target
notifies: run exec[reload alice's systemd units] (immediately)
~ service[clean-downloads.timer]
enabled: false -> true
state: stopped -> running
Plan: 16 to create, 1 to update, 0 to remove, 2 to run, 2 unchanged.
$ sudo sadame apply examples/12-omarchy-workstation
sadame 0.5.1 apply on local (Arch Linux ARM)
changed package[git]
state: absent -> installed
changed package[htop]
state: absent -> installed
changed package[ripgrep]
state: absent -> installed
changed package[fd]
state: absent -> installed
changed package[bat]
state: absent -> installed
changed package[starship]
state: absent -> installed
changed directory[/home/alice/.config]
state: absent -> present
mode: 0755
owner: alice
changed directory[/home/alice/.config/systemd]
state: absent -> present
mode: 0755
owner: alice
changed directory[/home/alice/.config/systemd/user]
state: absent -> present
mode: 0755
owner: alice
changed directory[/home/alice/.local]
state: absent -> present
mode: 0755
owner: alice
changed directory[/home/alice/.local/bin]
state: absent -> present
mode: 0755
owner: alice
changed file[/home/alice/.gitconfig]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.gitconfig (desired)
@@ -0,0 +1,8 @@
+# Managed by sadame. Local changes will be overwritten.
+[user]
+ name = Your Name
+ email = you@example.com
+[init]
+ defaultBranch = main
+[pull]
+ rebase = true
changed file[/home/alice/.config/starship.toml]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.config/starship.toml (desired)
@@ -0,0 +1,5 @@
+# Managed by sadame.
+add_newline = false
+
+[directory]
+truncation_length = 3
changed file[/home/alice/.local/bin/update-system]
state: absent -> present
mode: 0755
owner: alice
--- /dev/null
+++ /home/alice/.local/bin/update-system (desired)
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Updates the system packages, then the AUR packages if yay is installed.
+set -e
+sudo pacman -Syu
+if command -v yay >/dev/null 2>&1; then
+ yay -Sua
+fi
changed exec[enable lingering for alice]
command: loginctl enable-linger alice
(/var/lib/systemd/linger/alice does not exist)
changed exec[start alice's systemd instance]
command: systemctl start user@$(id -u alice).service
(not_if exited non-zero)
unchanged exec[reload alice's systemd units]
changed file[/home/alice/.config/systemd/user/clean-downloads.service]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.config/systemd/user/clean-downloads.service (desired)
@@ -0,0 +1,6 @@
+[Unit]
+Description=Remove files older than 30 days from ~/Downloads
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/find %h/Downloads -type f -mtime +30 -delete
notified exec[reload alice's systemd units]: run (immediately, from file[/home/alice/.config/systemd/user/clean-downloads.service])
changed file[/home/alice/.config/systemd/user/clean-downloads.timer]
state: absent -> present
mode: 0644
owner: alice
--- /dev/null
+++ /home/alice/.config/systemd/user/clean-downloads.timer (desired)
@@ -0,0 +1,9 @@
+[Unit]
+Description=Clean ~/Downloads weekly
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+
+[Install]
+WantedBy=timers.target
notified exec[reload alice's systemd units]: run (immediately, from file[/home/alice/.config/systemd/user/clean-downloads.timer])
changed service[clean-downloads.timer]
enabled: false -> true
state: stopped -> running
unchanged exec[install visual-studio-code-bin from the AUR]
Summary
changed 19 package[git]
package[htop]
package[ripgrep]
package[fd]
package[bat]
package[starship]
directory[/home/alice/.config]
directory[/home/alice/.config/systemd]
directory[/home/alice/.config/systemd/user]
directory[/home/alice/.local]
directory[/home/alice/.local/bin]
file[/home/alice/.gitconfig]
file[/home/alice/.config/starship.toml]
file[/home/alice/.local/bin/update-system]
exec[enable lingering for alice]
exec[start alice's systemd instance]
file[/home/alice/.config/systemd/user/clean-downloads.service]
file[/home/alice/.config/systemd/user/clean-downloads.timer]
service[clean-downloads.timer]
unchanged 2
failed 0
skipped 0
notified 2 exec[reload alice's systemd units]: run
exec[reload alice's systemd units]: run
Finished in 37.34s.
The two notified lines are systemctl --user daemon-reload running
right after each unit file was written (:immediately), before the timer
is enabled.
Did I break my laptop?¶
$ sudo sadame drift examples/12-omarchy-workstation
sadame 0.5.1 drift on local (Arch Linux ARM)
No drift: 21 resources match the recipe.
$ echo $?
0
Now edit ~/.gitconfig by hand and delete a script, as one does:
$ sed -i 's/rebase = true/rebase = false/' ~/.gitconfig
$ rm ~/.local/bin/update-system
$ sudo sadame drift examples/12-omarchy-workstation
sadame 0.5.1 drift on local (Arch Linux ARM)
~ file[/home/alice/.gitconfig]
--- /home/alice/.gitconfig
+++ /home/alice/.gitconfig (desired)
@@ -5,4 +5,4 @@
[init]
defaultBranch = main
[pull]
- rebase = false
+ rebase = true
+ file[/home/alice/.local/bin/update-system]
state: absent -> present
mode: 0755
owner: alice
--- /dev/null
+++ /home/alice/.local/bin/update-system (desired)
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Updates the system packages, then the AUR packages if yay is installed.
+set -e
+sudo pacman -Syu
+if command -v yay >/dev/null 2>&1; then
+ yay -Sua
+fi
Drift: 2 of 21 resources differ from the recipe.
$ echo $?
1
apply puts both back:
$ sudo sadame apply examples/12-omarchy-workstation
sadame 0.5.1 apply on local (Arch Linux ARM)
unchanged package[git]
unchanged package[htop]
unchanged package[ripgrep]
unchanged package[fd]
unchanged package[bat]
unchanged package[starship]
unchanged directory[/home/alice/.config]
unchanged directory[/home/alice/.config/systemd]
unchanged directory[/home/alice/.config/systemd/user]
unchanged directory[/home/alice/.local]
unchanged directory[/home/alice/.local/bin]
changed file[/home/alice/.gitconfig]
--- /home/alice/.gitconfig
+++ /home/alice/.gitconfig (desired)
@@ -5,4 +5,4 @@
[init]
defaultBranch = main
[pull]
- rebase = false
+ rebase = true
unchanged file[/home/alice/.config/starship.toml]
changed file[/home/alice/.local/bin/update-system]
state: absent -> present
mode: 0755
owner: alice
--- /dev/null
+++ /home/alice/.local/bin/update-system (desired)
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Updates the system packages, then the AUR packages if yay is installed.
+set -e
+sudo pacman -Syu
+if command -v yay >/dev/null 2>&1; then
+ yay -Sua
+fi
unchanged exec[enable lingering for alice]
unchanged exec[start alice's systemd instance]
unchanged exec[reload alice's systemd units]
unchanged file[/home/alice/.config/systemd/user/clean-downloads.service]
unchanged file[/home/alice/.config/systemd/user/clean-downloads.timer]
unchanged service[clean-downloads.timer]
unchanged exec[install visual-studio-code-bin from the AUR]
Summary
changed 2 file[/home/alice/.gitconfig]
file[/home/alice/.local/bin/update-system]
unchanged 19
failed 0
skipped 0
notified 0
Finished in 0.11s.
Running sudo sadame drift ... from time to time, or from a systemd user
timer, tells you when the machine has wandered from what you wrote down.
From a fresh install to the same machine¶
- Install Omarchy.
- Clone the repository with your recipe.
- Install sadame (see Install).
sudo sadame apply path/to/your-recipesudo sadame drift path/to/your-recipe; exit status0means done.
Common mistakes¶
- Running without sudo, or as root directly. Without
sudo, installing packages fails; as root withoutsudo,SUDO_USERis empty and the recipe stops. Pass--var user=NAMEif you must. - The AUR step asking for a password.
yayrunssudo pacman, and sadame cannot answer a prompt. Runsudo -vjust before, or allowpacmanwithout a password. - Managing Omarchy's own files. See the table above.
- Upgrading packages with the recipe.
packagemeans "installed", not "latest"; keep updating withpacman -Syu(orupdate-systemfrom the example).
See also¶
12-omarchy-workstation- service for
user:units. - package for AUR packages.