#!/bin/bash # omarchy:summary=Upgrade a legacy Omarchy install to the package-backed Omarchy 4 layout. # omarchy:args=[--yes] [--reboot] [--channel stable|rc|edge] [--user USER] # omarchy:examples=omarchy upgrade to 4 # omarchy:requires-sudo=true set -euo pipefail usage() { cat <<'USAGE' Usage: omarchy-upgrade-to-4 [--yes] [--reboot] [--channel stable|rc|edge] [--user USER] Upgrade any pre-4 Omarchy install to the package-backed Omarchy 4 layout. This script is intentionally self-contained so it can be shipped as a command in Omarchy 3.8.x or run directly with curl | bash on older systems. Options: -y, --yes Do not prompt before upgrading --reboot Reboot automatically after a successful upgrade --channel stable|rc|edge Override detected Omarchy package channel --user USER User account to refresh after package install -h, --help Show this help USAGE } log() { printf '\033[32m==>\033[0m %s\n' "$*" } warn() { printf '\033[33mWarning:\033[0m %s\n' "$*" >&2 } fail() { printf '\033[31mError:\033[0m %s\n' "$*" >&2 exit 1 } normalize_channel() { case "${1:-}" in stable|master|main) echo stable ;; rc) echo rc ;; edge|dev) echo edge ;; *) return 1 ;; esac } valid_channel() { normalize_channel "$1" >/dev/null 2>&1 } channel_override="${OMARCHY_UPGRADE_CHANNEL:-${OMARCHY_CHANNEL:-${OMARCHY_MIRROR:-}}}" target_user="${OMARCHY_INSTALL_USER:-}" yes=0 auto_reboot=0 while (($#)); do case "$1" in -y|--yes) yes=1 shift ;; --reboot) auto_reboot=1 shift ;; --channel) channel_override="${2:-}" shift 2 ;; --user) target_user="${2:-}" shift 2 ;; -h|--help) usage exit 0 ;; *) fail "Unknown option: $1" ;; esac done if ! command -v pacman >/dev/null 2>&1; then fail "This upgrade is only supported on Arch/Omarchy systems with pacman." fi if ! command -v sudo >/dev/null 2>&1 && (( EUID != 0 )); then fail "sudo is required when not running as root." fi if [[ -z $target_user ]]; then if (( EUID == 0 )); then target_user="${SUDO_USER:-}" else target_user="${USER:-$(id -un)}" fi fi if [[ -z $target_user || $target_user == root ]]; then fail "Could not determine the non-root Omarchy user. Re-run with --user USER." fi if ! getent passwd "$target_user" >/dev/null; then fail "User '$target_user' does not exist." fi target_home=$(getent passwd "$target_user" | cut -d: -f6) [[ -n $target_home && -d $target_home ]] || fail "Home directory for '$target_user' was not found." target_uid=$(id -u "$target_user") target_runtime_dir="/run/user/$target_uid" as_root() { if (( EUID == 0 )); then "$@" else sudo "$@" fi } run_as_user() { if (( EUID == 0 )); then if command -v sudo >/dev/null 2>&1; then sudo -u "$target_user" -H "$@" else runuser -u "$target_user" -- "$@" fi else "$@" fi } run_as_user_session() { run_as_user env \ HOME="$target_home" \ USER="$target_user" \ LOGNAME="$target_user" \ XDG_RUNTIME_DIR="$target_runtime_dir" \ DBUS_SESSION_BUS_ADDRESS="unix:path=$target_runtime_dir/bus" \ XDG_CURRENT_DESKTOP=Hyprland \ XDG_SESSION_DESKTOP=Hyprland \ DESKTOP_SESSION=hyprland \ "$@" } target_hyprland_pid() { pgrep -u "$target_uid" -x Hyprland 2>/dev/null | head -n1 || true } target_process_env() { local name="$1" pid value pid=$(target_hyprland_pid) [[ -n $pid && -r /proc/$pid/environ ]] || return 1 value=$(tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null | awk -v name="$name" ' BEGIN { prefix = name "=" } index($0, prefix) == 1 { print substr($0, length(prefix) + 1); exit } ' || true) [[ -n $value ]] || return 1 printf '%s\n' "$value" } valid_wayland_display() { [[ -n ${1:-} && -S $target_runtime_dir/$1 ]] } discover_wayland_display() { local candidate candidate="${WAYLAND_DISPLAY:-}" if valid_wayland_display "$candidate"; then printf '%s\n' "$candidate" return 0 fi candidate=$(target_process_env WAYLAND_DISPLAY || true) if valid_wayland_display "$candidate"; then printf '%s\n' "$candidate" return 0 fi local candidates=() mapfile -t candidates < <(find "$target_runtime_dir" -maxdepth 1 -type s -name 'wayland-*' -printf '%f\n' 2>/dev/null || true) (( ${#candidates[@]} == 1 )) || return 1 printf '%s\n' "${candidates[0]}" } valid_hyprland_signature() { [[ -n ${1:-} ]] || return 1 [[ -S $target_runtime_dir/hypr/$1/.socket.sock || -S $target_runtime_dir/hypr/$1/.socket2.sock ]] } discover_hyprland_signature() { local candidate candidate="${HYPRLAND_INSTANCE_SIGNATURE:-}" if valid_hyprland_signature "$candidate"; then printf '%s\n' "$candidate" return 0 fi candidate=$(target_process_env HYPRLAND_INSTANCE_SIGNATURE || true) if valid_hyprland_signature "$candidate"; then printf '%s\n' "$candidate" return 0 fi local candidates=() mapfile -t candidates < <(find "$target_runtime_dir/hypr" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null || true) (( ${#candidates[@]} == 1 )) || return 1 printf '%s\n' "${candidates[0]}" } detect_channel() { local candidate="${channel_override:-}" if [[ -n $candidate ]] && valid_channel "$candidate"; then normalize_channel "$candidate" return 0 fi if command -v omarchy-version-channel >/dev/null 2>&1; then candidate=$(omarchy-version-channel 2>/dev/null | awk '{ print $1 }' || true) if [[ -n $candidate ]] && valid_channel "$candidate"; then normalize_channel "$candidate" return 0 fi fi if [[ -f /etc/pacman.conf ]]; then if grep -q 'https://pkgs\.omarchy\.org/rc/' /etc/pacman.conf; then echo rc return 0 elif grep -q 'https://pkgs\.omarchy\.org/edge/' /etc/pacman.conf; then echo edge return 0 elif grep -q 'https://pkgs\.omarchy\.org/stable/' /etc/pacman.conf; then echo stable return 0 fi fi if [[ -f /etc/pacman.d/mirrorlist ]]; then if grep -q 'https://rc-mirror\.omarchy\.org/' /etc/pacman.d/mirrorlist; then echo rc return 0 elif grep -q 'https://mirror\.omarchy\.org/' /etc/pacman.d/mirrorlist; then echo edge return 0 elif grep -q 'https://stable-mirror\.omarchy\.org/' /etc/pacman.d/mirrorlist; then echo stable return 0 fi fi if [[ -d $target_home/.local/share/omarchy/.git ]]; then if (( EUID == 0 )); then candidate=$(run_as_user git -C "$target_home/.local/share/omarchy" branch --show-current 2>/dev/null || true) else candidate=$(git -C "$target_home/.local/share/omarchy" branch --show-current 2>/dev/null || true) fi if [[ -n $candidate ]] && valid_channel "$candidate"; then normalize_channel "$candidate" return 0 fi fi echo stable } channel=$(detect_channel) if ! valid_channel "$channel"; then fail "Invalid channel '$channel'. Use stable, rc, or edge." fi case "$channel" in stable) mirror_server='https://stable-mirror.omarchy.org/$repo/os/$arch' package_server='https://pkgs.omarchy.org/stable/$arch' ;; rc) mirror_server='https://rc-mirror.omarchy.org/$repo/os/$arch' package_server='https://pkgs.omarchy.org/rc/$arch' ;; edge) mirror_server='https://mirror.omarchy.org/$repo/os/$arch' package_server='https://pkgs.omarchy.org/edge/$arch' ;; esac if (( ! yes )); then cat </dev/tty read -r answer /dev/null & sudo_keepalive_pid=$! trap 'kill "$sudo_keepalive_pid" 2>/dev/null || true' EXIT fi backup_suffix=$(date +%Y%m%d%H%M%S) configure_pacman_channel() { log "Configuring Omarchy pacman repositories ($channel)" as_root install -d -m 0755 /etc/pacman.d if [[ -f /etc/pacman.d/mirrorlist ]]; then as_root cp -f /etc/pacman.d/mirrorlist "/etc/pacman.d/mirrorlist.omarchy-upgrade-to-4.$backup_suffix.bak" fi printf 'Server = %s\n' "$mirror_server" | as_root tee /etc/pacman.d/mirrorlist >/dev/null if [[ ! -f /etc/pacman.conf ]]; then fail "/etc/pacman.conf does not exist." fi as_root cp -f /etc/pacman.conf "/etc/pacman.conf.omarchy-upgrade-to-4.$backup_suffix.bak" tmp=$(mktemp) awk -v server="$package_server" ' BEGIN { in_omarchy = 0; wrote = 0 } /^\[omarchy\]$/ { if (!wrote) { print "[omarchy]" print "SigLevel = Optional TrustAll" print "Server = " server wrote = 1 } in_omarchy = 1 next } /^\[/ && in_omarchy { in_omarchy = 0 } !in_omarchy { print } END { if (!wrote) { print "" print "[omarchy]" print "SigLevel = Optional TrustAll" print "Server = " server } } ' /etc/pacman.conf >"$tmp" as_root install -m 0644 "$tmp" /etc/pacman.conf rm -f "$tmp" } install_keyrings() { log "Refreshing package databases and keyrings" as_root pacman -Sy --noconfirm archlinux-keyring omarchy-keyring || { warn "Keyring package install failed; attempting to trust the Omarchy packaging key directly." as_root pacman-key --recv-keys 40DFB630FF42BCFFB047046CF0134EE680CAC571 --keyserver keys.openpgp.org || true as_root pacman-key --lsign-key 40DFB630FF42BCFFB047046CF0134EE680CAC571 || true as_root pacman -Sy --noconfirm archlinux-keyring omarchy-keyring } } remove_legacy_installer_package() { if pacman -Q omarchy-installer >/dev/null 2>&1; then log "Removing legacy omarchy-installer package" # The pre-4 omarchy meta package may depend on omarchy-installer. Ignore # that edge temporarily; the next transaction installs the Omarchy 4 meta. as_root pacman -Rdd --noconfirm omarchy-installer fi } remove_conflicting_legacy_packages() { # Remove legacy packages that are known to conflict with Omarchy 4 defaults # before the main package transaction. Broader retired-package cleanup runs # after NetworkManager and the rest of the new stack are installed. local conflicting_packages=( asdcontrol-git lazydocker-bin localsend-bin omarchy-chromium omarchy-lazyvim openai-codex-bin ttf-jetbrains-mono ttf-jetbrains-mono-nerd wayfreeze wayfreeze-git ) local pkg for pkg in "${conflicting_packages[@]}"; do if pacman -Q "$pkg" >/dev/null 2>&1; then # Some pre-4 meta packages depend on these retired/default packages. We # intentionally break those old dependency edges before installing the # Omarchy 4 meta, otherwise pacman prompts to remove conflicts (for # example ttf-jetbrains-mono-nerd -> ttf-jetbrains-mono-nerd-basic) and # --noconfirm answers "no". as_root pacman -Rdd --noconfirm "$pkg" || warn "Could not remove conflicting legacy package '$pkg'; continuing." fi done } install_omarchy_4_packages() { local core_packages=( omarchy-keyring omarchy-settings omarchy-limine omarchy-nvim omarchy ) log "Installing Omarchy 4 packages" # --noconfirm answers "no" to conflict-removal prompts. --ask 4 preselects # removing the conflicting package, which is required for legacy packages like # ttf-jetbrains-mono-nerd -> ttf-jetbrains-mono-nerd-basic. as_root pacman -Syu --needed --noconfirm --ask 4 --overwrite='*' "${core_packages[@]}" if [[ -f /usr/share/omarchy/install/omarchy-base.packages ]]; then log "Installing Omarchy 4 default package set" mapfile -t base_packages < <(sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' /usr/share/omarchy/install/omarchy-base.packages) if (( ${#base_packages[@]} )); then as_root pacman -S --needed --noconfirm --ask 4 --overwrite='*' "${base_packages[@]}" fi else warn "/usr/share/omarchy/install/omarchy-base.packages was not found; skipping default package set install." fi # Archinstall installs the audio application during fresh ISO installs, so # legacy upgrades need to ensure the same PipeWire stack is present without # depending on Archinstall or Omarchy setup helpers. local audio_packages=( pipewire pipewire-alsa pipewire-jack pipewire-pulse gst-plugin-pipewire libpulse wireplumber ) as_root pacman -S --needed --noconfirm --ask 4 --overwrite='*' "${audio_packages[@]}" } remove_retired_default_packages() { local retired_packages=( 1password-beta 1password-cli alacritty asdcontrol-git blueberry bluetui chaotic-keyring chaotic-mirrorlist claude-code elephant elephant-all elephant-bluetooth elephant-calc elephant-clipboard elephant-desktopapplications elephant-files elephant-menus elephant-providerlist elephant-runner elephant-symbols elephant-todo elephant-unicode elephant-websearch fcitx5-configtool gcc14 github-cli hypridle hyprlock impala iwd lazydocker-bin lmstudio lmstudio-bin localsend-bin makima-bin mako omarchy-chromium omarchy-lazyvim omarchy-walker openai-codex openai-codex-bin opencode pavucontrol playerctl polkit-gnome qt5-remoteobjects signal-desktop spotify swaybg swayosd ttf-jetbrains-mono ttf-jetbrains-mono-nerd vlc waybar wayfreeze wayfreeze-git wf-recorder wiremix wl-screenrec yay-bin-debug zoom ) log "Removing packages retired from the Omarchy 4 default install" local pkg local installed_retired=() for pkg in "${retired_packages[@]}"; do if pacman -Q "$pkg" >/dev/null 2>&1; then installed_retired+=("$pkg") fi done ((${#installed_retired[@]})) || return 0 # Remove as one transaction so dependency anchors like omarchy-walker and all # of its elephant-* modules disappear together instead of failing one-by-one. if as_root pacman -Rns --noconfirm --ask 4 "${installed_retired[@]}"; then return 0 fi warn "Batch retired-package removal failed; retrying packages individually." for pkg in "${installed_retired[@]}"; do if pacman -Q "$pkg" >/dev/null 2>&1; then as_root pacman -Rns --noconfirm --ask 4 "$pkg" || warn "Could not remove retired package '$pkg'; leaving it installed." fi done } cleanup_legacy_user_paths() { log "Cleaning legacy user command shims" run_as_user env TARGET_HOME="$target_home" bash <<'USER_CLEANUP' set -euo pipefail local_bin="$TARGET_HOME/.local/bin" legacy_root="$TARGET_HOME/.local/share/omarchy/bin/" [[ -d $local_bin ]] || exit 0 shopt -s nullglob for link in "$local_bin"/omarchy*; do [[ -L $link ]] || continue target=$(readlink "$link" || true) case "$target" in "$legacy_root"*|../share/omarchy/bin/*|*.local/share/omarchy/bin/*) rm -f "$link" ;; esac done USER_CLEANUP } cleanup_retired_user_unit_files() { run_as_user bash <<'USER_UNITS' set -euo pipefail units=( hyprpolkitagent.service mako.service omarchy-battery-monitor.service omarchy-battery-monitor.timer omarchy-shell.service swayosd-server.service elephant.service app-walker@autostart.service ) shopt -s nullglob for base in "$HOME/.config/systemd/user" "$HOME/.local/share/systemd/user"; do [[ -d $base ]] || continue for unit in "${units[@]}"; do rm -f "$base/$unit" for link in "$base"/*.wants/"$unit" "$base"/*.requires/"$unit"; do rm -f "$link" done done done USER_UNITS } cleanup_retired_services() { log "Disabling services retired by Omarchy 4" # Do not stop iwd mid-upgrade; disabling is enough and avoids dropping a # running Wi-Fi connection before the user can reboot into NetworkManager. as_root systemctl disable iwd.service >/dev/null 2>&1 || true cleanup_retired_user_unit_files if [[ -S $target_runtime_dir/bus ]]; then run_as_user_session systemctl --user disable \ hyprpolkitagent.service \ mako.service \ omarchy-battery-monitor.service \ omarchy-battery-monitor.timer \ omarchy-shell.service \ swayosd-server.service \ elephant.service \ app-walker@autostart.service \ >/dev/null 2>&1 || true run_as_user_session systemctl --user daemon-reload >/dev/null 2>&1 || true fi } stop_retired_session_processes() { log "Stopping retired Omarchy 3 session processes" if [[ -S $target_runtime_dir/bus ]]; then run_as_user_session systemctl --user stop \ hyprpolkitagent.service \ mako.service \ omarchy-battery-monitor.service \ omarchy-battery-monitor.timer \ omarchy-shell.service \ swayosd-server.service \ elephant.service \ app-walker@autostart.service \ >/dev/null 2>&1 || true fi # Removed packages do not stop already-running processes. Stop the old UI # only after the new Quickshell has been verified, so users are not left # without a bar/launcher if current-session shell startup fails. run_as_user pkill -x waybar >/dev/null 2>&1 || true run_as_user pkill -x walker >/dev/null 2>&1 || true run_as_user pkill -x elephant >/dev/null 2>&1 || true run_as_user pkill -x mako >/dev/null 2>&1 || true run_as_user pkill -x swayosd-server >/dev/null 2>&1 || true run_as_user_session systemctl --user daemon-reload >/dev/null 2>&1 || true } start_omarchy_shell_session() { # Omarchy 4 starts Quickshell from Hyprland autostart, not a user service. # Reboot is still the real cutover, but start the new shell in the current # Wayland session when possible so users do not sit without a bar after the # retired waybar/walker/elephant processes are stopped. command -v quickshell >/dev/null 2>&1 || { warn "quickshell is not available; Omarchy shell will start after reboot."; return 1; } [[ -d /usr/share/omarchy/shell ]] || { warn "/usr/share/omarchy/shell is missing; Omarchy shell will start after reboot."; return 1; } [[ -d $target_runtime_dir ]] || { warn "No running user session found; Omarchy shell will start after reboot."; return 1; } local wayland_display hyprland_signature wayland_display=$(discover_wayland_display || true) [[ -n $wayland_display ]] || { warn "No Wayland display found; Omarchy shell will start after reboot."; return 1; } hyprland_signature=$(discover_hyprland_signature || true) [[ -n $hyprland_signature ]] || { warn "No Hyprland session found; Omarchy shell will start after reboot."; return 1; } log "Starting Omarchy shell in the current session" run_as_user env \ HOME="$target_home" \ USER="$target_user" \ LOGNAME="$target_user" \ XDG_RUNTIME_DIR="$target_runtime_dir" \ WAYLAND_DISPLAY="$wayland_display" \ HYPRLAND_INSTANCE_SIGNATURE="$hyprland_signature" \ DBUS_SESSION_BUS_ADDRESS="unix:path=$target_runtime_dir/bus" \ XDG_CURRENT_DESKTOP=Hyprland \ XDG_SESSION_DESKTOP=Hyprland \ XDG_SESSION_TYPE=wayland \ DESKTOP_SESSION=hyprland \ OMARCHY_PATH=/usr/share/omarchy \ PATH="/usr/share/omarchy/bin:$PATH" \ bash -c ' if [[ $(omarchy-shell lock isLocked 2>/dev/null || true) == "true" ]]; then exit 2 fi pkill -x quickshell 2>/dev/null || true sleep 0.2 setsid quickshell -n -p /usr/share/omarchy/shell >/dev/null 2>&1 & ' || { warn "Could not start Omarchy shell in the current session; it will start after reboot."; return 1; } local attempt for attempt in {1..20}; do if run_as_user env \ HOME="$target_home" \ USER="$target_user" \ LOGNAME="$target_user" \ XDG_RUNTIME_DIR="$target_runtime_dir" \ DBUS_SESSION_BUS_ADDRESS="unix:path=$target_runtime_dir/bus" \ OMARCHY_PATH=/usr/share/omarchy \ PATH="/usr/share/omarchy/bin:$PATH" \ omarchy-shell shell ping >/dev/null 2>&1; then return 0 fi sleep 0.25 done warn "Omarchy shell did not respond in the current session; it will start after reboot." return 1 } enable_system_service() { local unit="$1" as_root systemctl enable "$unit" >/dev/null 2>&1 || warn "Could not enable $unit; it may not be installed on this system." } apply_firewall_defaults() { if ! command -v ufw >/dev/null 2>&1; then warn "ufw is not installed; skipping firewall defaults." return 0 fi log "Applying Omarchy firewall defaults" as_root ufw default deny incoming >/dev/null || true as_root ufw default allow outgoing >/dev/null || true as_root ufw allow 53317/udp >/dev/null || true as_root ufw allow 53317/tcp >/dev/null || true as_root ufw allow in proto udp from 172.16.0.0/12 to 172.17.0.1 port 53 comment 'allow-docker-dns' >/dev/null || true as_root ufw allow in proto udp from 192.168.0.0/16 to 172.17.0.1 port 53 comment 'allow-docker-dns' >/dev/null || true as_root sed -i 's/^ENABLED=.*/ENABLED=yes/' /etc/ufw/ufw.conf 2>/dev/null || true enable_system_service ufw.service } apply_system_transition() { log "Applying Omarchy 4 system transition" [[ -d /usr/share/omarchy ]] || fail "/usr/share/omarchy was not installed." as_root install -d -m 0755 /usr/share/icons/Yaru/scalable/actions as_root ln -snf /usr/share/icons/Adwaita/symbolic/actions/go-previous-symbolic.svg \ /usr/share/icons/Yaru/scalable/actions/go-previous-symbolic.svg as_root ln -snf /usr/share/icons/Adwaita/symbolic/actions/go-next-symbolic.svg \ /usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg as_root gtk-update-icon-cache /usr/share/icons/Yaru >/dev/null 2>&1 || true as_root install -d -m 0777 /etc/chromium/policies/managed as_root install -d -m 0755 /usr/lib/chromium printf '%s\n' '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \ as_root tee /usr/lib/chromium/initial_preferences >/dev/null if getent group docker >/dev/null; then as_root usermod -aG docker "$target_user" fi if [[ -f /usr/bin/powerprofilesctl ]]; then as_root sed -i '/env python3/ c\#!/bin/python3' /usr/bin/powerprofilesctl || true fi as_root install -d -m 0755 /etc/systemd/resolved.conf.d /etc/docker cat <<'EOF' | as_root tee /etc/systemd/resolved.conf.d/10-disable-multicast.conf >/dev/null [Resolve] LLMNR=no MulticastDNS=no EOF cat <<'EOF' | as_root tee /etc/systemd/resolved.conf.d/20-docker-dns.conf >/dev/null [Resolve] DNSStubListenerExtra=172.17.0.1 EOF cat <<'EOF' | as_root tee /etc/docker/daemon.json >/dev/null { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "5" }, "dns": ["172.17.0.1"], "bip": "172.17.0.1/16" } EOF enable_system_service cups.service enable_system_service cups-browsed.service enable_system_service avahi-daemon.service enable_system_service linux-modules-cleanup.service enable_system_service docker.socket enable_system_service systemd-resolved.service enable_system_service NetworkManager.service enable_system_service power-profiles-daemon.service enable_system_service sddm.service apply_firewall_defaults log "Normalizing SDDM login configuration" as_root install -d -m 0755 /etc/sddm.conf.d printf '[Theme]\nCurrent=omarchy\n' | as_root tee /etc/sddm.conf.d/10-theme.conf >/dev/null cat <<'EOF' | as_root tee /etc/sddm.conf.d/10-wayland.conf >/dev/null [General] DisplayServer=wayland [Wayland] CompositorCommand=start-hyprland -- --config /usr/share/sddm/hyprland.lua EOF cat <<'EOF' | as_root tee /etc/sddm.conf.d/99-omarchy-login.conf >/dev/null [Theme] Current=omarchy [Users] RememberLastUser=true RememberLastSession=true EOF if as_root test -f /etc/sddm.conf.d/autologin.conf; then local autologin_user autologin_user=$(as_root awk -F= '/^User=/ { print $2; exit }' /etc/sddm.conf.d/autologin.conf 2>/dev/null || true) [[ -n $autologin_user ]] || autologin_user="$target_user" cat </dev/null [Autologin] User=$autologin_user Session=omarchy.desktop EOF fi as_root install -d -m 0755 -o sddm -g sddm /var/lib/sddm 2>/dev/null || as_root install -d -m 0755 /var/lib/sddm cat </dev/null [Last] Session=omarchy.desktop User=$target_user EOF as_root chown sddm:sddm /var/lib/sddm /var/lib/sddm/state.conf 2>/dev/null || true as_root rm -f /etc/systemd/system/getty@tty1.service.d/autologin.conf as_root rm -f /etc/systemd/system/plymouth-quit.service.d/wait-for-graphical.conf as_root rm -f /etc/systemd/system/omarchy-seamless-login.service as_root rm -f /usr/local/bin/seamless-login as_root systemctl disable omarchy-seamless-login.service >/dev/null 2>&1 || true as_root systemctl disable makima.service >/dev/null 2>&1 || true as_root rm -rf /etc/systemd/system/makima.service.d as_root rm -f /etc/udev/rules.d/99-uinput.rules as_root systemctl disable dell-xps-haptic-touchpad.service >/dev/null 2>&1 || true as_root rm -f /etc/systemd/system/dell-xps-haptic-touchpad.service as_root rm -f /etc/systemd/system/multi-user.target.wants/dell-xps-haptic-touchpad.service as_root rm -rf /etc/systemd/system/dell-xps-haptic-touchpad.service.d as_root rm -f /etc/udev/rules.d/99-dell-xps-haptic-touchpad.rules as_root rm -f /etc/omarchy-dell-haptic-touchpad.env as_root rm -f /etc/pacman.d/hooks/walker-restart.hook as_root systemctl daemon-reload >/dev/null 2>&1 || true if [[ -f /etc/pam.d/sddm ]]; then as_root sed -i '/-auth.*pam_gnome_keyring\.so/d' /etc/pam.d/sddm as_root sed -i '/-password.*pam_gnome_keyring\.so/d' /etc/pam.d/sddm fi if [[ -f /etc/pam.d/system-auth ]]; then as_root sed -i 's|^\(auth\s\+required\s\+pam_faillock.so\)\s\+preauth.*$|\1 preauth silent deny=10 unlock_time=120|' /etc/pam.d/system-auth as_root sed -i 's|^\(auth\s\+\[default=die\]\s\+pam_faillock.so\)\s\+authfail.*$|\1 authfail deny=10 unlock_time=120|' /etc/pam.d/system-auth fi if [[ -f /etc/pam.d/sddm-autologin ]]; then as_root sed -i '/pam_faillock\.so preauth/d' /etc/pam.d/sddm-autologin as_root sed -i '/pam_faillock\.so authsucc/d' /etc/pam.d/sddm-autologin as_root sed -i '/auth.*pam_permit\.so/a auth required pam_faillock.so authsucc' /etc/pam.d/sddm-autologin fi } apply_user_transition() { log "Applying Omarchy 4 user transition" [[ -d /usr/share/omarchy ]] || fail "/usr/share/omarchy was not installed." run_as_user env \ TARGET_HOME="$target_home" \ BACKUP_SUFFIX="$backup_suffix" \ OMARCHY_USER_NAME="${OMARCHY_USER_NAME:-}" \ OMARCHY_USER_EMAIL="${OMARCHY_USER_EMAIL:-}" \ bash <<'USER_SETUP' set -euo pipefail root=/usr/share/omarchy state_dir="$HOME/.local/state/omarchy" mkdir -p "$state_dir" "$HOME/.config" if [[ -d $root/config ]]; then cp -R "$root/config/." "$HOME/.config/" fi if [[ -f $root/default/bashrc ]]; then cp "$root/default/bashrc" "$HOME/.bashrc" echo '[[ -f ~/.bashrc ]] && . ~/.bashrc' >"$HOME/.bash_profile" fi mkdir -p "$HOME/.agents/skills" "$HOME/.claude/skills" "$HOME/.codex/skills" "$HOME/.pi/agent/skills" if [[ -d $root/default/omarchy-skill ]]; then ln -sfn "$root/default/omarchy-skill" "$HOME/.agents/skills/omarchy" ln -sfn "$root/default/omarchy-skill" "$HOME/.claude/skills/omarchy" ln -sfn "$root/default/omarchy-skill" "$HOME/.codex/skills/omarchy" ln -sfn "$root/default/omarchy-skill" "$HOME/.pi/agent/skills/omarchy" fi mkdir -p "$HOME/.local/state/omarchy/toggles/hypr" if [[ -f $root/default/hypr/toggles/flags.lua ]]; then cp "$root/default/hypr/toggles/flags.lua" "$HOME/.local/state/omarchy/toggles/hypr/" fi mkdir -p "$HOME/.local/share/nautilus-python/extensions" for extension in localsend.py transcode.py; do if [[ -f $root/default/nautilus-python/extensions/$extension ]]; then cp "$root/default/nautilus-python/extensions/$extension" "$HOME/.local/share/nautilus-python/extensions/" fi done mkdir -p "$HOME/.config/omarchy/branding" [[ -f $root/icon.txt ]] && cp "$root/icon.txt" "$HOME/.config/omarchy/branding/about.txt" [[ -f $root/logo.txt ]] && cp "$root/logo.txt" "$HOME/.config/omarchy/branding/screensaver.txt" mkdir -p "$HOME/Downloads" "$HOME/Pictures" "$HOME/Videos" "$HOME/Projects" "$HOME/.config/gtk-3.0" if command -v xdg-user-dirs-update >/dev/null 2>&1; then xdg-user-dirs-update --set TEMPLATES "$HOME" || true xdg-user-dirs-update --set PUBLICSHARE "$HOME" || true xdg-user-dirs-update --set DESKTOP "$HOME" || true fi rmdir "$HOME/Templates" "$HOME/Public" "$HOME/Desktop" 2>/dev/null || true touch "$HOME/.config/gtk-3.0/bookmarks" for dir in Downloads Projects Pictures Videos; do bookmark="file://$HOME/$dir $dir" grep -qxF "$bookmark" "$HOME/.config/gtk-3.0/bookmarks" || echo "$bookmark" >>"$HOME/.config/gtk-3.0/bookmarks" done conf=/etc/vconsole.conf hyprlua="$HOME/.config/hypr/input.lua" if [[ -f $conf && -f $hyprlua ]]; then sed -i '/^[[:space:]]*kb_layout[[:space:]]*=/d' "$hyprlua" sed -i '/^[[:space:]]*kb_variant[[:space:]]*=/d' "$hyprlua" if grep -q '^XKBLAYOUT=' "$conf"; then layout=$(grep '^XKBLAYOUT=' "$conf" | cut -d= -f2 | tr -d '"') sed -i "/^[[:space:]]*kb_options *=/i\ kb_layout = \"$layout\"," "$hyprlua" fi if grep -q '^XKBVARIANT=' "$conf"; then variant=$(grep '^XKBVARIANT=' "$conf" | cut -d= -f2 | tr -d '"') sed -i "/^[[:space:]]*kb_options *=/i\ kb_variant = \"$variant\"," "$hyprlua" fi fi if [[ -n ${OMARCHY_USER_NAME//[[:space:]]/} ]]; then git config --global user.name "$OMARCHY_USER_NAME" || true fi if [[ -n ${OMARCHY_USER_EMAIL//[[:space:]]/} ]]; then git config --global user.email "$OMARCHY_USER_EMAIL" || true fi if [[ ! -f $HOME/.XCompose ]]; then cat >"$HOME/.XCompose" < : "$OMARCHY_USER_NAME" : "$OMARCHY_USER_EMAIL" EOF fi mkdir -p "$HOME/.config/omarchy/themes" "$HOME/.config/btop/themes" ln -snf "$HOME/.config/omarchy/current/theme/btop.theme" "$HOME/.config/btop/themes/current.theme" if [[ ! -e $HOME/.config/omarchy/current/background && -d $HOME/.config/omarchy/current/theme/backgrounds ]]; then background=$(find "$HOME/.config/omarchy/current/theme/backgrounds" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \) | sort | head -n1) [[ -n ${background:-} ]] && ln -snf "$background" "$HOME/.config/omarchy/current/background" fi mkdir -p "$HOME/.config/wireplumber/wireplumber.conf.d" if [[ -f $root/default/wireplumber/wireplumber.conf.d/bluetooth-a2dp-autoconnect.conf ]]; then cp "$root/default/wireplumber/wireplumber.conf.d/bluetooth-a2dp-autoconnect.conf" "$HOME/.config/wireplumber/wireplumber.conf.d/" fi enable_user_unit() { local unit="$1" unit_path="$HOME/.config/systemd/user/$1" target local -a targets=() [[ -f $unit_path ]] || return 0 mapfile -t targets < <(awk -F= ' /^\[Install\]/ { in_install=1; next } /^\[/ { in_install=0 } in_install && $1 == "WantedBy" { n=split($2, values, /[[:space:]]+/) for (i = 1; i <= n; i++) if (values[i] != "") print values[i] } ' "$unit_path") ((${#targets[@]})) || targets=(default.target) for target in "${targets[@]}"; do mkdir -p "$HOME/.config/systemd/user/$target.wants" ln -sfn "../$unit" "$HOME/.config/systemd/user/$target.wants/$unit" done } enable_user_unit bt-agent.service enable_user_unit omarchy-sleep-lock.service mkdir -p "$HOME/.config/systemd/user/default.target.wants" for unit in pipewire-pulse.service pipewire-pulse.socket; do if [[ -e /usr/lib/systemd/user/$unit ]]; then ln -sfn "/usr/lib/systemd/user/$unit" "$HOME/.config/systemd/user/default.target.wants/$unit" fi done keyring_dir="$HOME/.local/share/keyrings" keyring_file="$keyring_dir/Default_keyring.keyring" default_file="$keyring_dir/default" mkdir -p "$keyring_dir" if [[ ! -f $keyring_file ]]; then cat >"$keyring_file" <"$default_file" fi chmod 700 "$keyring_dir" chmod 600 "$keyring_file" chmod 644 "$default_file" mkdir -p "$HOME/.local/share/applications" rm -f "$HOME/.config/autostart/walker.desktop" rm -rf "$HOME/.config/elephant" "$HOME/.config/makima" "$HOME/.config/wiremix" rm -rf "$HOME/.config/systemd/user/app-walker@autostart.service.d" rm -f \ "$HOME/.config/systemd/user/omarchy-battery-monitor.service" \ "$HOME/.config/systemd/user/omarchy-battery-monitor.timer" \ "$HOME/.config/systemd/user/omarchy-shell.service" \ "$HOME/.config/systemd/user/swayosd-server.service" \ "$HOME/.config/systemd/user/elephant.service" rm -f \ "$HOME/.local/state/omarchy/toggles/mako.ini" \ "$HOME/.local/state/omarchy/toggles/waybar-off" \ "$HOME/.local/state/omarchy/toggles/hyprlock.conf" \ "$HOME/.local/state/omarchy/toggles/lock.conf" \ "$HOME/.local/state/omarchy/toggles/hypr/rounded-corners.lua" for desktop in \ About.desktop \ Activity.desktop \ Alacritty.desktop \ Cliamp.desktop \ blueberry.desktop \ nvim.desktop \ omarchy.desktop \ org.pulseaudio.pavucontrol.desktop \ vlc.desktop \ wiremix.desktop; do rm -f "$HOME/.local/share/applications/$desktop" done legacy_icons_dir="$HOME/.local/share/applications/icons" legacy_icons_backup="$HOME/.local/share/applications/icons.omarchy-upgrade-to-4.$BACKUP_SUFFIX.bak" if [[ -d $legacy_icons_dir ]]; then mkdir -p "$legacy_icons_backup" for icon in \ Basecamp.png \ Battle.net.png \ ChatGPT.png \ Cliamp.png \ Discord.png \ "Disk Usage.png" \ Docker.png \ Figma.png \ Fizzy.png \ GitHub.png \ "Google Contacts.png" \ "Google Maps.png" \ "Google Messages.png" \ "Google Photos.png" \ HEY.png \ imv.png \ "Retro Gaming.png" \ WhatsApp.png \ windows.png \ X.png \ YouTube.png \ Zoom.png; do if [[ -e $legacy_icons_dir/$icon ]]; then mv -f "$legacy_icons_dir/$icon" "$legacy_icons_backup/" fi done rmdir "$legacy_icons_dir" 2>/dev/null || true rmdir "$legacy_icons_backup" 2>/dev/null || true fi if [[ -d $root/applications ]]; then find "$root/applications" -maxdepth 1 -type f -name '*.desktop' -exec cp -f {} "$HOME/.local/share/applications/" \; fi if [[ -f $root/default/alacritty/Alacritty.desktop && ! -f $HOME/.local/share/applications/Alacritty.desktop ]]; then cp "$root/default/alacritty/Alacritty.desktop" "$HOME/.local/share/applications/" fi if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true fi if command -v xdg-settings >/dev/null 2>&1; then xdg-settings set default-web-browser chromium.desktop || true fi if command -v xdg-mime >/dev/null 2>&1; then xdg-mime default HEY.desktop x-scheme-handler/mailto || true fi touch "$state_dir/setup-user.done" USER_SETUP } configure_pacman_channel install_keyrings remove_legacy_installer_package remove_conflicting_legacy_packages install_omarchy_4_packages cleanup_legacy_user_paths apply_system_transition apply_user_transition cleanup_retired_services remove_retired_default_packages if start_omarchy_shell_session; then stop_retired_session_processes else warn "Leaving retired Omarchy 3 session processes running until reboot." fi cat </dev/tty; } 2>/dev/null; then printf '\nReboot now? [y/N] ' >&$tty_fd read -r reboot_answer <&$tty_fd || reboot_answer="" exec {tty_fd}>&- if [[ $reboot_answer =~ ^[Yy]([Ee][Ss])?$ ]]; then as_root systemctl reboot else echo "Not rebooting. Please reboot manually after confirming there were no errors." fi else echo "No TTY available for reboot prompt. Please reboot manually after confirming there were no errors." fi