#!/bin/bash # Apply an omarchy-setup profile to the machine it runs on. # # Safe to re-run: every step is a no-op when the target already matches, and any # file it would overwrite is backed up to .bak. first. # # ./install.sh # this machine's profile, every step # ./install.sh --profile hp-laptop # pick the profile # ./install.sh --roles devops,ai-agents # add roles on top of the profile # ./install.sh config themes # only the named steps # ./install.sh --dry-run # print what would change, touch nothing # ./install.sh --no-clobber # never overwrite a file that differs # ./install.sh --show # the layers, and which layer owns each file # ./install.sh --profiles | --list # list profiles | steps # # Exits non-zero if any step reported an error (x). # # With no --profile it uses the one the last install recorded in # ~/.config/omarchy-setup/profile, else the machine whose hostnames= matches. # # Steps: packages config themes plugins bin handoff services system setup set -uo pipefail REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" # Omarchy's commands (omarchy-theme-set, omarchy-pkg-add) are only on PATH in a # login session; an image build over SSH or a Coder start script has neither. OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy} export OMARCHY_PATH case ":$PATH:" in *":$OMARCHY_PATH/bin:"*) ;; *) [[ -d $OMARCHY_PATH/bin ]] && PATH="$PATH:$OMARCHY_PATH/bin" ;; esac STAMP="$(date +%s)" DRY=0 NO_CLOBBER=0 FORCE=0 STEPS=(packages config themes plugins bin handoff services system setup) c_ok=$'\033[32m'; c_skip=$'\033[2m'; c_warn=$'\033[33m'; c_err=$'\033[31m'; c_hd=$'\033[1m'; c_off=$'\033[0m' [[ -t 1 ]] || { c_ok=; c_skip=; c_warn=; c_err=; c_hd=; c_off=; } say() { printf '%s\n' "$*"; } head_() { printf '\n%s==> %s%s\n' "$c_hd" "$*" "$c_off"; } ok() { printf ' %s+%s %s\n' "$c_ok" "$c_off" "$*"; } skip() { printf ' %s= %s%s\n' "$c_skip" "$*" "$c_off"; } warn() { printf ' %s! %s%s\n' "$c_warn" "$*" "$c_off"; } ERRORS=0 err() { printf ' %sx %s%s\n' "$c_err" "$*" "$c_off"; ERRORS=$((ERRORS + 1)); } run() { if (( DRY )); then printf ' %s? would: %s%s\n' "$c_skip" "$*" "$c_off"; else "$@"; fi; } # shellcheck source=lib/layers.sh source "$REPO/lib/layers.sh" # install_file [mode] -- copy only when content differs, backing up first install_file() { local src=$1 dest=$2 mode=${3:-} local rel=${dest/#"$HOME"/\~} if [[ -e $dest ]] && cmp -s "$src" "$dest"; then skip "$rel" return 0 fi if [[ -e $dest ]] && (( NO_CLOBBER )); then warn "$rel differs from the profile - kept (--no-clobber)" return 0 fi if (( DRY )); then printf ' %s? would write %s%s\n' "$c_skip" "$rel" "$c_off" return 0 fi mkdir -p "$(dirname -- "$dest")" if [[ -e $dest ]]; then cp -a "$dest" "$dest.bak.$STAMP" cp "$src" "$dest" [[ -n $mode ]] && chmod "$mode" "$dest" ok "$rel (previous saved as $(basename -- "$dest").bak.$STAMP)" else cp "$src" "$dest" [[ -n $mode ]] && chmod "$mode" "$dest" ok "$rel" fi } # hypr_reload -- reload Hyprland if one is running, even from outside the # session (a Coder workspace-start script has no HYPRLAND_INSTANCE_SIGNATURE). hypr_reload() { (( DRY )) && return 0 command -v hyprctl >/dev/null || return 0 local sig=${HYPRLAND_INSTANCE_SIGNATURE:-} if [[ -z $sig ]]; then sig=$(hyprctl instances -j 2>/dev/null | jq -r '.[0].instance // empty' 2>/dev/null) fi [[ -n $sig ]] || return 0 HYPRLAND_INSTANCE_SIGNATURE=$sig hyprctl reload >/dev/null 2>&1 local errs; errs=$(HYPRLAND_INSTANCE_SIGNATURE=$sig hyprctl configerrors 2>/dev/null) if [[ -n $errs && $errs != "no errors"* ]]; then err "Hyprland reported config errors:"; printf '%s\n' "$errs" | sed 's/^/ /' else ok "hyprctl reload - no config errors" fi } # ---------------------------------------------------------------- packages step_packages() { head_ "Packages" command -v omarchy >/dev/null || { err "omarchy not on PATH - is this an Omarchy system?"; return 1; } load_packages local e name archs cmds layer missing=() present=0 bycmd=() offarch=0 for e in "${PKG_repo[@]}"; do IFS=$PKG_FS read -r name archs cmds layer <<<"$e" if ! pkg_applies "$archs"; then (( offarch++ )); continue; fi if pacman -Qq "$name" &>/dev/null; then (( present++ )) elif pkg_cmd_present "$cmds"; then bycmd+=("$name") else missing+=("$name"); fi done if (( ${#missing[@]} )); then say " installing from repos: ${missing[*]}" run omarchy pkg add "${missing[@]}" || err "some repo packages failed" else skip "all $present repo packages present" fi (( ${#bycmd[@]} )) && skip "already on PATH from elsewhere, not installed: ${bycmd[*]}" (( offarch )) && skip "$offarch not built for $(uname -m), skipped" missing=() for e in "${PKG_aur[@]}"; do IFS=$PKG_FS read -r name archs cmds layer <<<"$e" pkg_applies "$archs" || continue pacman -Qq "$name" &>/dev/null || pkg_cmd_present "$cmds" || missing+=("$name") done if (( ${#missing[@]} )); then say " installing from AUR: ${missing[*]}" run omarchy pkg aur add "${missing[@]}" || err "some AUR packages failed" else skip "all ${#PKG_aur[@]} AUR packages present" fi for e in "${PKG_optional[@]}"; do IFS=$PKG_FS read -r name archs cmds layer <<<"$e" pkg_applies "$archs" || continue pacman -Qq "$name" &>/dev/null || warn "optional, not installed: $name (omarchy pkg add $name)" done if (( ${#P_DOTFILES[@]} )) && [[ ! -d $HOME/.dotfiles ]]; then local -A u=(); local m; for m in "${P_DOTFILES[@]}"; do u[$m]=1; done say "" say " This profile's roles pair with dotfiles modules: ${!u[*]}" say " (git.nic-oconnor.com/public/dotfiles) - not run from here." fi } # ---------------------------------------------------------------- config step_config() { head_ "Config files -> ~/.config" local rel layer declare -A mine=() while IFS=$'\t' read -r rel layer; do install_file "$PROFILES/$layer/config/$rel" "$HOME/.config/$rel" mine[$rel]=1 done < <(owners config) # A hypr/layers/ file that no layer of this profile provides is left over from # a role or profile that no longer applies. It still loads, so say so. local f for f in "$HOME"/.config/hypr/layers/*.lua; do [[ -e $f ]] || continue [[ -n ${mine[hypr/layers/${f##*/}]:-} ]] && continue warn "~/.config/hypr/layers/${f##*/} is from no layer of this profile - still loaded; rm it if unwanted" done hypr_reload } # ---------------------------------------------------------------- themes step_themes() { head_ "Themes -> ~/.config/omarchy/themes" local l t name dest declare -A theme=() for l in "${LAYERS[@]}"; do for t in "$PROFILES/$l"/themes/*/; do [[ -d $t ]] && theme[$(basename -- "$t")]=$t done done (( ${#theme[@]} )) || skip "no layer carries themes" for name in $(printf '%s\n' "${!theme[@]}" | sort); do t=${theme[$name]} dest="$HOME/.config/omarchy/themes/$name" if [[ -d $dest ]] && diff -rq "$t" "$dest" >/dev/null 2>&1; then skip "$name" else run mkdir -p "$dest" run cp -r "$t." "$dest/" ok "$name" fi done if (( ${#P_AETHER[@]} )); then say "" say " Themes managed by aether (not in this repo - reinstall with \`aether\`):" say " ${P_AETHER[*]}" fi (( ${#theme[@]} )) && say " Apply one with: omarchy theme set " return 0 } # ---------------------------------------------------------------- plugins step_plugins() { head_ "Shell plugins -> ~/.config/omarchy/plugins" local dir="$HOME/.config/omarchy/plugins" l declare -A url=() sha=() local id u s for l in "${LAYERS[@]}"; do [[ -f $PROFILES/$l/plugins.txt ]] || continue while IFS=$'\t' read -r id u s; do [[ -z ${id:-} || $id == \#* ]] && continue url[$id]=$u; sha[$id]=$s done < "$PROFILES/$l/plugins.txt" done if (( ! ${#url[@]} )); then skip "no layer carries plugins"; return 0; fi [[ -d $dir ]] || run mkdir -p "$dir" local p for id in $(printf '%s\n' "${!url[@]}" | sort); do p="$dir/$id"; s=${sha[$id]} if [[ -d $p/.git ]]; then if [[ $(git -C "$p" rev-parse HEAD 2>/dev/null) == "$s" ]]; then skip "$id @ ${s:0:7}" continue fi if [[ -n $(git -C "$p" status --porcelain 2>/dev/null) ]]; then warn "$id has local changes - leaving it alone (wanted ${s:0:7})" continue fi run git -C "$p" fetch --quiet origin run git -C "$p" checkout --quiet "$s" && ok "$id -> ${s:0:7}" else if (( DRY )); then printf ' %s? would clone %s -> %s%s\n' "$c_skip" "$id" "${s:0:7}" "$c_off" continue fi git clone --quiet "${url[$id]}" "$p" || { err "clone failed: $id (${url[$id]})"; continue; } git -C "$p" checkout --quiet "$s" || warn "$id: sha $s not found, left on default branch" ok "$id @ ${s:0:7}" fi done # Local plugin work that exists on no remote, per layer: # .bundle a 'local' branch, replayed and checked out # -worktree.patch uncommitted working-tree changes # --bin-- an untracked file installed as /bin/ local f for l in "${LAYERS[@]}"; do [[ -d $PROFILES/$l/plugin-patches ]] || continue for f in "$PROFILES/$l"/plugin-patches/*.bundle; do [[ -f $f ]] && apply_bundle "$(basename -- "$f" .bundle)" "$f" done for f in "$PROFILES/$l"/plugin-patches/*-worktree.patch; do [[ -f $f ]] || continue id=$(basename -- "$f"); apply_worktree_patch "${id%-worktree.patch}" "$f" done for f in "$PROFILES/$l"/plugin-patches/*--bin--*; do [[ -f $f ]] || continue id=$(basename -- "$f") [[ -d $dir/${id%%--bin--*}/.git ]] || continue install_file "$f" "$dir/${id%%--bin--*}/bin/${id##*--bin--}" 755 done done say "" say " Bar placement lives in config/omarchy/shell.json, installed by the config" say " step. The shell hot-reloads it; \`omarchy restart shell\` if not." } # apply_bundle -- replay a plugin's 'local' branch. indie.herdr-hud # carries commits that intentionally never went to the upstream PR. apply_bundle() { local id=$1 bundle=$2 local p="$HOME/.config/omarchy/plugins/$id" [[ -d $p/.git ]] || return 0 if git -C "$p" rev-parse --verify --quiet local >/dev/null; then skip "$id: 'local' branch already present" elif (( DRY )); then printf ' %s? would replay the %s local branch%s\n' "$c_skip" "$id" "$c_off" else git -C "$p" fetch --quiet "$bundle" 'refs/heads/local:refs/heads/local' \ && { git -C "$p" checkout --quiet local && ok "$id: replayed 'local' branch"; } \ || warn "$id: could not replay the local branch from the bundle" fi } apply_worktree_patch() { local id=$1 patch=$2 local p="$HOME/.config/omarchy/plugins/$id" [[ -d $p/.git && -s $patch ]] || return 0 if git -C "$p" apply --reverse --check "$patch" &>/dev/null; then skip "$id: working-tree patch already applied" elif (( DRY )); then printf ' %s? would apply %s working-tree patch%s\n' "$c_skip" "$id" "$c_off" elif git -C "$p" apply --3way "$patch" &>/dev/null; then ok "$id: applied working-tree patch" else warn "$id: working-tree patch did not apply cleanly - see $patch" fi } # ---------------------------------------------------------------- bin step_bin() { head_ "Scripts -> ~/.local/bin" local rel layer n=0 while IFS=$'\t' read -r rel layer; do install_file "$PROFILES/$layer/bin/$rel" "$HOME/.local/bin/$rel" 755 (( n++ )) done < <(owners bin) (( n )) || { skip "no layer carries scripts"; return 0; } case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) warn "~/.local/bin is not on PATH" ;; esac [[ -n $(layers_with bin/fix-stuck-displays) ]] && { say "" say " fix-stuck-displays needs passwordless sudo for 'tee /sys/class/drm/*/status'." } return 0 } # ---------------------------------------------------------------- services # install_unit -- the unit plus its .service.d/*.conf # drop-ins, from whichever layers own them. Sets UNITS_CHANGED. UNITS_CHANGED=0 install_unit() { local name=$1 rel layer dest while IFS=$'\t' read -r rel layer; do [[ $rel == "$name" || $rel == "$name.d/"* ]] || continue dest="$HOME/.config/systemd/user/$rel" [[ -e $dest ]] && cmp -s "$PROFILES/$layer/systemd/user/$rel" "$dest" || UNITS_CHANGED=1 install_file "$PROFILES/$layer/systemd/user/$rel" "$dest" 0644 done < <(owners systemd/user) } # enable_unit -- systemctl --user enable, or, with no user # manager to talk to (an image build), the symlink enable would have made. enable_unit() { local name=$1 want if systemctl --user is-enabled --quiet "$name" 2>/dev/null; then skip "$name already enabled"; return 0 fi if (( DRY )); then printf ' %s? would enable %s%s\n' "$c_skip" "$name" "$c_off"; return 0; fi if systemctl --user enable "$name" >/dev/null 2>&1; then ok "enabled $name"; return 0; fi want=$(sed -n 's/^WantedBy=//p' "$HOME/.config/systemd/user/$name" | head -1) [[ -n $want ]] || { err "could not enable $name"; return 1; } mkdir -p "$HOME/.config/systemd/user/$want.wants" ln -sf "../$name" "$HOME/.config/systemd/user/$want.wants/$name" && ok "enabled $name (symlink; no user manager)" } step_services() { head_ "systemd user units" local rel layer names=() while IFS=$'\t' read -r rel layer; do [[ $rel == *.service ]] && names+=("$rel") done < <(owners systemd/user) (( ${#names[@]} )) || { skip "no layer carries units"; return 0; } UNITS_CHANGED=0 for rel in "${names[@]}"; do install_unit "$rel"; done (( UNITS_CHANGED )) && run systemctl --user daemon-reload 2>/dev/null for rel in "${names[@]}"; do enable_unit "$rel"; done say "" say " Units are WantedBy=graphical-session.target, so they start with Hyprland" say " rather than now. 'systemctl --user start ' to bring one up early." } # ---------------------------------------------------------------- handoff step_handoff() { head_ "omarchy-handoff (lan-mouse)" local layer; layer=$(layers_with handoff | tail -1) [[ -n $layer ]] || { skip "not part of this profile"; return 0; } local hd="$PROFILES/$layer/handoff" # Self-sufficient on purpose: `install.sh handoff` alone brings input sharing up # on an otherwise stock machine, without the packages/config/themes steps that # would drag personalizations along. Everything here is idempotent, so running # the full install.sh afterwards does no extra work. # lan-mouse: the signed package, for the GUI/CLI and as the unpatched fallback. # rust: only to compile handoff/patch/, removable afterwards. # NOT python-evdev: omarchy-air runs this whole setup without it installed, so # it is not required for sharing. local need=() p for p in lan-mouse rust; do pacman -Qq "$p" &>/dev/null || need+=("$p") done if (( ${#need[@]} )); then say " installing: ${need[*]}" run omarchy pkg add "${need[@]}" || warn "package install failed: ${need[*]}" else skip "lan-mouse, rust" fi install_file "$PROFILES/$layer/bin/omarchy-handoff" "$HOME/.local/bin/omarchy-handoff" 0755 # 1. The patched daemon. lan-mouse.service points at this path, so it has to # exist before the unit is enabled. Per-architecture: the HP is x86_64 and # omarchy-air is aarch64, so each builds its own. if [[ -x $HOME/.local/bin/lan-mouse-patched ]]; then skip "~/.local/bin/lan-mouse-patched (delete it to force a rebuild)" elif ! command -v cargo >/dev/null; then warn "cargo missing - run the packages step first, or: omarchy pkg add rust" elif (( DRY )); then printf ' %s? would build ~/.local/bin/lan-mouse-patched from handoff/patch/%s\n' "$c_skip" "$c_off" else say " building patched daemon from handoff/patch/ (~1 min; see handoff/patch/REBUILD.md for why)" if "$hd/patch/build.sh"; then ok "~/.local/bin/lan-mouse-patched" else err "build failed - the unit will not start until this succeeds"; fi fi # 2. The unit (and this machine's drop-ins, e.g. the Air's pointer scale). UNITS_CHANGED=0 install_unit lan-mouse.service (( UNITS_CHANGED )) && run systemctl --user daemon-reload 2>/dev/null enable_unit lan-mouse.service # 3. The firewall. THE bug that cost a full day: ufw is default-deny inbound, so # the peer's DTLS handshake dies at the wire and presents EXACTLY like a bad # certificate. Authorization is receiver-side only (connect.rs sets # insecure_skip_verify), so the RECEIVER's firewall must allow 4242/udp or # that direction is silently dead. local ips=("${P_HANDOFF_IPS[@]}") [[ -n ${HANDOFF_PEER_IPS:-} ]] && read -ra ips <<<"$HANDOFF_PEER_IPS" if ! command -v ufw >/dev/null; then skip "ufw not installed - nothing to open" elif (( ! ${#ips[@]} )); then warn "no handoff_peer_ips in this profile - set them, or HANDOFF_PEER_IPS='a b'" else local ip rules="" # Both reading and changing the ruleset need root. If sudo can't run without # a prompt and there's no terminal to prompt on, say exactly what to run # instead of emitting raw sudo errors and half-applying. if (( ! DRY )) && ! sudo -n true 2>/dev/null && [[ ! -t 0 ]]; then warn "sudo needs a password and there is no terminal; run these yourself:" for ip in "${ips[@]}"; do say " sudo ufw allow from $ip to any port 4242 proto udp comment 'lan-mouse peer $ip'" done say "" say " Without them, anything the peer INITIATES toward this machine is" say " dropped at the wire and looks exactly like a bad certificate." return 0 fi (( DRY )) || rules=$(sudo ufw status 2>/dev/null || true) for ip in "${ips[@]}"; do if [[ $rules == *"4242"*"$ip"* ]]; then skip "ufw allows 4242/udp from $ip" elif (( DRY )); then printf ' %s? would allow 4242/udp from %s%s\n' "$c_skip" "$ip" "$c_off" else sudo ufw allow from "$ip" to any port 4242 proto udp \ comment "lan-mouse peer $ip" >/dev/null && ok "ufw: 4242/udp from $ip" fi done say "" say " Verify a blocked handshake with: journalctl -k -g 'UFW BLOCK'" fi say "" say " ~/.config/lan-mouse/ is NOT in this repo (private key + per-machine" say " fingerprints). Start from $layer/handoff/config/config.toml.example-* and:" say " omarchy-handoff fingerprint # give it to the peer, LOWERCASE" say " omarchy-handoff pair " say " omarchy-handoff doctor # must pass before arming" say " omarchy-handoff on" say " A fresh install regenerates the cert, so the PEER must re-pair." } # ---------------------------------------------------------------- system step_system() { head_ "System drop-ins -> /etc (needs sudo)" local rel layer dest n=0 sysctl=0 modprobe=0 while IFS=$'\t' read -r rel layer; do (( n++ )) dest="/etc/$rel" if [[ -e $dest ]] && cmp -s "$PROFILES/$layer/system/$rel" "$dest"; then skip "$dest" continue fi if (( DRY )); then printf ' %s? would write %s%s\n' "$c_skip" "$dest" "$c_off" continue fi sudo install -D -m 0644 "$PROFILES/$layer/system/$rel" "$dest" && ok "$dest" [[ $rel == sysctl.d/* ]] && sysctl=1 [[ $rel == modprobe.d/* ]] && modprobe=1 done < <(owners system) (( n )) || { skip "no layer carries /etc drop-ins"; return 0; } (( sysctl )) && sudo sysctl --system >/dev/null 2>&1 && ok "sysctl reloaded" if (( modprobe )); then say "" say " modprobe.d options (hid_apple fnmode, appledrm notch) only take effect once" say " the initramfs is rebuilt or the module reloads:" say " sudo mkinitcpio -P # or: sudo modprobe -r && sudo modprobe " fi } # ---------------------------------------------------------------- setup # Each layer's setup.sh, for state outside ~/.config (markers, gsettings, a # first-run theme). Run last, in layer order. step_setup() { head_ "Layer setup scripts" local l n=0 for l in "${LAYERS[@]}"; do [[ -x $PROFILES/$l/setup.sh ]] || continue (( n++ )) say " $l/setup.sh" OMARCHY_SETUP_DRY=$DRY OMARCHY_SETUP_REPO=$REPO OMARCHY_SETUP_LAYER="$PROFILES/$l" \ "$PROFILES/$l/setup.sh" || err "$l/setup.sh failed" done (( n )) || skip "no layer has a setup.sh" return 0 } # ---------------------------------------------------------------- show show() { say "${c_hd}$PROFILE${c_off} $(layer_description "$PROFILE")" [[ -n $P_ARCH ]] && say " arch: $P_ARCH" say "" say "Layers, in order (later wins):" local l; for l in "${LAYERS[@]}"; do printf ' %-26s %s\n' "$l" "$(layer_description "$l")"; done local sub rel layer for sub in config bin systemd/user system; do local any=0 while IFS=$'\t' read -r rel layer; do (( any )) || { say ""; say "$sub/"; any=1; } printf ' %-52s %s\n' "$rel" "$layer" done < <(owners "$sub") done } list_profiles() { local kind d for kind in machines roles features; do say "${c_hd}$kind${c_off}" for d in "$PROFILES/$kind"/*/; do d=${d%/}; printf ' %-18s %s\n' "${d##*/}" "$(layer_description "$kind/${d##*/}")" done done say "${c_hd}shared${c_off}" for d in base personal personal-desktop; do printf ' %-18s %s\n' "$d" "$(layer_description "$d")"; done } # ---------------------------------------------------------------- main main() { local want=() profile="" roles_arg="" roles_given=0 show_only=0 a while (( $# )); do a=$1; shift case $a in --dry-run|-n) DRY=1 ;; --no-clobber) NO_CLOBBER=1 ;; --force) FORCE=1 ;; --profile|-p) profile=${1:?--profile needs a name}; shift ;; --profile=*) profile=${a#*=} ;; --roles|-r) roles_arg=${1-}; roles_given=1; shift ;; --roles=*) roles_arg=${a#*=}; roles_given=1 ;; --show) show_only=1 ;; --profiles) list_profiles; exit 0 ;; --list|-l) printf '%s\n' "${STEPS[@]}"; exit 0 ;; -h|--help) awk 'NR > 1 && /^#/ { sub(/^# ?/, ""); print; next } NR > 1 { exit }' "${BASH_SOURCE[0]}"; exit 0 ;; -*) err "unknown option: $a"; exit 2 ;; *) if [[ " ${STEPS[*]} " == *" $a "* ]]; then want+=("$a") else err "unknown step: $a (one of: ${STEPS[*]})"; exit 2; fi ;; esac done (( ${#want[@]} )) || want=("${STEPS[@]}") local saved_profile="" saved_roles="" if def=$(default_profile); then IFS=$'\t' read -r saved_profile saved_roles <<<"$def"; fi if [[ -z $profile ]]; then profile=$saved_profile [[ -n $profile ]] || { err "no --profile given and none matches host $(hostname)"; list_profiles; exit 2; } fi local rp; rp=$(find_layer "$profile") || { err "no such profile: $profile"; list_profiles; exit 2; } # No --roles reuses the roles last installed with this profile; --roles "" # means none (a Coder workspace with every extra role deselected). (( roles_given )) || [[ $rp != "$saved_profile" ]] || roles_arg=$saved_roles ROLES=() [[ -n $roles_arg ]] && IFS=', ' read -ra ROLES <<<"$roles_arg" resolve "$rp" "${ROLES[@]}" || exit 2 load_settings if (( show_only )); then show; exit 0; fi say "${c_hd}omarchy-setup${c_off} profile=$PROFILE${ROLES:+ roles=${ROLES[*]}} repo=$REPO" say " layers: ${LAYERS[*]}" (( DRY )) && warn "dry run - nothing will be changed" (( NO_CLOBBER )) && warn "--no-clobber: files that differ from the profile are kept" command -v omarchy >/dev/null || warn "omarchy not found; this looks like a non-Omarchy host" if [[ -n $P_ARCH && $P_ARCH != "$(uname -m)" ]]; then err "$PROFILE is for $P_ARCH, this machine is $(uname -m)" (( FORCE )) || { say " --force to apply it anyway"; exit 2; } fi local host; host=$(hostname 2>/dev/null || cat /etc/hostname) if (( ${#P_HOSTNAMES[@]} )) && [[ " ${P_HOSTNAMES[*]} " != *" $host "* ]]; then warn "$PROFILE is for ${P_HOSTNAMES[*]}, this host is $host" fi # Canonical order whatever order they were named in: setup after config, etc. for s in "${STEPS[@]}"; do [[ " ${want[*]} " == *" $s "* ]] && "step_$s"; done (( DRY )) || save_state head_ "Done" say " Re-running is safe. Backups from this run are suffixed .bak.$STAMP" say " Pull live changes back into the repo with ./capture.sh" # Non-zero when any step reported an error, so an image build fails on it. if (( ERRORS )); then err "$ERRORS error(s) above"; exit 1; fi } main "$@"