/ de-omarchy / install.sh
swadhinbiswas install.sh: yay-compatible AUR flags, preserve theme on re-run, restart-shell guidance · 8/24/2026
e6deb17
334 lines · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#!/bin/bash
# ============================================================================
#  de-omarchy installer — Omarchy look & feel for any Arch-based system
#
#  Safety model:
#    * backs up everything BEFORE touching anything (outside this repo)
#    * never deletes or overwrites an existing user config; new files are
#      added, one clearly-marked block is APPENDED to hyprland.lua
#    * idempotent: safe to re-run
#    * fully reversible: uninstall.sh undoes every change made here
#  It never touches pacman.conf, kernels, bootloaders, mirrors or repos.
# ============================================================================
set -euo pipefail

REPO_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
RUNTIME=/usr/share/de-omarchy
BACKUP_ROOT="${DEO_BACKUP_ROOT:-$HOME/.de-omarchy-backups}"
MARKER_START=">>> de-omarchy layer"
MARKER_END="<<< end de-omarchy layer >>>"

log()  { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m ->\033[0m %s\n' "$*"; }
die()  { printf '\033[1;31mERROR:\033[0m %s\n' "$*" >&2; exit 1; }

# ----------------------------------------------------------------------------
# 1. Preflight
# ----------------------------------------------------------------------------
[[ $EUID -eq 0 ]] && die "Run as your normal user (sudo is invoked when needed)."

[[ -r /etc/os-release ]] || die "/etc/os-release not found — not an Arch-based system?"
. /etc/os-release
case ${ID_LIKE:-} in
  *arch*) ;; *) [[ ${ID:-} == arch ]] || die "This looks like '${ID:-unknown}', de-omarchy targets Arch-based distros." ;;
esac

for cmd in git rsync curl; do command -v $cmd >/dev/null 2>&1 || die "missing command: $cmd"; done

AUR_HELPER=""
for h in paru yay; do command -v $h >/dev/null 2>&1 && { AUR_HELPER=$h; break; }; done

ASSUME_YES=0
SKIP_BACKUP=0
SKIP_PACKAGES=0
for arg in "$@"; do
  case $arg in
    -y|--yes) ASSUME_YES=1 ;;
    --skip-backup) SKIP_BACKUP=1 ;;
    --skip-packages) SKIP_PACKAGES=1 ;;
    *) warn "unknown option: $arg" ;;
  esac
done

cat <<BANNER

  de-omarchy — apply the Omarchy desktop look on top of $(printf '%s' "${PRETTY_NAME:-this system}")

  What happens:
    1. TARGETED backup (seconds): every file this installer could touch,
       plus .ssh / zsh stack / starship — -> $BACKUP_ROOT/<timestamp>/
       (add DEO_BACKUP_FULL=1 to also snapshot all of ~/.config)
    2. Missing UI packages installed (system layer untouched)
    3. Runtime deployed to $RUNTIME
    4. Additive user config hooks installed:
         ~/.config/hypr/{deo-layer,deo-bindings,deo-autostart}.lua  (symlinks -> runtime)
         ~/.config/hypr/hyprland.lua                                (marked block APPENDED)
         ~/.config/zshrc.d/50-de-omarchy.zsh                        (symlink -> runtime;
          your .zshrc already sources this dir — zero edits to .zshrc)
    5. Omarchy state initialized + rose-pine theme applied

  NOTHING in your home directory is ever deleted or overwritten:
    * new files are only ADDED (symlinks into this repo)
    * hyprland.lua gets one clearly-marked block APPENDED; a timestamped
      copy of it is saved next to it first
    * existing ~/.config/omarchy state is merged with --ignore-existing
  NOT touched: .zshrc/.zshenv/starship.toml/kitty.conf/ssh keys, pacman.conf,
  mirrors, kernel, bootloader, display manager, monitors layout.

BANNER

if (( ! ASSUME_YES )); then
  read -rp "Proceed? [y/N] " answer
  [[ $answer =~ ^[Yy]$ ]] || die "aborted by user"
fi

# ----------------------------------------------------------------------------
# 2. Backup (Phase 0 — always first)
# ----------------------------------------------------------------------------
if (( SKIP_BACKUP )); then
  warn "skipping backup (--skip-backup) — NOT recommended"
else
  log "Creating timestamped backup"
  DEO_BACKUP_ROOT=$BACKUP_ROOT bash "$REPO_DIR/scripts/backup.sh"
fi
LATEST_BACKUP=$(ls -1dt "$BACKUP_ROOT"/*/ 2>/dev/null | head -1 || true)
[[ -n $LATEST_BACKUP ]] || die "backup missing after backup step — aborting"
log "Backup ready: $LATEST_BACKUP"

# ----------------------------------------------------------------------------
# 3. Packages (UI layer only, missing ones only)
# ----------------------------------------------------------------------------
if (( ! SKIP_PACKAGES )); then
  log "Checking UI packages"
  MISSING=()
  while read -r pkg; do
    [[ -z $pkg || $pkg == \#* ]] && continue
    pacman -Qi "$pkg" >/dev/null 2>&1 || MISSING+=("$pkg")
  done < "$REPO_DIR/packages/ui.packages"

  if (( ${#MISSING[@]} == 0 )); then
    log "All UI packages already present"
  else
    log "Installing missing UI packages: ${MISSING[*]}"
    # Repo packages first, in ONE transaction (single sudo prompt).
    REPO_PKGS=()
    for pkg in "${MISSING[@]}"; do
      pacman -Si "$pkg" >/dev/null 2>&1 && REPO_PKGS+=("$pkg")
    done
    (( ${#REPO_PKGS[@]} > 0 )) && sudo pacman -S --needed --noconfirm "${REPO_PKGS[@]}"

    # Whatever remains is AUR-only: per package, with piped answers so
    # prompts (provider selection etc.) never eat our package list.
    # --skipreview is paru-specific; yay rejects unknown flags.
    AUR_FLAGS=(-S --needed --noconfirm)
    [[ $AUR_HELPER == paru ]] && AUR_FLAGS+=(--skipreview)
    FAILED=""
    for pkg in "${MISSING[@]}"; do
      pacman -Qi "$pkg" >/dev/null 2>&1 && continue
      if [[ -n $AUR_HELPER ]]; then
        log "Installing AUR package: $pkg ($AUR_HELPER)"
        if (( ASSUME_YES )); then
          "$AUR_HELPER" "${AUR_FLAGS[@]}" "$pkg" </dev/null || FAILED+="$pkg "
        else
          printf 'y\n' | "$AUR_HELPER" "${AUR_FLAGS[@]}" "$pkg" || FAILED+="$pkg "
        fi
      else
        FAILED+="$pkg "
      fi
    done
    [[ -n ${FAILED:-} ]] && warn "could not auto-install: $FAILED (install manually; desktop works without them)"
  fi
fi

command -v quickshell >/dev/null 2>&1 || die "quickshell is required but not installed"

# ----------------------------------------------------------------------------
# 4. Runtime deployment to $RUNTIME
# ----------------------------------------------------------------------------
log "Deploying runtime to $RUNTIME"
sudo mkdir -p "$RUNTIME"
sudo rsync -a --delete \
  "$REPO_DIR/bin" "$REPO_DIR/shell" "$REPO_DIR/themes" \
  "$REPO_DIR/applications" "$REPO_DIR/default" "$REPO_DIR/config" \
  "$REPO_DIR/keybindings" "$REPO_DIR/plugin-library" \
  "$REPO_DIR/monitor-manager" \
  "$REPO_DIR/packages" \
  "$REPO_DIR/registry.json" "$REPO_DIR/logo.txt" "$REPO_DIR/icon.txt" \
  "$RUNTIME/"
sudo chmod +x "$RUNTIME"/bin/* 2>/dev/null || true

log "Writing /etc/environment.d/10-de-omarchy.conf"
sudo mkdir -p /etc/environment.d
printf 'OMARCHY_PATH=%s\n' "$RUNTIME" | sudo tee /etc/environment.d/10-de-omarchy.conf >/dev/null

# Symlink key commands into /usr/bin so sudo/polkit can find them (omarchy-dns
# requires root, and the shell IPC commands need to be on the system PATH).
log "Symlinking key commands to /usr/bin"
for cmd in omarchy-dns omarchy-menu omarchy-shell omarchy-theme-set omarchy-theme-list \
           omarchy-theme-bg-set omarchy-theme-bg-next omarchy-theme-refresh \
           omarchy-capture-screenshot omarchy-capture-region omarchy-capture-text \
           omarchy-system-lock omarchy-restart-shell omarchy-keybindings omarchy-plugin-manager; do
  [[ -f "$RUNTIME/bin/$cmd" ]] && sudo ln -sf "$RUNTIME/bin/$cmd" "/usr/bin/$cmd" 2>/dev/null
done

# ----------------------------------------------------------------------------
# 5. User config hooks (additive only)
# ----------------------------------------------------------------------------
log "Installing user config hooks"

# Symlinks point at the RUNTIME copy, never at this git checkout: after
# install, the desktop must keep working even if the repo is moved or deleted.
# The runtime copies are refreshed by every install.sh / rsync run.
mkdir -p "$HOME/.config/hypr" "$HOME/.config/zshrc.d"

ln -sfn "$RUNTIME/config/hypr/deo-layer.lua"     "$HOME/.config/hypr/deo-layer.lua"
ln -sfn "$RUNTIME/config/hypr/deo-bindings.lua"  "$HOME/.config/hypr/deo-bindings.lua"
ln -sfn "$RUNTIME/config/hypr/deo-autostart.lua" "$HOME/.config/hypr/deo-autostart.lua"
ln -sfn "$RUNTIME/config/zshrc.d/50-de-omarchy.zsh" "$HOME/.config/zshrc.d/50-de-omarchy.zsh"

# Default omarchy user-state config (extensions/hooks/themed) — never overwrite.
if [[ -d $REPO_DIR/config/omarchy ]]; then
  mkdir -p "$HOME/.config/omarchy"
  rsync -a --ignore-existing "$REPO_DIR/config/omarchy/" "$HOME/.config/omarchy/"
fi

# Deploy terminal/starship/nvim/opencode configs (only if user doesn't have them)
log "Deploying UI configs (never overwrites existing)"
for pair in "kitty/kitty.conf:.config/kitty/kitty.conf" "starship/starship.toml:.config/starship.toml" "opencode/opencode.jsonc:.config/opencode/opencode.jsonc"; do
  src="$REPO_DIR/config/${pair%%:*}"
  dst="$HOME/${pair##*:}"
  if [[ -f $src ]] && [[ ! -f $dst ]]; then
    mkdir -p "$(dirname "$dst")"
    cp "$src" "$dst"
    log "deployed $dst"
  fi
done
# Neovim config: rsync with ignore-existing (preserves user's plugins/config)
if [[ -d $REPO_DIR/config/nvim ]]; then
  mkdir -p "$HOME/.config/nvim"
  rsync -a --ignore-existing "$REPO_DIR/config/nvim/" "$HOME/.config/nvim/"
fi

# CapsLock emoji compose sequences — only when the user has no XCompose of
# their own. The include points at the runtime copy, so theme-independent.
XCOMPOSE="$HOME/.XCompose"
if [[ ! -f $XCOMPOSE && -f "$RUNTIME/default/xcompose" ]]; then
  printf 'include "%s"\n' "$RUNTIME/default/xcompose" > "$XCOMPOSE"
  log "deployed $XCOMPOSE (CapsLock emoji compose; delete to opt out)"
fi

# Voxtype dictation config template — same never-overwrite rule as above.
VOXTYPE_CFG="$HOME/.config/voxtype/config.toml"
if [[ ! -f $VOXTYPE_CFG && -f "$RUNTIME/default/voxtype/config.toml" ]]; then
  mkdir -p "$(dirname "$VOXTYPE_CFG")"
  cp "$RUNTIME/default/voxtype/config.toml" "$VOXTYPE_CFG"
  log "deployed $VOXTYPE_CFG"
fi

# Branding art for the About window and screensaver — never-overwrite: the
# user's edits (omarchy branding …) survive reinstalls. Without these the
# branding editors fail with "no such file or directory" and the screensaver
# has nothing to draw.
if [[ -f "$RUNTIME/logo.txt" ]]; then
  mkdir -p "$HOME/.config/omarchy/branding"
  for art in screensaver:logo.txt about:icon.txt; do
    dst_name=${art%%:*}; src_name=${art##*:}
    dst="$HOME/.config/omarchy/branding/$dst_name.txt"
    if [[ ! -f $dst ]]; then
      cp "$RUNTIME/$src_name" "$dst"
      log "deployed $dst"
    fi
  done
fi

# AI agent skills: symlink the bundled Omarchy/diagnose-crash skills into
# every known agent skill directory (same loop omarchy-provision-user runs;
# safe to re-run, ln -sfn is idempotent).
if [[ -d $RUNTIME/default/agents/skills ]]; then
  log "Linking agent skills"
  mkdir -p "$HOME/.agents/skills" "$HOME/.claude/skills" "$HOME/.codex/skills" \
    "$HOME/.pi/agent/skills" "$HOME/.gemini/config/skills"
  for skill_dir in "$RUNTIME"/default/agents/skills/*/; do
    skill_name=${skill_dir%/}
    skill_name=${skill_name##*/}
    ln -sfn "$skill_dir" "$HOME/.agents/skills/$skill_name"
    ln -sfn "$skill_dir" "$HOME/.claude/skills/$skill_name"
    ln -sfn "$skill_dir" "$HOME/.codex/skills/$skill_name"
    ln -sfn "$skill_dir" "$HOME/.pi/agent/skills/$skill_name"
    ln -sfn "$skill_dir" "$HOME/.gemini/config/skills/$skill_name"
  done
fi

# Append the marked activation block to hyprland.lua (idempotent).
HYPR_MAIN="$HOME/.config/hypr/hyprland.lua"
if [[ ! -f $HYPR_MAIN ]]; then
  die "~/.config/hypr/hyprland.lua not found — install/configure Hyprland first (or point me at your entry config)"
fi
if grep -q "$MARKER_START" "$HYPR_MAIN"; then
  log "hyprland.lua already carries the de-omarchy block — skipping append"
else
  cp -a "$HYPR_MAIN" "$HYPR_MAIN.pre-de-omarchy.$(date +%Y%m%d%H%M%S)"
  cat "$REPO_DIR/config/hypr/append-block.lua" >> "$HYPR_MAIN"
  log "Appended de-omarchy block to $HYPR_MAIN (pre-change copy saved next to it)"
fi

# ----------------------------------------------------------------------------
# 6. PAM config for Omarchy shell lock screen
# ----------------------------------------------------------------------------
if [[ ! -f /etc/pam.d/omarchy-lock-password ]]; then
  log "Installing PAM config for Omarchy shell lock"
  sudo cp "$REPO_DIR/config/pam/omarchy-lock-password" /etc/pam.d/omarchy-lock-password
  sudo chmod 644 /etc/pam.d/omarchy-lock-password
fi

# ----------------------------------------------------------------------------
# 7. Clean up stale host config (additive: comments out dead code)
# ----------------------------------------------------------------------------
log "Cleaning up stale host config"

# Remove stale zsh files from old shell ecosystem
for stale in moonrice.zsh dots-hyprland.zsh auto-Hypr.sh; do
  [[ -f "$HOME/.config/zshrc.d/$stale" ]] && rm "$HOME/.config/zshrc.d/$stale" && log "removed stale zsh: $stale"
done

# Repoint dead moonshell:* keymap targets at Omarchy equivalents
bash "$REPO_DIR/scripts/migrate-host-binds.sh"

# ----------------------------------------------------------------------------
# 8. Omarchy state init + default theme (first install only)
# ----------------------------------------------------------------------------
export OMARCHY_PATH=$RUNTIME
if grep -q . "$HOME/.local/state/omarchy/current/theme.name" 2>/dev/null; then
  log "Omarchy state already initialized — keeping theme '$(cat "$HOME/.local/state/omarchy/current/theme.name")'"
else
  log "Initializing Omarchy state + rose-pine theme"
  mkdir -p "$HOME/.local/state/omarchy/current"
  "$RUNTIME/bin/omarchy-theme-set" rose-pine || warn "theme set failed (non-fatal; run 'omarchy theme set <name>' later)"
fi

unset OMARCHY_PATH

# ----------------------------------------------------------------------------
# 9. Done
# ----------------------------------------------------------------------------
log "Verifying installation"
bash "$REPO_DIR/scripts/verify.sh" || warn "verification reported issues above"

cat <<DONE

  de-omarchy installed.

  If your desktop was ALREADY running before this install, apply the new
  runtime now (this picks up shell/QML changes too):
      omarchy-restart-shell
      hyprctl reload

  Full experience after next login (env vars settle everywhere).
  Themes:   omarchy theme list   |   omarchy theme set "Tokyo Night"
  Menu:     SUPER + SHIFT + M    (upstream's SUPER+SPACE stays yours)
  Screensaver: idle for ~2.5 min — animated via ttfx if python-terminaltexteffects
               installed; static art otherwise (see any warning above).
  Revert:   ./uninstall.sh       (restores the exact pre-install state)

DONE