hosts/<hostname>/{config,system}/ now overlays the shared trees. install.sh
installs a host file in place of the shared copy at the same path, and
capture.sh pulls back into whichever copy the host installs from. That keeps
hardware-specific files off the wrong machine: the HP's EDID-named monitors.lua
and its fnmode=2 would otherwise land on the Air.
capture.sh refreshes the shared trees only on omarchy-laptop, where they were
captured, or when CAPTURE_SHARED=1 is set. On the Air, a plain run copied its
stock terminal, hypr and shell files over every one of the HP's customizations.
air's overlay holds monitors.lua (1.33333 scale and GDK_SCALE=1 for the
2560x1664 panel), input.lua (stock settings plus the inert-accel measurements),
herdr onboarding=false, git identity and credential helpers (no secrets),
mimeapps.list, and the Asahi installer's hid_apple fnmode=1 and show_notch=1.
bindings.lua is not overlaid because the shared one is a strict superset.
hosts/omarchy-air/README.md records what was left out on purpose and why, and
warns that the shared packages.txt is x86-specific.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KKPyqMc7MGKyBLKVdeDejp
215 lines
8.3 KiB
Bash
Executable File
215 lines
8.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# Pull the live machine's state back into this repo - the reverse of install.sh.
|
|
#
|
|
# Run it after customizing something by hand, then review `git diff` and commit.
|
|
# It only refreshes paths the repo already tracks; it never invents new ones, so
|
|
# adding a newly customized file is a deliberate act (see README, "Adding a file").
|
|
#
|
|
# ./capture.sh # refresh everything
|
|
# ./capture.sh config # only the named steps
|
|
# ./capture.sh --list
|
|
#
|
|
# Steps: config themes plugins bin services system packages
|
|
|
|
set -uo pipefail
|
|
|
|
REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
STEPS=(config themes plugins bin services system packages)
|
|
HOST="${OMARCHY_SETUP_HOST:-$(hostname)}"
|
|
HOST_DIR="$REPO/hosts/$HOST"
|
|
# The shared config/ and system/ trees were captured from this host. Capturing
|
|
# them anywhere else would overwrite its customizations with the other machine's
|
|
# (often stock) files, so elsewhere only the host overlay is refreshed unless
|
|
# CAPTURE_SHARED=1 says the change really is meant for every machine.
|
|
SHARED_HOST="${OMARCHY_SETUP_SHARED_HOST:-omarchy-laptop}"
|
|
CAPTURE_SHARED="${CAPTURE_SHARED:-0}"
|
|
[[ $HOST == "$SHARED_HOST" ]] && CAPTURE_SHARED=1
|
|
|
|
c_ch=$'\033[33m'; c_skip=$'\033[2m'; c_warn=$'\033[33m'; c_hd=$'\033[1m'; c_off=$'\033[0m'
|
|
[[ -t 1 ]] || { c_ch=; c_skip=; c_warn=; c_hd=; c_off=; }
|
|
say() { printf '%s\n' "$*"; }
|
|
head_() { printf '\n%s==> %s%s\n' "$c_hd" "$*" "$c_off"; }
|
|
chg() { printf ' %sM %s%s\n' "$c_ch" "$*" "$c_off"; }
|
|
skip() { printf ' %s= %s%s\n' "$c_skip" "$*" "$c_off"; }
|
|
warn() { printf ' %s! %s%s\n' "$c_warn" "$*" "$c_off"; }
|
|
gone() { printf ' %s? %s%s\n' "$c_warn" "$*" "$c_off"; }
|
|
|
|
# pull_back <live> <repo-copy> <label>
|
|
pull_back() {
|
|
local live=$1 dest=$2 label=$3
|
|
if [[ ! -e $live ]]; then gone "$label - not on this machine any more"; return; fi
|
|
if cmp -s "$live" "$dest"; then skip "$label"; else cp "$live" "$dest"; chg "$label"; fi
|
|
}
|
|
|
|
# layered <tree> -- NUL-separated "<repo-copy>\t<rel>" across the shared tree and
|
|
# this host's overlay, the overlay copy winning, so a live file is pulled back into
|
|
# whichever copy this host actually installs from (see install.sh).
|
|
layered() {
|
|
local tree=$1 f rel
|
|
declare -A pick=()
|
|
for base in "$REPO/$tree" "$HOST_DIR/$tree"; do
|
|
[[ -d $base ]] || continue
|
|
while IFS= read -r -d '' f; do
|
|
rel=${f#"$base/"}; pick[$rel]=$f
|
|
done < <(find "$base" -type f -print0)
|
|
done
|
|
for rel in "${!pick[@]}"; do printf '%s\t%s\0' "${pick[$rel]}" "$rel"; done | sort -z -t$'\t' -k2
|
|
}
|
|
|
|
step_config() {
|
|
head_ "~/.config -> config/ (+ hosts/$HOST/config)"
|
|
local dest rel
|
|
while IFS=$'\t' read -r -d '' dest rel; do
|
|
local tag=$rel; [[ $dest == "$HOST_DIR"/* ]] && tag="$rel (hosts/$HOST)"
|
|
if [[ $dest != "$HOST_DIR"/* ]] && (( ! CAPTURE_SHARED )); then skip "$rel (shared; not on $SHARED_HOST)"; continue; fi
|
|
pull_back "$HOME/.config/$rel" "$dest" "$tag"
|
|
done < <(layered config)
|
|
}
|
|
|
|
step_themes() {
|
|
head_ "Themes"
|
|
local t name live
|
|
for t in "$REPO"/themes/*/; do
|
|
[[ -d $t ]] || continue
|
|
name=$(basename -- "$t")
|
|
live="$HOME/.config/omarchy/themes/$name"
|
|
if [[ ! -d $live ]]; then gone "$name - no longer installed"; continue; fi
|
|
if diff -rq "$live" "$t" >/dev/null 2>&1; then
|
|
skip "$name"
|
|
else
|
|
rm -rf "$t"; mkdir -p "$t"; cp -r "$live/." "$t/"
|
|
chg "$name"
|
|
fi
|
|
done
|
|
# Flag themes installed by hand that the repo does not carry.
|
|
local l lname
|
|
for l in "$HOME"/.config/omarchy/themes/*/; do
|
|
[[ -d $l ]] || continue
|
|
lname=$(basename -- "$l")
|
|
[[ -d "$REPO/themes/$lname" ]] && continue
|
|
[[ -e "$l/.aether-managed" ]] && continue
|
|
warn "$lname is installed but not in this repo (cp -r it into themes/ to track it)"
|
|
done
|
|
}
|
|
|
|
step_plugins() {
|
|
head_ "Plugin pins and local work"
|
|
local dir="$HOME/.config/omarchy/plugins"
|
|
local tmp; tmp=$(mktemp)
|
|
{
|
|
echo "# Omarchy shell plugins. Fields: id<TAB>repo<TAB>pinned-sha"
|
|
echo "# install.sh clones each into ~/.config/omarchy/plugins/<id> and checks out the sha."
|
|
local p id url sha
|
|
for p in "$dir"/*/; do
|
|
[[ -d $p/.git ]] || continue
|
|
id=$(basename -- "$p")
|
|
url=$(git -C "$p" remote get-url origin 2>/dev/null) || continue
|
|
sha=$(git -C "$p" rev-parse HEAD)
|
|
printf '%s\t%s\t%s\n' "$id" "$url" "$sha"
|
|
done
|
|
} > "$tmp"
|
|
if cmp -s "$tmp" "$REPO/plugins.txt"; then skip "plugins.txt"; else mv "$tmp" "$REPO/plugins.txt"; chg "plugins.txt"; fi
|
|
rm -f "$tmp"
|
|
|
|
# herdr-hud's local-only branch, re-bundled
|
|
local p="$dir/indie.herdr-hud"
|
|
if [[ -d $p/.git ]] && git -C "$p" rev-parse --verify --quiet local >/dev/null; then
|
|
local b="$REPO/plugin-patches/indie.herdr-hud.bundle"
|
|
git -C "$p" bundle create "$b.new" origin/main..local >/dev/null 2>&1
|
|
# Bundles embed a timestamp-free pack but differ byte-wise; compare the tips instead.
|
|
if [[ -f $b ]] && [[ $(git -C "$p" bundle list-heads "$b" 2>/dev/null) == $(git -C "$p" bundle list-heads "$b.new" 2>/dev/null) ]]; then
|
|
rm -f "$b.new"; skip "indie.herdr-hud.bundle"
|
|
else
|
|
mv "$b.new" "$b"; chg "indie.herdr-hud.bundle"
|
|
fi
|
|
[[ -f $p/bin/herdr-agents ]] && pull_back "$p/bin/herdr-agents" \
|
|
"$REPO/plugin-patches/indie.herdr-hud--bin--herdr-agents" "indie.herdr-hud bin/herdr-agents"
|
|
fi
|
|
|
|
local f id
|
|
for f in "$REPO"/plugin-patches/*-worktree.patch; do
|
|
[[ -f $f ]] || continue
|
|
id=$(basename -- "$f"); id=${id%-worktree.patch}
|
|
if [[ ! -d $dir/$id/.git ]]; then gone "$id - not installed"; continue; fi
|
|
local tmp2; tmp2=$(mktemp)
|
|
git -C "$dir/$id" diff > "$tmp2"
|
|
if cmp -s "$tmp2" "$f"; then skip "$id-worktree.patch"; else mv "$tmp2" "$f"; chg "$id-worktree.patch"; fi
|
|
rm -f "$tmp2"
|
|
done
|
|
}
|
|
|
|
step_bin() {
|
|
head_ "~/.local/bin -> bin/"
|
|
local f
|
|
for f in "$REPO"/bin/*; do
|
|
[[ -f $f ]] || continue
|
|
pull_back "$HOME/.local/bin/$(basename -- "$f")" "$f" "$(basename -- "$f")"
|
|
done
|
|
}
|
|
|
|
step_services() {
|
|
head_ "systemd user units"
|
|
local f name
|
|
for f in "$REPO"/systemd/user/*.service; do
|
|
[[ -f $f ]] || continue
|
|
name=$(basename -- "$f")
|
|
pull_back "$HOME/.config/systemd/user/$name" "$f" "$name"
|
|
done
|
|
}
|
|
|
|
step_system() {
|
|
head_ "/etc drop-ins"
|
|
local dest rel
|
|
while IFS=$'\t' read -r -d '' dest rel; do
|
|
if [[ $dest != "$HOST_DIR"/* ]] && (( ! CAPTURE_SHARED )); then skip "/etc/$rel (shared; not on $SHARED_HOST)"; continue; fi
|
|
pull_back "/etc/$rel" "$dest" "/etc/$rel"
|
|
done < <(layered system)
|
|
}
|
|
|
|
# Report drift in the package delta without rewriting the hand-annotated file:
|
|
# packages.txt carries a "why" comment per line that a regenerated list would lose.
|
|
step_packages() {
|
|
head_ "Package delta (report only)"
|
|
local listed; listed=$(mktemp)
|
|
sed 's/#.*//' "$REPO/packages.txt" | tr -d '[:blank:]' | grep -v '^$' | grep -v '^\[' | sort -u > "$listed"
|
|
|
|
local baseline; baseline=$(mktemp)
|
|
{
|
|
pacman -Qi omarchy 2>/dev/null | awk '/^Depends On/{sub(/^Depends On *: /,"");print}' \
|
|
| tr ' ' '\n' | sed 's/[<>=].*//'
|
|
cat /usr/share/omarchy/install/omarchy-base.packages 2>/dev/null
|
|
} | grep -v '^$' | sort -u > "$baseline"
|
|
|
|
local extra; extra=$(mktemp)
|
|
pacman -Qqe 2>/dev/null | sort -u | comm -23 - "$baseline" > "$extra"
|
|
|
|
local untracked; untracked=$(comm -23 "$extra" "$listed" | grep -vxE 'base|base-devel|sudo|omarchy|mkinitcpio|linux-firmware|efibootmgr|devtools|cmake|ninja|wayvnc|sunshine|freerdp|remmina|omarchy-remote-desktop-backend|pipewire-debug' | tr '\n' ' ')
|
|
local removed; removed=$(comm -13 "$extra" "$listed" | tr '\n' ' ')
|
|
|
|
[[ -n ${untracked// } ]] && warn "installed but not in packages.txt: $untracked" || skip "nothing installed that packages.txt misses"
|
|
[[ -n ${removed// } ]] && warn "in packages.txt but not installed: $removed"
|
|
say " packages.txt keeps a 'why' comment per package, so edit it by hand."
|
|
rm -f "$listed" "$baseline" "$extra"
|
|
}
|
|
|
|
main() {
|
|
local want=()
|
|
for a in "$@"; do
|
|
case $a in
|
|
--list|-l) printf '%s\n' "${STEPS[@]}"; exit 0 ;;
|
|
-h|--help) sed -n '2,13p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
-*) warn "unknown option: $a"; exit 2 ;;
|
|
*) if [[ " ${STEPS[*]} " == *" $a "* ]]; then want+=("$a"); else warn "unknown step: $a"; exit 2; fi ;;
|
|
esac
|
|
done
|
|
(( ${#want[@]} )) || want=("${STEPS[@]}")
|
|
|
|
say "${c_hd}capture${c_off} repo=$REPO host=$(hostname)"
|
|
for s in "${want[@]}"; do "step_$s"; done
|
|
|
|
head_ "Done"
|
|
say " Review with: git -C $REPO diff"
|
|
}
|
|
|
|
main "$@"
|