# shellcheck shell=bash disable=SC1090 # Profile resolution, shared by install.sh and capture.sh. Sourced, not run. # # A profile is a directory under profiles/ with a `profile` file (bash, sourced) # and any of: config/ (mirrors ~/.config), themes/, plugins.txt, # plugin-patches/, bin/, systemd/user/, system/ (mirrors /etc), handoff/, # packages.txt, setup.sh. `include=(...)` names the layers it builds on. # # Resolving a profile gives an ordered list of layers: each layer's includes # first (depth first, each layer once), then any --roles, then the profile # itself. For a path that several layers provide, the LAST layer wins, so a # machine always overrides the roles and shared layers under it. # # Expects REPO to be set, and err/warn helpers from the caller. PROFILES=$REPO/profiles STATE_FILE=$HOME/.config/omarchy-setup/profile LAYERS=() # resolved layer paths relative to profiles/, in order PROFILE="" # the profile asked for, e.g. machines/hp-laptop ROLES=() # extra roles asked for, e.g. devops ai-agents _resolving=() # find_layer -> layer path relative to profiles/. Accepts the full path # (machines/hp-laptop) or a bare name looked up in machines/, roles/, features/. find_layer() { local c for c in "$1" "machines/$1" "roles/$1" "features/$1"; do [[ -f $PROFILES/$c/profile ]] && { printf '%s\n' "$c"; return 0; } done return 1 } layer_includes() { ( include=(); source "$PROFILES/$1/profile"; printf '%s\n' "${include[@]}" ) } layer_description() { ( description=""; source "$PROFILES/$1/profile"; printf '%s\n' "$description" ) } _add_layer() { local rel l inc rel=$(find_layer "$1") || { err "no such layer: $1"; return 1; } for l in "${LAYERS[@]}"; do [[ $l == "$rel" ]] && return 0; done [[ " ${_resolving[*]} " == *" $rel "* ]] && { err "include cycle through $rel"; return 1; } _resolving+=("$rel") while IFS= read -r inc; do [[ -n $inc ]] || continue _add_layer "$inc" || return 1 done < <(layer_includes "$rel") LAYERS+=("$rel") } # resolve [role...] -> fills LAYERS resolve() { local rel inc r LAYERS=(); _resolving=() rel=$(find_layer "$1") || { err "no such profile: $1 (./install.sh --profiles lists them)"; return 1; } PROFILE=$rel _resolving+=("$rel") while IFS= read -r inc; do [[ -n $inc ]] || continue _add_layer "$inc" || return 1 done < <(layer_includes "$rel") for r in "${@:2}"; do _add_layer "roles/${r#roles/}" || return 1; done LAYERS+=("$rel") } # Settings from the layers' profile files. Arrays accumulate across layers; # handoff_peer_ips and arch come from the last layer that sets them. P_ARCH=""; P_HOSTNAMES=(); P_HANDOFF_IPS=(); P_DOTFILES=(); P_AETHER=() load_settings() { local l P_ARCH=""; P_HOSTNAMES=(); P_HANDOFF_IPS=(); P_DOTFILES=(); P_AETHER=() for l in "${LAYERS[@]}"; do local arch="" hostnames=() handoff_peer_ips=() dotfiles=() aether_themes=() include=() description="" source "$PROFILES/$l/profile" [[ -n $arch ]] && P_ARCH=$arch (( ${#hostnames[@]} )) && P_HOSTNAMES=("${hostnames[@]}") (( ${#handoff_peer_ips[@]} )) && P_HANDOFF_IPS=("${handoff_peer_ips[@]}") P_DOTFILES+=("${dotfiles[@]}") P_AETHER+=("${aether_themes[@]}") done } # Which profile to use when none is given: the one recorded by the last # install on this machine, else the machine profile whose hostnames= matches. # Prints "profilerole,role". default_profile() { if [[ -f $STATE_FILE ]]; then local profile="" roles="" source "$STATE_FILE" [[ -n $profile ]] && { printf '%s\t%s\n' "$profile" "$roles"; return 0; } fi local f host; host=$(hostname 2>/dev/null || cat /etc/hostname) for f in "$PROFILES"/machines/*/profile; do if ( hostnames=(); source "$f"; [[ " ${hostnames[*]} " == *" $host "* ]] ); then f=${f%/profile}; printf '%s\t\n' "${f#"$PROFILES/"}"; return 0 fi done return 1 } save_state() { mkdir -p "${STATE_FILE%/*}" local IFS=, printf '# Written by omarchy-setup/install.sh; read when no --profile is given.\nprofile=%q\nroles=%q\n' \ "$PROFILE" "${ROLES[*]}" > "$STATE_FILE" } # owners -> "relpathlayer" for every file under / # across the layers, the last layer winning each relpath. Sorted by relpath. owners() { local sub=$1 l f declare -A own=() for l in "${LAYERS[@]}"; do [[ -d $PROFILES/$l/$sub ]] || continue while IFS= read -r -d '' f; do own[${f#"$PROFILES/$l/$sub/"}]=$l done < <(find "$PROFILES/$l/$sub" -type f -print0) done for f in "${!own[@]}"; do printf '%s\t%s\n' "$f" "${own[$f]}"; done | sort } # Every layer that has (a file or directory), in order. layers_with() { local l for l in "${LAYERS[@]}"; do [[ -e $PROFILES/$l/$1 ]] && printf '%s\n' "$l"; done } # Parse every layer's packages.txt into PKG_
arrays of # "namearchscmdslayer", US being $PKG_FS. Not a tab: tab is IFS # whitespace, so `read` would collapse an empty archs field and shift the rest. # [ignore] lines may hold several names. PKG_FS=$'\x1f' PKG_repo=(); PKG_aur=(); PKG_optional=(); PKG_ignore=() load_packages() { local l line section tok name archs cmds PKG_repo=(); PKG_aur=(); PKG_optional=(); PKG_ignore=() declare -A seen=() for l in "${LAYERS[@]}"; do [[ -f $PROFILES/$l/packages.txt ]] || continue section="" while IFS= read -r line; do line=${line%%#*} read -ra tok <<<"$line" (( ${#tok[@]} )) || continue case ${tok[0]} in '[repo]'|'[aur]'|'[optional]'|'[ignore]') section=${tok[0]//[\[\]]/}; continue ;; esac [[ -n $section ]] || continue if [[ $section == ignore ]]; then for name in "${tok[@]}"; do PKG_ignore+=("$name$PKG_FS$PKG_FS$PKG_FS$l"); done continue fi name=${tok[0]}; archs=""; cmds="" for t in "${tok[@]:1}"; do case $t in cmd=*) cmds=${t#cmd=} ;; x86_64|aarch64) archs+="$t," ;; *) warn "$l/packages.txt: unknown token '$t' on $name" ;; esac done [[ -n ${seen[$name]:-} ]] && continue seen[$name]=1 local -n arr="PKG_$section" arr+=("$name$PKG_FS${archs%,}$PKG_FS$cmds$PKG_FS$l") unset -n arr done < "$PROFILES/$l/packages.txt" done } # pkg_applies -> true when this machine's arch is in the list (or no list) pkg_applies() { [[ -z $1 || ",$1," == *",$(uname -m),"* ]]; } # pkg_cmd_present -> true when any of the comma-separated commands exists pkg_cmd_present() { local c [[ -n $1 ]] || return 1 for c in ${1//,/ }; do command -v "$c" >/dev/null 2>&1 && return 0; done return 1 }