diff --git a/CloverPackage/CloverLogoutHook/CloverLogoutHook.command b/CloverPackage/CloverLogoutHook/CloverLogoutHook.command new file mode 100755 index 0000000000..e411fa8530 --- /dev/null +++ b/CloverPackage/CloverLogoutHook/CloverLogoutHook.command @@ -0,0 +1,1172 @@ +#!/bin/bash +# ===================================================================== +# CloverLogoutHook +# Persistent NVRAM for Clover Legacy Boot. +# +# WHAT IT DOES +# • Keeps /nvram.plist always in sync with live NVRAM. +# • Sticky anchor (sticky.xml): keys macOS consumes after boot +# (efi-boot-device*) are NEVER lost — they travel to every boot. +# • LIVE-WINS merge (PlistBuddy): fresh Startup Disk values always +# overwrite old ones; the anchor only re-adds consumed keys. +# • Boot-args control: manage boot-args from the terminal without +# touching config.plist (setargs command). Stored as native EFI +# binary form (), the form the kernel consumes. +# • Self-heal: Clover file rebuilt at daemon start + verified every +# minute against the anchor. +# • Invisible: transient hidden mount (nobrowse), released after +# use; user mounts are reused, never stolen. +# • Housekeeping: log rotation, tmp cleanup, 'clean' command. +# +# NEW IN v1.6.1 +# • Fresh rig with no Startup Disk click yet -> create +# a minimal anchor; setargs itself provides the first heal key. +# +# NEW IN v1.6 +# • Native Foundation data codec for boot-args: every encode/decode +# is performed by macOS itself (no manual base64/hex chains, +# no PlistBuddy data quirks, no perl). +# • Round-trip verification before any Clover-file write of +# boot-args (encode -> store -> read back -> compare). +# • Clover-only: built by and for the Clover Legacy community. +# +# FROM v1.4/v1.5 +# • EXPERIMENT-PROVEN: Clover MERGES config arguments with the +# NVRAM-file arguments at boot — the file can carry flags the +# config does not have. +# • HFS+ boot support (daemon runs on Mavericks-era systems). +# • Multi-disk internal discovery (boot disk first). +# • /Library/Logs/CloverHook state folder, full uninstall cleanup. +# +# FROM v1.3 +# • Multi-disk internal discovery (boot disk first). +# • From v1.2: /Library/Logs/CloverHook · full uninstall cleanup. +# +# FROM v1 +# • Runs on APFS-only systems (Catalina to Ventura) +# ═══════════════════════════════════════════════════════════════════════ +# +# LAYOUT OF THIS FILE +# §1 Configuration §7 Core sync (sync_once) +# §2 Output & logging §8 Clover file verification +# §3 Generic helpers §9 Housekeeping +# §4 ESP/Clover resolution §10 Daemon (watch / install / remove) +# §5 Diagnostics §11 Maintenance (clean / setargs / prune) +# §6 Boot-args data codec §12 Command dispatcher +# ===================================================================== + +VERSION="1.6.1" +set -uo pipefail + + +# ──────────────────────────────────────────────────────────────────────── +# §1 CONFIGURATION +# ──────────────────────────────────────────────────────────────────────── + +# --- Daemon identity ---------------------------------------------------- +DAEMON_LABEL="com.clover.nvramhook.daemon" +INSTALL_BIN="/usr/local/bin/clover-logout-hook" +DAEMON_PLIST="/Library/LaunchDaemons/${DAEMON_LABEL}.plist" + +# --- Timing ------------------------------------------------------------- +POLL_INTERVAL="${CLOVER_POLL_INTERVAL:-2}" # cheap check cadence (sec) +VERIFY_EVERY=30 # verify+rotate every N cycles + +# --- Paths -------------------------------------------------------------- +LOG_DIR="/Library/Logs/CloverHook" +LOG_FILE="$LOG_DIR/nvramhook.log" +ESP_CACHE="$LOG_DIR/last-esp.txt" # cached Clover-volume identifier +LAST_XML="$LOG_DIR/last.xml" # raw live baseline (change detect) +STICKY_XML="$LOG_DIR/sticky.xml" # anchor: survives macOS consumption +HIDDEN_MP="/private/var/.CloverEFI" # transient mount point (invisible) + +# --- Boot-selection keys kept alive across boots ------------------------- +HEAL_KEYS=( + "efi-boot-device" + "efi-boot-device-data" + "efi-apple-recovery" + "boot-args" # v1.5: sticky-only key +) + +# --- Keys EXCLUDED from live-wins merge (anchor is the sole owner) ------- +# boot-args: Clover reads them from NVRAM after importing the file, +# so the anchor must be the single source of truth. +MERGE_EXCLUDE=( + "boot-args" +) + +# --- Internal state ----------------------------------------------------- +ESPID="" # current Clover-volume identifier +HOST_DISK="" # physical disk hosting the OS +MP="" # current volume mount point +OWN_MOUNT=0 # 1 = we created this mount + + +# ──────────────────────────────────────────────────────────────────────── +# §2 OUTPUT & LOGGING +# ──────────────────────────────────────────────────────────────────────── + +setup_log_dir() { + if [[ -d "/Library/Logs/CloverHook" ]]; then + LOG_DIR="/Library/Logs/CloverHook" + elif mkdir -p "/Library/Logs/CloverHook" 2>/dev/null \ + && [[ -w "/Library/Logs/CloverHook" ]]; then + LOG_DIR="/Library/Logs/CloverHook" + else + LOG_DIR="/tmp/clover-nvramhook" + mkdir -p "$LOG_DIR" 2>/dev/null + fi +} + +log() { echo "$(date '+%Y-%m-%d %H:%M:%S') [v${VERSION}] $*" >> "$LOG_FILE" 2>/dev/null; } +dbg() { log "DEBUG: $*"; } +die() { echo "--> ERROR: $*" >&2; log "ERROR: $*"; exit 1; } +info() { echo "--> INFO: $*"; } +ok() { echo "--> OK: $*"; } +warn() { echo "--> WARN: $*"; } + +banner() { + echo "CloverLogoutHook v${VERSION}" +} + + +# ──────────────────────────────────────────────────────────────────────── +# §3 GENERIC HELPERS +# ──────────────────────────────────────────────────────────────────────── + +need_root() { + [[ $(id -u) -eq 0 ]] || die "This action needs sudo: sudo $0 ${1:-}" +} + +is_mounted_at() { + mount | grep -qF " on $1 " +} + +# Prints the mount point of a partition identifier (space-safe), or fails. +mounted_mp_of_espid() { + local mp + [[ -n "$ESPID" ]] || return 1 + mp="$(diskutil info "$ESPID" 2>/dev/null \ + | awk -F': *' '/^ *Mount Point:/ {print $2; exit}')" + if [[ "$mp" == /* ]]; then + printf '%s\n' "$mp" + return 0 + fi + return 1 +} + + +# ──────────────────────────────────────────────────────────────────────── +# §4 ESP / CLOVER VOLUME RESOLUTION +# +# APFS path: "/" -> snapshot -> container -> physical store -> disk +# HFS path: "/" -> the HFS disk itself +# +# Then: EFI-typed partition (boot disk) -> internal HFS volumes +# hosting EFI/CLOVER (multi-disk, boot disk first). +# ──────────────────────────────────────────────────────────────────────── + +get_root_slice() { + mount | awk '$3=="/" {sub("^/dev/","",$1); print $1; exit}' +} + +get_disk_prefix() { # "disk5s5s1" -> "disk5" + [[ $1 =~ ^(disk[0-9]+) ]] && echo "${BASH_REMATCH[1]}" +} + +get_physical_store() { # APFS only: "disk5" -> "disk3s2" + diskutil list "$1" 2>/dev/null \ + | awk '/Physical Store[ \t]/ {print $NF; exit}' +} + +get_efi_partition() { # "disk3" -> "disk3s1" (or "") + diskutil list "$1" 2>/dev/null \ + | awk '/^[ \t]*[0-9]+:/ && /EFI/ {print $NF; exit}' +} + +# probe_clover_volume +# Succeeds if the volume hosts an EFI/CLOVER folder. +# On APFS boots: the live root is a sealed snapshot - skip its volume. +# On HFS boots: the live root is writable and MAY host EFI/CLOVER. +probe_clover_volume() { + local part="$1" + local root_slice mp own=0 found=0 + + root_slice="$(get_root_slice)" + + if [[ $root_slice =~ ^disk[0-9]+s[0-9]+s[0-9]+$ ]]; then + local root_vol="" + [[ $root_slice =~ ^(disk[0-9]+s[0-9]+) ]] && root_vol="${BASH_REMATCH[1]}" + if [[ "$part" == "$root_vol" ]]; then + dbg "probe: skip sealed APFS system volume $part" + return 1 + fi + fi + + mp="$(diskutil info "$part" 2>/dev/null \ + | awk -F': *' '/^ *Mount Point:/ {print $2; exit}')" + if [[ "$mp" == /* ]]; then + [[ -d "$mp/EFI/CLOVER" ]] && return 0 + return 1 + fi + + # transient hidden mount for the probe + mkdir -p "$HIDDEN_MP" 2>/dev/null + if ! diskutil mount -mountOptions nobrowse -mountPoint "$HIDDEN_MP" "$part" >/dev/null 2>&1 \ + && ! diskutil mount -mountPoint "$HIDDEN_MP" "$part" >/dev/null 2>&1; then + dbg "probe: cannot mount $part" + return 1 + fi + own=1 + mp="$HIDDEN_MP" + + [[ -d "$mp/EFI/CLOVER" ]] && found=1 + + if (( own )); then + diskutil unmount "$mp" >/dev/null 2>&1 \ + || diskutil unmount force "$mp" >/dev/null 2>&1 + fi + + (( found )) && return 0 || return 1 +} + +# discover_clover_volume [host-whole-disk] +# Scans ALL INTERNAL disks for a volume hosting EFI/CLOVER, +# boot disk first (its result wins). External/USB disks excluded. +discover_clover_volume() { + local host_disk="${1:-}" + local dev part + + dbg "discover: scanning INTERNAL disks for EFI/CLOVER volumes…" + + local -a candidates=() + [[ -n "$host_disk" ]] && candidates+=("$host_disk") + + while read -r dev; do + [[ "$dev" == "$host_disk" ]] && continue + if diskutil info "$dev" 2>/dev/null | grep -q "Internal.*Yes"; then + candidates+=("$dev") + fi + done < <(diskutil list 2>/dev/null \ + | awk '/^\/dev\/disk[0-9]+ \(/ {gsub("/dev/","",$1); gsub(":","",$1); print $1}') + + (( ${#candidates[@]} > 0 )) || return 1 + + local candidate + for candidate in "${candidates[@]}"; do + for part in $(diskutil list "$candidate" 2>/dev/null \ + | awk '/Apple_HFS/ {print $NF}'); do + dbg "discover: probing $part (disk $candidate)…" + if probe_clover_volume "$part"; then + ESPID="$part" + printf '%s\n' "$ESPID" > "$ESP_CACHE" 2>/dev/null + log "discover: Clover volume found at $part (HFS+, disk $candidate)" + return 0 + fi + done + done + + dbg "discover: no internal HFS+ volume hosts EFI/CLOVER" + return 1 +} + +resolve_boot_esp_id() { + local slice container store whole espid + + slice="$(get_root_slice)" + dbg "resolve: root slice = ${slice:-MISSING}" + [[ -n "$slice" ]] || return 1 + + container="$(get_disk_prefix "$slice")" + dbg "resolve: boot disk = ${container:-MISSING}" + [[ -n "$container" ]] || return 1 + + store="$(get_physical_store "$container")" + + if [[ "$store" =~ ^disk[0-9]+s[0-9]+$ ]]; then + # APFS boot: physical store -> real host disk + whole="$(get_disk_prefix "$store")" + dbg "resolve: apfs store = $store" + dbg "resolve: host whole = ${whole:-MISSING}" + else + # HFS boot: no APFS layer, the boot disk hosts the root + whole="$container" + dbg "resolve: HFS boot (no APFS store) - host whole = $whole" + fi + [[ -n "$whole" ]] || return 1 + HOST_DISK="$whole" + + # Priority 1 - EFI-typed partition on the boot disk (GPT ESP) + espid="$(get_efi_partition "$whole")" + if [[ "$espid" =~ ^disk[0-9]+s[0-9]+$ ]]; then + ESPID="$espid" + printf '%s\n' "$ESPID" > "$ESP_CACHE" 2>/dev/null + dbg "resolve: clover volume (EFI-typed) = $espid" + return 0 + fi + + # Priority 2 - internal HFS+ volume hosting EFI/CLOVER (any disk) + dbg "resolve: no EFI-typed partition on boot disk - multi-disk scan…" + discover_clover_volume "$whole" +} + + +# ──────────────────────────────────────────────────────────────────────── +# §5 DIAGNOSTICS (diagnose / status) +# ──────────────────────────────────────────────────────────────────────── + +cmd_diagnose() { + local slice container store whole espid mp key + + slice="$(get_root_slice)" + container="$(get_disk_prefix "$slice")" + store="$(get_physical_store "$container")" + whole="$(get_disk_prefix "$store")" + [[ -z "$whole" ]] && whole="$container" + espid="$(get_efi_partition "$whole")" + + banner + echo "root slice : ${slice:-NOT FOUND}" + echo "apfs store : ${store:-none (HFS boot)}" + echo "host whole disk : ${whole:-NOT FOUND}" + echo "esp identifier : ${espid:-NOT FOUND}" + + if [[ -z "$espid" && -n "$whole" ]]; then + echo "esp fallback : multi-disk HFS+ scan…" + if discover_clover_volume "$whole"; then + echo "clover volume : ${ESPID} (HFS+)" + else + echo "clover volume : NOT FOUND" + fi + fi + + if [[ -n "$espid" || -n "$ESPID" ]]; then + [[ -n "$espid" ]] && ESPID="$espid" + if mp="$(mounted_mp_of_espid)"; then + echo "current mount : $mp" + else + echo "current mount : none (idle)" + fi + fi + + echo "poll interval : ${POLL_INTERVAL}s (verify every $((VERIFY_EVERY * POLL_INTERVAL))s)" + echo "last baseline : $(grep -c '' "$LAST_XML" 2>/dev/null || echo none)" + echo "sticky anchor : $(grep -c '' "$STICKY_XML" 2>/dev/null || echo none)" + + for key in "${HEAL_KEYS[@]}"; do + if grep -q "${key}" "$STICKY_XML" 2>/dev/null; then + echo "anchor has : ${key} [OK]" + else + echo "anchor missing : ${key} [--]" + fi + done + + # current sticky boot-args (decoded), if any + if [[ -f "$STICKY_XML" ]]; then + local bavalue + bavalue=$(read_bootargs_text "$STICKY_XML") + if [[ -n "$bavalue" && "$bavalue" != ERR:* ]]; then + echo "sticky boot-args : ${bavalue} [data/binary form]" + elif [[ "$bavalue" == ERR:* ]]; then + echo "sticky boot-args : (read error: ${bavalue})" + else + echo "sticky boot-args : (not set in anchor)" + fi + fi + + echo "log dir : $LOG_DIR" +} + +cmd_status() { + banner + + if launchctl print "system/${DAEMON_LABEL}" >/dev/null 2>&1; then + ok "Daemon RUNNING" + elif launchctl list 2>/dev/null | grep -q "$DAEMON_LABEL"; then + ok "Daemon RUNNING (legacy launchctl)" + elif [[ -f "$DAEMON_PLIST" ]]; then + warn "Daemon installed but NOT loaded" + else + info "Daemon: not installed" + fi + + ESPID="$(cat "$ESP_CACHE" 2>/dev/null || true)" + if [[ -n "$ESPID" ]]; then + if MP="$(mounted_mp_of_espid)"; then + info "Clover volume ${ESPID} mounted at: ${MP}" + else + info "Clover volume ${ESPID} unmounted (normal idle state)" + fi + fi + + [[ -f "$LAST_XML" ]] && stat -f "baseline : %N (%z bytes, %Sm)" "$LAST_XML" + [[ -f "$STICKY_XML" ]] && stat -f "sticky anchor : %N (%z bytes, %Sm)" "$STICKY_XML" + [[ -f "$LOG_FILE" ]] && stat -f "log : %N (%z bytes)" "$LOG_FILE" +} + + +# ──────────────────────────────────────────────────────────────────────── +# §6 BOOT-ARGS DATA CODEC (Foundation native - macOS does the work) +# The JXA helper is the shell equivalent of Swift's +# let data = Data(someString.utf8) +# Every encode/decode is performed by Foundation natively. +# ──────────────────────────────────────────────────────────────────────── + +BA_JS="$LOG_DIR/.clh-ba.js" + +write_ba_js() { + cat > "$BA_JS" <<'JSEOF' +ObjC.import('Foundation'); +// argv: [plistPath, key, mode, text?] +// mode "write": store text as NSData (UTF-8) under key +// mode "read" : return the text decoded from the data under key +// mode "b64" : return the base64 of the data under key +function run(argv) { + var path = argv[0], key = argv[1], mode = argv[2]; + + if (mode === 'write') { + var text = argv[3]; + var d = $.NSMutableDictionary.dictionaryWithContentsOfFile(path); + if (d.isNil()) return 'ERR:read'; + var ns = $.NSString.alloc.initWithUTF8String(text); + var data = ns.dataUsingEncoding($.NSUTF8StringEncoding); + if (data.isNil()) return 'ERR:encode'; + d.setObjectForKey(data, key); + if (!d.writeToFileAtomically(path, true)) return 'ERR:write'; + return 'OK'; + } + + var d2 = $.NSDictionary.dictionaryWithContentsOfFile(path); + if (d2.isNil()) return 'ERR:read'; + var data = d2.objectForKey(key); + if (data === null || data.isNil()) return 'NONE'; + + if (mode === 'read') { + var s = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding); + var out = ObjC.unwrap(s); + return (out === null || out === undefined) ? '' : String(out); + } + + if (mode === 'b64') { + var b64 = data.base64EncodedStringWithOptions(0); + return String(ObjC.unwrap(b64)); + } + + return 'ERR:mode'; +} +JSEOF +} + +# write_bootargs_data +# Stores the text as native (UTF-8 bytes) in the anchor. +# Foundation handles the encoding; round-trip verified before return. +write_bootargs_data() { + local new_args="$1" + write_ba_js + + local msg + msg="$(osascript -l JavaScript "$BA_JS" "$STICKY_XML" boot-args write "$new_args" 2>/dev/null)" + if [[ "$msg" != "OK" ]]; then + warn "boot-args write failed: ${msg:-no output}" + return 1 + fi + + # keep the anchor in XML form (safe for every reader, Clover included) + plutil -convert xml1 "$STICKY_XML" >/dev/null 2>&1 + + # round-trip verification - the gatekeeper + local check + check=$(read_bootargs_text "$STICKY_XML") + if [[ "$check" == "$new_args" ]]; then + dbg "boot-args round-trip verified" + return 0 + fi + warn "boot-args round-trip MISMATCH: stored='${check}'" + return 1 +} + +# read_bootargs_text [plist] -> decoded text of boot-args ("" if absent) +read_bootargs_text() { + write_ba_js + osascript -l JavaScript "$BA_JS" "${1:-$STICKY_XML}" boot-args read 2>/dev/null +} + +# read_bootargs_b64 [plist] -> base64 of the boot-args data bytes +read_bootargs_b64() { + write_ba_js + osascript -l JavaScript "$BA_JS" "${1:-$STICKY_XML}" boot-args b64 2>/dev/null +} + +hex_to_percent() { + printf '%s' "$1" | LC_ALL=C sed -E 's/(..)/%\1/g' +} + + +# ──────────────────────────────────────────────────────────────────────── +# §6b LIVE-NVRAM HEALING +# Restores heal keys MISSING from live NVRAM, reading the anchor. +# Note: on modern macOS, runtime writes to boot-governed keys are +# refused - expected; persistence travels through nvram.plist. +# ──────────────────────────────────────────────────────────────────────── + +heal_live_vars() { + local quiet="${1:-0}" + local source_file key tag value hex percent + + (( ${#HEAL_KEYS[@]} )) || return 0 + + source_file="$STICKY_XML" + [[ -f "$source_file" ]] || source_file="$LAST_XML" + [[ -f "$source_file" ]] || return 0 + + local restored=0 refused=0 + + for key in "${HEAL_KEYS[@]}"; do + # Present in live NVRAM -> leave untouched (fresh user choice wins). + if nvram "$key" >/dev/null 2>&1; then + continue + fi + + # v1.5: boot-args go through the Foundation codec + if [[ "$key" == "boot-args" ]]; then + local b64 + b64=$(read_bootargs_b64 "$source_file") + if [[ -z "$b64" || "$b64" == "NONE" || "$b64" == ERR:* ]]; then + continue + fi + hex=$(printf '%s' "$b64" \ + | openssl base64 -d -A 2>/dev/null \ + | LC_ALL=C od -An -tx1 | LC_ALL=C tr -d ' \n') + [[ -z "$hex" ]] && continue + percent=$(hex_to_percent "$hex") + if nvram "${key}=${percent}" 2>/dev/null; then + restored=1 + log "heal: REINJECTED ${key} (data/binary)" + else + refused=1 + fi + continue + fi + + tag=$(grep -A1 "${key}" "$source_file" 2>/dev/null \ + | sed -n '2p' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + case "$tag" in + ""*) + value=$(awk -v key="$key" ' + index($0, "" key "") > 0 { grab=1; next } + grab==1 && // { grab=2; next } + grab==2 && /<\/data>/ { + sub(/<\/data>.*/, "") + gsub(/[ \t\r]/, "") + printf "%s", $0 + exit + } + grab==2 { gsub(/[ \t\r]/, ""); printf "%s", $0 } + ' "$source_file" 2>/dev/null) + [[ -z "$value" ]] && continue + hex=$(printf '%s' "$value" \ + | openssl base64 -d -A 2>/dev/null \ + | LC_ALL=C od -An -tx1 | LC_ALL=C tr -d ' \n') + [[ -z "$hex" ]] && continue + percent=$(hex_to_percent "$hex") + if nvram "${key}=${percent}" 2>/dev/null; then + restored=1 + log "heal: REINJECTED ${key} (data/binary)" + else + refused=1 + fi + ;; + ""*) + value=$(/usr/libexec/PlistBuddy -c "Print :$key" "$source_file" 2>/dev/null) + [[ -z "$value" ]] && continue + if nvram "${key}=${value}" 2>/dev/null; then + restored=1 + log "heal: REINJECTED ${key} (string)" + else + refused=1 + fi + ;; + esac + done + + if (( restored )); then + (( quiet )) || ok "Live NVRAM healed" + log "heal applied" + elif (( refused )); then + log "heal: live-writes refused by macOS (expected) - file covers it" + fi + return 0 +} + + +# ──────────────────────────────────────────────────────────────────────── +# §7 CORE SYNC (sync_once) +# dump live NVRAM -> live-wins merge with anchor -> write Clover file +# +# usage: sync_once [quiet] [merge|prune] [force] +# ──────────────────────────────────────────────────────────────────────── + +mount_esp() { + OWN_MOUNT=0 + + if MP="$(mounted_mp_of_espid)"; then # reuse an existing mount + dbg "mount: reusing $MP" + return 0 + fi + + mkdir -p "$HIDDEN_MP" 2>/dev/null + if diskutil mount -mountOptions nobrowse -mountPoint "$HIDDEN_MP" "$ESPID" >/dev/null 2>&1 \ + || diskutil mount -mountPoint "$HIDDEN_MP" "$ESPID" >/dev/null 2>&1; then + MP="$HIDDEN_MP" + OWN_MOUNT=1 + dbg "mount: transient hidden mount at $MP" + return 0 + fi + + warn "mount: cannot mount ${ESPID} this cycle" + return 1 +} + +release_esp() { + if (( OWN_MOUNT )); then + diskutil unmount "$MP" >/dev/null 2>&1 \ + || diskutil unmount force "$MP" >/dev/null 2>&1 + dbg "mount: transient mount released" + fi +} + +# v1.5.1 - ANCHOR RECOVERY +# If the local anchor is missing (fresh install, uninstall, clean) +# but the Clover volume still holds a nvram.plist containing heal +# keys, restore the anchor from it. This recovers boot-device keys +# AND boot-args from previous runs without any manual step. +seed_anchor_from_esp() { + [[ -f "$STICKY_XML" ]] && return 0 + + resolve_boot_esp_id || return 1 + + local mp="" own=0 target + mp="$(mounted_mp_of_espid || true)" + + if [[ -z "$mp" ]]; then + mkdir -p "$HIDDEN_MP" 2>/dev/null + if ! diskutil mount -mountOptions nobrowse -mountPoint "$HIDDEN_MP" "$ESPID" >/dev/null 2>&1 \ + && ! diskutil mount -mountPoint "$HIDDEN_MP" "$ESPID" >/dev/null 2>&1; then + dbg "seed: cannot mount ${ESPID} for anchor recovery" + return 1 + fi + mp="$HIDDEN_MP" + own=1 + fi + + target="${mp}/nvram.plist" + local recovered=0 + + if [[ -f "$target" ]]; then + local key + for key in "${HEAL_KEYS[@]}"; do + if grep -q "${key}" "$target" 2>/dev/null; then + cp "$target" "$STICKY_XML" 2>/dev/null + recovered=1 + log "seed: anchor RECOVERED from existing ESP nvram.plist" + break + fi + done + fi + + if (( own )); then + diskutil unmount "$mp" >/dev/null 2>&1 \ + || diskutil unmount force "$mp" >/dev/null 2>&1 + fi + + if (( recovered )); then + ok "Anchor recovered from existing Clover file (boot keys preserved)" + else + dbg "seed: no valid anchor source on ESP" + fi + return 0 +} + +# v1.5 - migrate legacy boot-args to native (once) +migrate_bootargs_to_data() { + [[ -f "$STICKY_XML" ]] || return 0 + + local tag + tag=$(grep -A1 'boot-args' "$STICKY_XML" 2>/dev/null \ + | sed -n '2p' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + [[ "$tag" == ""* ]] || return 0 # already data / absent + + local text + text=$(/usr/libexec/PlistBuddy -c "Print :boot-args" "$STICKY_XML" 2>/dev/null) + [[ -z "$text" ]] && return 0 + + if write_bootargs_data "$text"; then + log "migrate: boot-args converted string -> data (binary form)" + ok "boot-args migrated to binary form" + fi +} + +sync_once() { + local quiet="${1:-0}" + local mode="${2:-merge}" + local force="${3:-0}" + + resolve_boot_esp_id || return 1 + + # v1.5.1: recover the anchor from the ESP if it is missing + if [[ ! -f "$STICKY_XML" ]]; then + seed_anchor_from_esp + fi + + # v1.5.2: one-time migration of legacy string boot-args + migrate_bootargs_to_data + + local tmp="$LOG_DIR/nvram.new.xml" + if ! nvram -x -p > "$tmp" 2>/dev/null; then + rm -f "$tmp"; return 1 + fi + [[ -s "$tmp" ]] || { rm -f "$tmp"; return 1; } + grep -q '' "$tmp" || { rm -f "$tmp"; return 1; } + plutil -lint "$tmp" >/dev/null 2>&1 \ + || { rm -f "$tmp"; return 1; } + + local total + total=$(grep -c '' "$tmp" 2>/dev/null) + (( total > 0 )) || { rm -f "$tmp"; return 1; } + + if [[ "$force" != "1" && -f "$LAST_XML" ]] && cmp -s "$tmp" "$LAST_XML"; then + if [[ ! -f "$STICKY_XML" ]]; then + # v1.5.1: never report "all good" while the anchor is missing + dbg "no-change (${total} vars) BUT anchor missing - forcing seed" + force=1 + else + (( quiet )) || info "NVRAM unchanged (${total} vars) - nothing touched." + dbg "no-change (${total} vars)" + rm -f "$tmp" + return 0 + fi + fi + + local final="$LOG_DIR/nvram.final.xml" + local anchor="$STICKY_XML" + [[ -f "$anchor" ]] || anchor="$LAST_XML" + + if [[ "$mode" == "prune" ]]; then + cp "$tmp" "$final" + elif [[ ! -f "$anchor" ]]; then + cp "$tmp" "$final" # very first run + else + cp "$anchor" "$final" + + local live_key ex skip + while IFS= read -r live_key; do + [[ -n "$live_key" ]] || continue + skip=0 + for ex in "${MERGE_EXCLUDE[@]}"; do + if [[ "$live_key" == "$ex" ]]; then + skip=1 + break + fi + done + (( skip )) && continue + /usr/libexec/PlistBuddy -c "Delete :${live_key}" "$final" >/dev/null 2>&1 || true + done < <(grep -o '[^<]*' "$tmp" | sed 's/<[^>]*>//g') + + # stdout+stderr both captured: silences the harmless + # "Duplicate Entry Was Skipped" notices (colon keys like + # prev-lang:kbd cannot be Deleted via PlistBuddy paths) + local merge_out + if ! merge_out=$(/usr/libexec/PlistBuddy -c "Merge $tmp" "$final" 2>&1); then + warn "PlistBuddy merge failed - previous Clover file kept, will retry." + log "merge-fail keep-prev (${merge_out})" + rm -f "$tmp" "$final" + return 0 + fi + fi + + mount_esp || { rm -f "$tmp" "$final"; return 1; } + + # Sanity: the mounted volume should host Clover. + if [[ ! -d "$MP/EFI/CLOVER" ]]; then + warn "Mounted volume ${ESPID} has no EFI/CLOVER - multi-disk search…" + log "clover-search: ${ESPID} lacks EFI/CLOVER" + release_esp + if discover_clover_volume "$HOST_DISK"; then + (( quiet )) || ok "Clover volume located: ${ESPID} (HFS+)" + mount_esp || { rm -f "$tmp" "$final"; return 1; } + fi + if [[ ! -d "$MP/EFI/CLOVER" ]]; then + # Last resort: some builds read nvram.plist from the volume + # root even without a Clover folder there. + warn "No EFI/CLOVER found - writing volume root anyway." + log "clover-search: fallback - writing volume root" + fi + fi + + if ! install -m 644 "$final" "${MP}/nvram.plist" 2>/dev/null; then + warn "write failed on ${MP}/nvram.plist" + rm -f "$tmp" "$final" + release_esp + return 1 + fi + sync + + # ---- update local state ---------------------------------------------- + cp "$tmp" "$LAST_XML" 2>/dev/null + + local key has_heal=0 + for key in "${HEAL_KEYS[@]}"; do + if grep -q "${key}" "$final" 2>/dev/null; then + has_heal=1 + break + fi + done + if (( has_heal )); then + cp "$final" "$STICKY_XML" 2>/dev/null # refresh anchor with NEW values + log "sticky anchor updated" + fi + + # ---- done ------------------------------------------------------------- + local nkeys + nkeys=$(grep -c '' "$final") + (( quiet )) || ok "Persisted ${nkeys} variable(s) -> ${MP}/nvram.plist" + log "sync ${mode} force=${force} (${nkeys} vars) -> ${ESPID}" + + rm -f "$tmp" "$final" + release_esp + return 0 +} + + +# ──────────────────────────────────────────────────────────────────────── +# §8 CLOVER FILE VERIFICATION +# If the on-volume file lost anchor keys (external amputation), +# force a rebuild. Runs every VERIFY_EVERY cycles. +# ──────────────────────────────────────────────────────────────────────── + +verify_esp_file() { + [[ -f "$STICKY_XML" ]] || return 0 + + resolve_boot_esp_id || return 1 + + local key anchor_has_key=0 + for key in "${HEAL_KEYS[@]}"; do + if grep -q "${key}" "$STICKY_XML" 2>/dev/null; then + anchor_has_key=1 + break + fi + done + (( anchor_has_key )) || return 0 + + MP="$(mounted_mp_of_espid || true)" + OWN_MOUNT=0 + + if [[ -z "$MP" ]]; then + mount_esp || return 1 + fi + + local need_rebuild=0 + local target="${MP}/nvram.plist" + + if [[ ! -f "$target" ]]; then + need_rebuild=1 + else + for key in "${HEAL_KEYS[@]}"; do + if grep -q "${key}" "$STICKY_XML" \ + && ! grep -q "${key}" "$target"; then + need_rebuild=1 + break + fi + done + fi + + release_esp + + if (( need_rebuild )); then + log "verify: Clover file lacks anchor keys - forced rebuild" + sync_once 1 merge 1 + else + dbg "verify: Clover file healthy" + fi +} + + +# ──────────────────────────────────────────────────────────────────────── +# §9 HOUSEKEEPING +# ──────────────────────────────────────────────────────────────────────── + +rotate_log() { + [[ -f "$LOG_FILE" ]] || return 0 + local size + size=$(stat -f%z "$LOG_FILE" 2>/dev/null || echo 0) + if (( size >= 409600 )); then # 400 KB cap + tail -c 150000 "$LOG_FILE" > "${LOG_FILE}.tmp" 2>/dev/null \ + && mv "${LOG_FILE}.tmp" "$LOG_FILE" + log "log rotated (${size} -> $(stat -f%z "$LOG_FILE" 2>/dev/null) bytes)" + fi +} + +cleanup_temp_files() { + rm -f "$LOG_DIR/nvram.new.xml" 2>/dev/null + rm -f "$LOG_DIR/nvram.final.xml" 2>/dev/null + rm -f "$LOG_DIR/nvram.fast.xml" 2>/dev/null +} + + +# ──────────────────────────────────────────────────────────────────────── +# §10 DAEMON +# ──────────────────────────────────────────────────────────────────────── + +on_shutdown() { # SIGTERM / SIGINT handler + log "shutdown signal received - final flush…" + if ! sync_once 1 merge >/dev/null 2>&1; then + sleep 1 + sync_once 1 merge >/dev/null 2>&1 # one retry during teardown + fi + heal_live_vars 1 >/dev/null 2>&1 + log "final flush done" + exit 0 +} + +cmd_watch() { + banner + info "Watch: check every ${POLL_INTERVAL}s · live-wins merge · self-heal · flush-on-shutdown." + + trap on_shutdown TERM INT + + cleanup_temp_files + + # Startup self-heal pass: repair any external amputation after boot. + sleep 5 + sync_once 1 merge 1 >/dev/null 2>&1 + + local fast="$LOG_DIR/nvram.fast.xml" + local cycle=0 + + while true; do + cycle=$((cycle + 1)) + + heal_live_vars 1 >/dev/null 2>&1 + + if nvram -x -p > "$fast" 2>/dev/null; then + if ! cmp -s "$fast" "$LAST_XML"; then + sync_once 1 merge >/dev/null 2>&1 || log "hiccup" + fi + fi + rm -f "$fast" 2>/dev/null + + if (( cycle % VERIFY_EVERY == 0 )); then + verify_esp_file >/dev/null 2>&1 || log "verify hiccup" + rotate_log + fi + + sleep "$POLL_INTERVAL" + done +} + +cmd_install() { + banner + mkdir -p /usr/local/bin + install -m 755 "$SELF_PATH" "$INSTALL_BIN" + + # migrate state from the old CloverEFI folder if present + if [[ -d "/Library/Logs/CloverEFI" && ! -d "$LOG_DIR" ]]; then + mv "/Library/Logs/CloverEFI" "$LOG_DIR" + info "Migrated state folder: /Library/Logs/CloverEFI -> ${LOG_DIR}" + fi + + cat > "$DAEMON_PLIST" < + + + + Label + com.clover.nvramhook.daemon + + ProgramArguments + + /usr/local/bin/clover-logout-hook + watch + + + EnvironmentVariables + + CLOVER_POLL_INTERVAL + ${POLL_INTERVAL} + + + RunAtLoad + + + KeepAlive + + + StandardOutPath + ${LOG_DIR}/daemon.out + + StandardErrorPath + ${LOG_DIR}/daemon.err + + +PLIST + + chmod 644 "$DAEMON_PLIST" + + launchctl bootout "system/${DAEMON_LABEL}" 2>/dev/null + launchctl bootstrap system "$DAEMON_PLIST" 2>/dev/null \ + || launchctl load -w "$DAEMON_PLIST" + + ok "Installed v${VERSION} -> ${INSTALL_BIN}" + + # v1.5.1 - GUARANTEED SEED: recover anchor from ESP if needed, + # then force one full sync so anchor + file exist immediately. + info "Seeding anchor and Clover file…" + sync_once 1 merge 1 + + ok "Ready. Logs: ${LOG_DIR}" +} + +cmd_uninstall() { + # stop the daemon (modern + legacy launchctl paths) + launchctl bootout "system/${DAEMON_LABEL}" 2>/dev/null + launchctl unload "$DAEMON_PLIST" 2>/dev/null + launchctl remove "$DAEMON_LABEL" 2>/dev/null + rm -f "$DAEMON_PLIST" + + # release our transient mount, if any + if is_mounted_at "$HIDDEN_MP"; then + diskutil unmount force "$HIDDEN_MP" >/dev/null 2>&1 + fi + rmdir "$HIDDEN_MP" 2>/dev/null + + # remove the installed binary + if [[ -f "$INSTALL_BIN" ]]; then + rm -f "$INSTALL_BIN" + ok "Removed: ${INSTALL_BIN}" + fi + + # remove ONLY the CloverHook folder (never Clover's own dirs) + case "$LOG_DIR" in + /Library/Logs/CloverHook|/tmp/clover-nvramhook) + if [[ -d "$LOG_DIR" ]]; then + rm -rf "$LOG_DIR" + ok "Removed: ${LOG_DIR}" + fi + ;; + esac + + # v1.5.3: also purge any stale /tmp fallback state + rm -rf "/tmp/clover-nvramhook" 2>/dev/null + + ok "CloverLogoutHook fully uninstalled." + + info "Note: the Clover volume's nvram.plist was left in place (harmless)." + info "Your master copy remains in ~/CloverLogoutHook/ (delete it if unwanted)." +} + + +# ──────────────────────────────────────────────────────────────────────── +# §11 MAINTENANCE +# ──────────────────────────────────────────────────────────────────────── + +cmd_clean() { + need_root clean + + cleanup_temp_files + rm -f "$LAST_XML" 2>/dev/null + rm -f "$STICKY_XML" 2>/dev/null + : > "$LOG_FILE" 2>/dev/null || true + [[ -f "${LOG_DIR}/daemon.out" ]] && : > "${LOG_DIR}/daemon.out" + [[ -f "${LOG_DIR}/daemon.err" ]] && : > "${LOG_DIR}/daemon.err" + + ok "Cleaned: logs truncated, tmp files, snapshots and sticky anchor removed." + warn "Anchor is RESET - make ONE Startup Disk click to re-seed it." +} + +cmd_prune() { + need_root prune + sync_once 0 prune || die "prune failed" +} + +# v1.5 - set boot-args from the terminal (anchor becomes source of truth). +# Clover reads boot-args from NVRAM after importing nvram.plist, so the +# value stored here wins at next boot - without touching config.plist. +cmd_setargs() { + need_root setargs + [[ $# -ge 1 ]] || die "usage: sudo $0 setargs \"-v keepsyms=1 ...\"" + local new_args="$*" + + if [[ ! -f "$STICKY_XML" ]]; then + sync_once 1 merge 1 + fi + if [[ ! -f "$STICKY_XML" ]]; then + # v1.6.1: fresh rig with no Startup Disk click yet -> create + # a minimal anchor; setargs itself provides the first heal key. + printf '\n\n\n\n\n\n' > "$STICKY_XML" 2>/dev/null \ + || die "cannot create anchor (permissions?)" + log "seed: minimal anchor created by setargs (fresh rig)" + fi + + # v1.6: write as native via the Foundation codec + if ! write_bootargs_data "$new_args"; then + die "boot-args write failed - anchor NOT modified inconsistently" + fi + + # mirror into live NVRAM as BINARY form (allowed on old macOS) + local b64 hex percent + b64=$(read_bootargs_b64 "$STICKY_XML") + hex=$(printf '%s' "$b64" \ + | openssl base64 -d -A 2>/dev/null \ + | LC_ALL=C od -An -tx1 | LC_ALL=C tr -d ' \n') + percent=$(hex_to_percent "$hex") + if nvram "boot-args=${percent}" 2>/dev/null; then + info "Live NVRAM updated too, in binary form (this OS allows it)." + else + info "Live NVRAM refused (expected on modern macOS) - file wins at next boot." + fi + + # force-write the anchor (with new args) to the Clover volume + sync_once 1 merge 1 + + ok "boot-args set as (binary form), round-trip verified:" + echo " ${new_args}" + info "Reboot to apply." +} + + +# ──────────────────────────────────────────────────────────────────────── +# §12 COMMAND DISPATCHER +# ──────────────────────────────────────────────────────────────────────── + +setup_log_dir + +SELF_PATH="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/$(basename "${BASH_SOURCE[0]:-$0}")" + +CMD="${1:-dump}" +(( $# )) && shift + +case "$CMD" in + + # --- information (no sudo needed) ------------------------------------ + version) banner ;; + diagnose) cmd_diagnose ;; + status) cmd_status ;; + + # --- maintenance (sudo) ---------------------------------------------- + clean) cmd_clean ;; + prune) cmd_prune ;; + setargs) cmd_setargs "$@" ;; + + # --- persistence (sudo) ---------------------------------------------- + dump|"") need_root dump; sync_once 0 merge; heal_live_vars 0 ;; + watch) need_root watch; cmd_watch ;; + install) need_root install; cmd_install ;; + uninstall) need_root uninstall; cmd_uninstall ;; + + # --- unknown ---------------------------------------------------------- + *) banner + echo "Usage: sudo $0 [dump | setargs \"-v ...\" | watch | install | uninstall | clean | prune]" + echo " $0 [status | diagnose | version]" ;; +esac diff --git a/CloverPackage/CloverLogoutHook/LICENSE b/CloverPackage/CloverLogoutHook/LICENSE new file mode 100644 index 0000000000..e62ec04cde --- /dev/null +++ b/CloverPackage/CloverLogoutHook/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/CloverPackage/CloverLogoutHook/Usage.txt b/CloverPackage/CloverLogoutHook/Usage.txt new file mode 100755 index 0000000000..0d7f581715 --- /dev/null +++ b/CloverPackage/CloverLogoutHook/Usage.txt @@ -0,0 +1,36 @@ + +### What you need before using this utility (Mandatery): + +1. Check `EmuVariableUefi.efi` exists in `/EFI/CLOVER/drivers/BIOS/` —> Legacy boots never load `drivers/UEFI/` + +--------------------------------------------- +USAGE: ⬇︎ + +cd /Library/Application\ Support/CloverLogoutHook +chmod +x ./CloverLogoutHook.command +sudo ./CloverLogoutHook.command install +--------------------------------------------- + +VERIFYING: +cd /Library/Application\ Support/CloverLogoutHook +./CloverLogoutHook.command diagnose +--------------------------------------------- + +Quick health check (always start here) +cd /Library/Application\ Support/CloverLogoutHook +./CloverLogoutHook.command status +./CloverLogoutHook.command diagnose +tail -30 /Library/Logs/CloverHook/nvramhook.log + +Note if you use Boot-Args Control: Use sudo for diagnose +sudo ./CloverLogoutHook.command diagnose +--------------------------------------------- + +Boot-Args Control: An example below ⬇︎ +sudo ./CloverLogoutHook.command setargs "-lilubetaall alcid=11 keepsyms=1 amfi=0x80 revpatch=sbvmm -v" +--------------------------------------------- + +UNINSTALL: +cd /Library/Application\ Support/CloverLogoutHook +sudo ./CloverLogoutHook.command uninstall +--------------------------------------------- \ No newline at end of file diff --git a/CloverPackage/CloverLogoutHook/index.html b/CloverPackage/CloverLogoutHook/index.html new file mode 100644 index 0000000000..e89bd67cb2 --- /dev/null +++ b/CloverPackage/CloverLogoutHook/index.html @@ -0,0 +1,1072 @@ + + + + + +CloverLogoutHook — Developer Documentation + + + + + +
+ +

CloverLogoutHook

+

+ Persistent NVRAM for Clover Legacy Boot. +

+

+ Replacement for the classic Clover RC scripts. + Keeps your Startup Disk choice alive across reboots, shutdowns, and bootloader overrides. +

+ + View on Github + +
+ 🏷 v1.6 + Bash + PlistBuddy + perl + Zero dependencies + Validated 10.9 → 26 + Legacy BIOS · APFS + HFS+ · Multi-disk + 🏷 GPL-V3 +
+
+ +
+ + + + +
+

🎯 Overview

+

+ CloverLogoutHook is a script + system-level LaunchDaemon used to emulate non-volatile random-access memory (NVRAM) on systems where native NVRAM is broken, non-functional, or loses its data upon shutdown. that keeps your **Startup Disk choice alive** across reboots, shutdowns, and bootloader overrides on Hackintosh systems running **Clover Legacy Boot**. +

+
    +
  • Cold boot — shutdown ❄️ → power on
  • +
  • Warm restart — menu → Restart 🔄
  • +
  • GUI override — picking a different volume in Clover's boot picker (one-shot, sticky choice resumes)
  • +
+
+ It replaces the deprecated Clover RC scripts (broken since macOS 13 Ventura) + with zero external dependencies. +
+
+ 🆕 v1.4 highlights: HFS+ boot support (the daemon now runs on + Mavericks-era systems), multi-disk internal discovery, full + uninstall cleanup, and its own state folder + /Library/Logs/CloverHook. +
+
+ 📎 What you need (Mandatery): ⬇︎ +

+


+ 1.

Check EmuVariableUefi.efi exists in —> /EFI/CLOVER/drivers/BIOS/ — Legacy boots never load drivers/UEFI/

+

+


+ 2.

For Big Sur 11 and above you will need in /EFI/CLOVER/config.plist Boot/Arguments —> amfi=0x80

+

+


+ 3.

Again for Big Sur 11 and above you will need in /EFI/CLOVER/config.plist RtVariables/CsrActiveConfig —> 0x803 RtVariables/BooterConfig -> 0x28

+

+


+ ⚠️

Warning: Never use this key in /EFI/CLOVER/config.plist Boot/Arguments —> DefaultVolume LastBootedVolume because it will create an error in the native Startup Disk when you choose the boot disk.

+

+

Don't worry, NVRAM will emulate it natively.

+
+
+ + +
+

🏗️ Architecture

+
+┌─────────────────────────────────────────────────────────────────────┐ +│ macOS KERNEL SPACE │ +│ │ +│ NVRAM (emulated by EmuVariableUefi.efi — lives in RAM) │ +│ │ +│ Key example: efi-boot-device = <GUID path string> │ +│ Written by: Startup Disk prefpane / bless │ +│ Consumed by: macOS after each boot (keys vanish from live RAM) │ +└────────────────────────────┬────────────────────────────────────────┘ + │ + │ every 2 seconds + +┌─────────────────────────────────────────────────────────────────────┐ +│ CloverLogoutHook daemon │ +│ (root LaunchDaemon — user space) │ +│ │ +│ 1. DUMP : nvram -x -p → raw XML plist (tmp file) │ +│ 2. COMPARE : cmp vs last.xml baseline → no change? skip. │ +│ 3. MERGE : LIVE-WINS against sticky.xml anchor: │ +│ • copy anchor → final │ +│ • PlistBuddy Delete every key present in live │ +│ • PlistBuddy Merge live → final │ +│ Result: fresh values WIN; anchor re-adds only │ +│ keys that macOS consumed (efi-boot-device*, …). │ +│ 4. WRITE : mount Clover volume (hidden, transient) │ +│ → install → unmount │ +│ 5. ANCHOR : if final contains heal-keys → refresh sticky.xml│ +└────────────────────────────┬────────────────────────────────────────┘ + │ + │ at boot time + +┌─────────────────────────────────────────────────────────────────────┐ +│ CLOVER (firmware) │ +│ │ +│ EmuVariableUefi.efi reads /nvram.plist on the volume │ +│ → imports every key into emulated NVRAM RAM │ +│ → boot-manager honors efi-boot-device → auto-selects volume │ +└─────────────────────────────────────────────────────────────────────┘
+
+ + +
+

🛠️ The Sticky-Anchor Pattern

+ +

The core innovation. A naive NVRAM-to-file dump has a fatal flaw on Hackintosh:

+ +
+ ⚠️ macOS consumes efi-boot-device* keys after reading them + at boot — they vanish from live NVRAM within seconds of the OS starting. + A pure mirror dump would progressively strip the file of exactly the keys + needed for boot selection. +
+ +

Three-file model

+ + + + + + + + + + + + + + + + + +
FileRoleUpdate rule
last.xmlRaw live NVRAM snapshotRefreshed every cycle (baseline for cmp)
sticky.xmlAnchor — superset containing heal keysRefreshed only when final output contains any heal key
nvram.plist
(on Clover volume)
What Clover importsResult of live-wins merge against anchor
+ +

Live-wins merge algorithm

+
# PlistBuddy Merge does NOT overwrite existing keys.
+# So: strip from anchor every key live provides, then merge live in.
+cp anchor → final
+for key in $(keys_in_live); do
+    PlistBuddy -c "Delete :${key}" final   # 2>/dev/null || true
+done
+PlistBuddy -c "Merge live.xml" final
+ +
+ ✅ Property: final is always a superset — + fresh values from live overwrite old ones, and the anchor re-adds anything + live has lost. Selecting A→B→A→… works indefinitely because + the fresh live value always lands in the final file. +
+
+ + +
+

🔒 ESP Mount Strategy

+

+ The daemon never keeps a persistent mount and never interferes with user tools. + The volume appears nowhere on the desktop, no Finder sidebar entry, + no residual state. +

+ + + + + + + + + + + + + + +
SituationBehavior
NVRAM unchanged (typical cycle)No mount at all — zero disk activity
Change detected, volume unmounteddiskutil mount -nobrowse → write → unmount (~1–2 s)
Change detected, user already has the volume mountedReuse user's mount — never steal or eject
+

+ Mount point used for transient operations: + /private/var/.CloverEFI (dot-prefixed → invisible to Finder). +

+
+ + +
+

🗺️ Boot-Volume Resolution

+

+ Two worlds, one resolver: +

+
    +
  • + APFS boots/ is a synthesized snapshot inside a + synthesized container, backed by a physical store on a real disk. The script + walks the whole chain to find the physical host disk. +
  • +
  • + HFS+ boots (v1.4) — no APFS layer exists; the boot disk + is the root disk. Handled automatically. +
  • +
+
root slice       : disk3s5s1       ← mount /
+apfs container   : disk3           ← regex ^(disk[0-9]+)
+physical store   : disk1s2         ← awk '/Physical Store/' in diskutil list disk3
+host whole disk  : disk1           ← regex ^(disk[0-9]+)
+esp identifier   : disk1s1         ← awk '/EFI/' in diskutil list disk1
+

+ v1.3+ multi-disk: if the boot disk has no EFI-typed partition — + or the mounted one lacks EFI/CLOVERall internal disks + are scanned (boot disk first) for an HFS+ volume hosting EFI/CLOVER + (MBR disks, boot1h installs, old rigs). External/USB drives are + never touched. On HFS+ Clover volumes, nvram.plist is written to + the volume root — exactly where Legacy Clover reads it. +

+

Only macOS built-ins: mount, diskutil, awk. No external interpreters.

+
+ + +
+

🩹 Self-Healing Pass

+

Two safety nets run against file corruption or external amputation:

+
    +
  1. + At daemon start (after every boot): a forced sync pass + (force=1) rebuilds nvram.plist from + live ⊕ anchor, bypassing the no-change fast path. +
  2. +
  3. + Every 60 s (VERIFY_EVERY=30 cycles × 2 s): + if the on-volume file lacks any key present in the anchor, a forced rebuild + is triggered automatically. +
  4. +
+
+ + +
+

💾 Shutdown Flush

+

+ launchd sends SIGTERM at shutdown/restart; + Ctrl+C sends SIGINT. The trap: +

+
on_shutdown() {
+    sync_once 1 merge         # final flush; one retry if disks are being torn down
+    heal_live_vars 1
+    exit 0
+}
+trap on_shutdown TERM INT
+

+ This captures the very last NVRAM state — e.g. a Startup Disk click + made one second before shutdown — before the OS releases the disks. +

+
+ + +
+

⌨️ Command Surface

+ + + + + + + + + + + + +
Commandsudo?Purpose
dump (default)One-shot sync + heal
watchContinuous daemon loop (used by LaunchDaemon)
installCopy binary + install LaunchDaemon
uninstallFull cleanup: daemon + binary + CloverHook folder
status-Daemon state + file overview
setargsboot-args can be managed
diagnoseFull resolver trace + anchor health
cleanWipe tmp files, snapshots, sticky anchor (re-seed required after)
pruneRebuild file strictly from live (drops consumed keys)
versionPrint version banner
+
+ + +
+

🚀 Usage

+ +

1 — Install

+
git clone https://github.com/chris1111/CloverLogoutHook.git
+cd CloverLogoutHook
+
+chmod +x CloverLogoutHook.command
+bash -n CloverLogoutHook.command && echo "syntax OK"
+
+sudo ./CloverLogoutHook.command install
+ +

2 — Seed the anchor (once)

+

+ Open System Preferences → Startup Disk, select your volume, + close the window. One click is all it takes — the daemon captures it + within 2 seconds. +

+ +

3 — Verify

+
./CloverLogoutHook.command diagnose
+
+# expected:
+esp identifier   : disk1s1
+anchor has       : efi-boot-device       ✓
+anchor has       : efi-boot-device-data  ✓
+ +

4 — Done. Forever.

+
+ The daemon now runs silently at every boot. No action is ever required again: + pick any volume in Startup Disk — from any of your macOS + installations — and the choice survives reboots, shutdowns, and Clover + GUI overrides. +
+ +

Multi-OS rigs

+

+ Install the daemon on every modern macOS you boot from (v1.4 + supports HFS+ boots — Mavericks and later each capture their own Startup Disk + clicks). Older systems without the daemon (Snow Leopard…) are covered for + reading: their boot choice is restored from the file written by the + other installations. +

+ +

Manual operations (rare)

+
./CloverLogoutHook.command status                  # quick state overview
+./CloverLogoutHook.command diagnose                # resolver trace + anchor health
+sudo ./CloverLogoutHook.command dump               # force a sync now
+sudo ./CloverLogoutHook.command clean              # reset logs + anchor (re-seed after)
+sudo ./CloverLogoutHook.command uninstall          # full removal
+
+ + +
+

🎛️ Boot-Args Control (v1.5+)

+ +

+ boot-args can be managed from the terminal, without + touching config.plist — using the setargs command. + The value is stored in the sticky anchor and the Clover file as + native EFI binary form (<data>). +

+ +
+ 🧪 Experiment-proven: Clover merges config.plist + arguments with the NVRAM-file arguments at boot (config without + -v + file with -v = verbose boot). The file injects + flags the config does not have. +
+ +

Set boot-args

+ An example below ⇩ +
sudo ./CloverLogoutHook.command setargs "-lilubetaall alcid=11 keepsyms=1 amfi=0x80 revpatch=sbvmm -v"
+ +
    +
  • Stored as <data> (round-trip verified before writing)
  • +
  • Live NVRAM mirror attempted (allowed on older macOS, refused on modern — expected)
  • +
  • Applied at next boot — config.plist no longer needs to carry these flags
  • +
+ +

Check the current sticky value

+
./CloverLogoutHook.command diagnose
+
+# shows, with its storage type:
+sticky boot-args : -lilubetaall alcid=11 ... -v  [data/Clover-compatible]
+ +

Three ways to manage your args

+ + + + + + + + + + + + + + + + + +
MethodHowBest for
config.plistEdit Boot → Arguments in Clover ConfiguratorThe classic way — args always applied
setargs command 🆕sudo ... setargs "..."Terminal control, without touching config
BothThey merge at bootconfig = base args · file = extra/experimental flags
+ +
+ ⚠️ ClearNVRAM (F11) semantics: if you F11 while the daemon + anchor exists, the sticky file will restore the args on the next cycle + (phoenix behavior). For a clean slate, uninstall the daemon + first, then F11, then reinstall — exactly like the OC LogoutHook semantics. +
+ +

+ Note: on modern macOS the live NVRAM write is refused by the kernel + (expected) — persistence travels through the nvram.plist file, which + Clover imports at every boot. +

+
+ + +
+

🛟 Support & Troubleshooting

+ +

Quick health check (always start here)

+
./CloverLogoutHook.command status
+./CloverLogoutHook.command diagnose
+tail -30 /Library/Logs/CloverHook/nvramhook.log
+ +

Choice not honored after cold boot

+
    +
  1. Check EmuVariableUefi.efi exists in + /EFI/CLOVER/drivers/BIOS/ — Legacy boots never load + drivers/UEFI/.
  2. +
  3. diagnose — the anchor keys must show ✓.
  4. +
  5. Mount your ESP/Clover volume and verify nvram.plist + contains efi-boot-device.
  6. +
  7. Some Clover builds read the file at the volume root, others inside + EFI/CLOVER/ — this tool writes to the volume root.
  8. +
+ +

Choice lost after restarting from an older OS

+

+ The daemon can only capture choices made while it is running. + If the restart was triggered from a system without the daemon, nothing + wrote the choice. Fix: install the daemon on that system too + (v1.4 runs on Mavericks+ HFS boots), or perform one Startup Disk click + from an OS that has it. +

+ +

Conflicts with my EFI mounting tool

+

+ The daemon reuses an existing mount of the target volume — wherever it is + mounted (/Volumes/EFI, /private/tmp/ESP, …) — and only + mounts transiently (~1–2 s) when nothing exists. On a rare collision, + simply remount: your tool always has priority. +

+ +

Multi-disk rigs

+

+ v1.3+ scans all internal disks (boot disk first) for a volume + hosting EFI/CLOVER. The first match wins; external and USB + drives are excluded by design. Note: with several Clover volumes present, + the daemon writes to the first discovered one — keep your Clover + installations at the same version to avoid drift. +

+ +

Reset everything

+
sudo ./CloverLogoutHook.command clean
+# then: ONE Startup Disk click to re-seed the anchor
+ +
+ ⚠️ After clean or uninstall, the sticky anchor is + gone — make ONE Startup Disk click to re-seed it before + relying on persistence again. +
+ +

Filing an issue

+

Always include the four musketeers:

+
    +
  • Full output of ./CloverLogoutHook.command diagnose
  • +
  • Output of ./CloverLogoutHook.command status
  • +
  • Last 30 lines of /Library/Logs/CloverHook/nvramhook.log
  • +
  • Your macOS version(s), diskutil list, and Clover version
  • +
+
+ + +
+

📁 Filesystem Footprint

+ + + + + + + + + + +
PathPurpose
/usr/local/bin/clover-logout-hookInstalled script (copy of the .command)
/Library/LaunchDaemons/com.clover.nvramhook.daemon.plistLaunchDaemon descriptor
<Clover-volume-root>/nvram.plistWhat Clover imports every boot
/Library/Logs/CloverHook/nvramhook.logEvent log (auto-rotated at 400 KB)
/Library/Logs/CloverHook/last.xmlLive NVRAM baseline
/Library/Logs/CloverHook/sticky.xmlPersistent anchor
/Library/Logs/CloverHook/last-esp.txtCached Clover-volume identifier
/private/var/.CloverEFI/Transient hidden mount point (only during write)
+
+ + +
+

🍎 macOS Compatibility

+ +

Required by design

+
    +
  • APFS or HFS+ boot volume — the resolver handles both + worlds (v1.4): APFS synthesized chains and plain HFS+ disks. On APFS rigs + it walks the Physical Store markers; on HFS it takes the boot + disk directly.
  • +
  • /usr/libexec/PlistBuddy — available since + 10.5 Leopard, stable API.
  • +
  • launchctl bootstrap system — modern LaunchDaemon + API introduced in 10.11 El Capitan. Falls back to + launchctl load -w if bootstrap fails.
  • +
  • perl -MMIME::Base64 — bundled with every macOS, + unchanged for years.
  • +
+ +

Compatibility matrix

+ + + + + + + + + + + + + + + + + + +
macOSVersionStatus
Snow Leopard10.6✅ Boots via this tool — daemon untested there (reading covered)
Lion10.7⚠️ Same mechanism as Mavericks — untested
Mountain Lion10.8⚠️ Same mechanism as Mavericks — untested
Mavericks10.9Validated — v1.4 HFS boot support
Yosemite10.10Validated
El Capitan10.11Validated
Sierra10.12Validated
High Sierra10.13Validated
Mojave10.14Validated
Catalina10.15Validated
Big Sur11Validated
Monterey12Validated
Ventura13Primary target — tested & validated
Sonoma14Validated
Sequoia15Validated
Tahoe26Validated
+ +
+ 🍀 Multi-OS rigs: the daemon only needs to run on the modern + macOS installations you boot from. Older systems (Snow Leopard, Lion…) boot + through the same Clover + nvram.plist chain and are fully covered + by the persisted keys — no daemon required on them. + Validated in the field on the same machine. 🐅 +
+
+ + +
+

📦 Zero Dependencies Statement

+
bash            ✓ built-in since macOS 1.0
+awk, sed        ✓ built-in since macOS 1.0
+perl            ✓ bundled since macOS 10.3 (MIME::Base64 included)
+PlistBuddy      ✓ /usr/libexec since macOS 10.5
+plutil          ✓ built-in since macOS 10.5
+diskutil        ✓ built-in since macOS 10.0
+nvram           ✓ built-in since macOS 10.0
+launchctl       ✓ built-in since macOS 10.4
+
+ ✅ No Homebrew. No Xcode CLT. No Python. No Node. No git.
+ The script can be copied to any Hackintosh with Clover Legacy + and works immediately after install. +
+
+ + +
+

+ 📊 Performance & System Impact + + + +

+ +

0.1% on average Measured on a machine (Legacy BIOS) running the v1.6 daemon — real-world numbers from Activity Monitor and `ps aux`. This was validated on macOS.

+ +

🧠 RAM Usage

+ + + + + +
ComponentFootprint
Sleeping bash process~2–4 MB RSS
Sub-processes (nvram, awk, grep — spawn then release)~30 ms per cycle, then freed
Total stable~3–5 MB constant
+

For comparison: a single Safari tab = 200–800 MB. The daemon is invisible in Activity Monitor.

+ +

⚙️ CPU Usage

+ + + + + + +
PhaseFrequencyCPU Cost
Sleep loop (sleep 2)Continuous~0%
Cheap check (nvram dump + cmp)Every 2 s~15–30 ms
Full sync (mount + merge + write)Only on real change~1–2 s one-shot
Self-heal verifyEvery 60 sSame as cheap check
+ + + + + + +
StateCPU
99% of the time (idle)0.0% — daemon sleeps
Startup Disk clickOne spike at 1–3% for ~200 ms
Daily average< 0.1%
+ +

💾 Disk I/O

+ + + + +
SituationESP Write
NVRAM unchanged (99% of the time)ZERO — not even mounted
Real change detected1 mount + 1 write (~2 KB) + unmount
+ +

🔋 Battery / Energy

+
Energy impact   : NEGLIGIBLE
+- Daemon sleeps 99.9% of the time (sleep = zero CPU)
+- No network, no GPU, no high-precision timers
+- Estimated average draw: < 0.01 W
+ +

🎯 Why this design matters (for the Clover PKG)

+ + + + + + + + +
Production daemon criteriaCloverLogoutHook
Minimal RAM✅ 3–5 MB
Invisible CPU✅ fast-path + sleep loop
Zero unnecessary disk I/O✅ change-detection only
No external dependencies✅ macOS built-ins only
Clean, rotating logging✅ 400 KB cap
Clean teardown✅ transient mount
+ +

🧪 Verify it yourself

+
ps aux | grep clover-logout-hook | grep -v grep
+# → %CPU and %MEM columns
+
+# A/B proof:
+sudo launchctl bootout system/com.clover.nvramhook.daemon   # OFF
+ps aux | grep clover-logout-hook | grep -v grep             # → nothing
+sudo launchctl bootstrap system /Library/LaunchDaemons/com.clover.nvramhook.daemon.plist  # ON
+
+ + +
+

🏅 Credits

+

+ Built by chris1111 and AI GLM-5 during a live debugging session (August 2026). +

+
+ +
+ +
+

+ CloverLogoutHook Copyright © 2026 CloverHackyColor, All Rights Reserved.
+ Made with for the Hackintosh Legacy community.
+

+
+ + + + + diff --git a/CloverPackage/package/Resources/templates/Localizable.strings b/CloverPackage/package/Resources/templates/Localizable.strings index e315f685f8..45f3f3e900 100644 --- a/CloverPackage/package/Resources/templates/Localizable.strings +++ b/CloverPackage/package/Resources/templates/Localizable.strings @@ -231,6 +231,15 @@ More infos https://github.com/jief666/BootloaderChooser. "; //Verify sanity of your config.plist keeps it clean and up to date. //Installs to /usr/local/bin and will be available as 'ccpv' "; +// ============================================================================ +// CloverLogoutHook +// ---------------------------------------------------------------------------- +//"CloverLogoutHook_title" = "CloverLogoutHook"; +//"CloverLogoutHook_description" = "Persistent NVRAM for Clover Legacy Boot, +Installs to /Library/Application Support/CloverLogoutHook +Check EmuVariableUefi.efi exists in → /EFI/CLOVER/drivers/BIOS/ — Legacy boots never load drivers/UEFI/, +Read the Usage.txt."; + // ============================================================================ // RC Scripts // ---------------------------------------------------------------------------- diff --git a/CloverPackage/package/Scripts.templates/CloverLogoutHook/postinstall b/CloverPackage/package/Scripts.templates/CloverLogoutHook/postinstall new file mode 100755 index 0000000000..bb92372799 --- /dev/null +++ b/CloverPackage/package/Scripts.templates/CloverLogoutHook/postinstall @@ -0,0 +1,31 @@ +#!/bin/bash + +echo "===============================================" +echo "CloverLogoutHook Post-Install Script" +echo "===============================================" + +#echo "DEBUG: $ 1 = Full path to the installation package the installer app is processing: " $1 +#echo "DEBUG: $ 2 = Full path to the installation destination: " $2 +#echo "DEBUG: $ 3 = Installation volume (mountpoint) to receive the payload: " $3 +#echo "DEBUG: $ 4 = Root directory for the system: " $4 + +echo "preinstall: Path to installer....... $1" +echo "preinstall: Path to destination..... $2" +echo "preinstall: Path to dest volume..... $3" +echo "preinstall: Root of system folder... $4" + +############################################################################# + +DEST_VOL="${3}" +EFI_ROOT_DIR=$(cd "${DEST_VOL}"/Private/tmp/EFIROOTDIR; pwd -P) + + +echo "=====================================================" +echo " " +echo "Installing CloverLogoutHook for Clover Legacy Boot" +echo " " +echo "=====================================================" + +# --------------------------------------------- +# Installing CloverLogoutHook +# --------------------------------------------- diff --git a/CloverPackage/package/Scripts.templates/CloverLogoutHook/preinstall b/CloverPackage/package/Scripts.templates/CloverLogoutHook/preinstall new file mode 100755 index 0000000000..21caf8cffb --- /dev/null +++ b/CloverPackage/package/Scripts.templates/CloverLogoutHook/preinstall @@ -0,0 +1,42 @@ +#!/bin/bash + +echo "===============================================" +echo "CloverLogoutHook Pre-Install Script" +echo "===============================================" + +#echo "DEBUG: $ 1 = Full path to the installation package the installer app is processing: " $1 +#echo "DEBUG: $ 2 = Full path to the installation destination: " $2 +#echo "DEBUG: $ 3 = Installation volume (mountpoint) to receive the payload: " $3 +#echo "DEBUG: $ 4 = Root directory for the system: " $4 + +echo "preinstall: Path to installer....... $1" +echo "preinstall: Path to destination..... $2" +echo "preinstall: Path to dest volume..... $3" +echo "preinstall: Root of system folder... $4" + +############################################################################# + +DEST_VOL="${3}" +EFI_ROOT_DIR=$(cd "${DEST_VOL}"/Private/tmp/EFIROOTDIR; pwd -P) +CLOVER_INSTALLER_PLIST_NEW="${DEST_VOL}@CLOVER_INSTALLER_PLIST_NEW@" +install_log="${DEST_VOL}/Private/tmp/Clover_Install_Log.txt" +installer_choice="@INSTALLER_CHOICE@" + +echo "=====================================================" +echo " " +echo "CloverLogoutHook for Clover Legacy Boot" +echo " " +echo "=====================================================" + +# Mark that the option was selected +/usr/libexec/PlistBuddy -c "Add $installer_choice bool true" "$CLOVER_INSTALLER_PLIST_NEW" >/dev/null + +echo "======================================================" >> "$install_log" +echo "Installing CloverLogoutHook for Clover Legacy Boot" >> "$install_log" +echo "" >> "$install_log" + + +if [ -d "${3}/Library/Application Support/CloverLogoutHook" ]; then + echo "CloverLogoutHook exist. Delete old files" + rm -rf "${3}/Library/Application Support/CloverLogoutHook" +fi diff --git a/CloverPackage/package/buildpkg.sh b/CloverPackage/package/buildpkg.sh index 8085c720c2..78e3aee92b 100755 --- a/CloverPackage/package/buildpkg.sh +++ b/CloverPackage/package/buildpkg.sh @@ -1245,96 +1245,113 @@ if [[ -d "${SRCROOT}/CloverV2/EFI/CLOVER/drivers/$DRIVERS_OFF/$DRIVERS_UEFI/Othe fi # End build Other drivers-x64UEFI packages +# build CloverLogoutHook package + echo "================= CloverLogoutHook =================" + local CLH_Dir="${SRCROOT}"/CloverLogoutHook + local CLH_Dest='/Library/Application Support/CloverLogoutHook' + choiceId="CloverLogoutHook" + packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") + ditto --noextattr --noqtn "$CLH_Dir" \ + "${PKG_BUILD_DIR}/${choiceId}/Root/${CLH_Dest}"/ + packagesidentity="${clover_package_identity}" + mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root/Library/Application\ Support/CloverLogoutHook + addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" ${choiceId} + packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") + buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" + addChoice --start-visible="true" --title="CloverLogoutHook" --description="CloverLogoutHook for Clover Legacy Boot" --start-selected="false" --pkg-refs="$packageRefId" "${choiceId}" + +# End build CloverLogoutHook package + # build rc scripts package -if [[ ${NOEXTRAS} != *"RC scripts"* ]]; then - echo "===================== RC Scripts =======================" - packagesidentity="$clover_package_identity" +# if [[ ${NOEXTRAS} != *"RC scripts"* ]]; then +# echo "===================== RC Scripts =======================" +# packagesidentity="$clover_package_identity" - choiceId="rc.scripts.on.target" - packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") - rcScriptsOnTargetPkgRefId=$packageRefId - mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root - addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ - --subst="INSTALLER_CHOICE=$packageRefId" MarkChoice - buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" - addChoice --start-visible="true" \ - --start-selected="checkFileExists('/System/Library/CoreServices/boot.efi') && choicePreviouslySelected('$packageRefId')" \ - --start-enabled="checkFileExists('/System/Library/CoreServices/boot.efi')" \ - --pkg-refs="$packageRefId" "${choiceId}" +# choiceId="rc.scripts.on.target" +# packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") +# rcScriptsOnTargetPkgRefId=$packageRefId +# mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root +# addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ +# --subst="INSTALLER_CHOICE=$packageRefId" MarkChoice +# buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" +# addChoice --start-visible="true" \ +# --start-selected="checkFileExists('/System/Library/CoreServices/boot.efi') && choicePreviouslySelected('$packageRefId')" \ +# --start-enabled="checkFileExists('/System/Library/CoreServices/boot.efi')" \ +# --pkg-refs="$packageRefId" "${choiceId}" - choiceId="rc.scripts.on.all.volumes" - packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") - rcScriptsOnAllColumesPkgRefId=$packageRefId - mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root - addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ - --subst="INSTALLER_CHOICE=$packageRefId" MarkChoice - buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" - addChoice --start-visible="true" --start-selected="choicePreviouslySelected('$packageRefId')" \ - --pkg-refs="$packageRefId" "${choiceId}" +# choiceId="rc.scripts.on.all.volumes" +# packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") +# rcScriptsOnAllColumesPkgRefId=$packageRefId +# mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root +# addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ +# --subst="INSTALLER_CHOICE=$packageRefId" MarkChoice +# buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" +# addChoice --start-visible="true" --start-selected="choicePreviouslySelected('$packageRefId')" \ +# --pkg-refs="$packageRefId" "${choiceId}" - choiceIdRcScriptsCore="rc.scripts.core" - choiceId=$choiceIdRcScriptsCore - mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root/Library/LaunchDaemons - mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root/Library/Application\ Support/Clover - mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root/etc - mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Scripts - addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ - --subst="INSTALLER_ON_TARGET_REFID=$rcScriptsOnTargetPkgRefId" \ - --subst="INSTALLER_ON_ALL_VOLUMES_REFID=$rcScriptsOnAllColumesPkgRefId" \ - RcScripts - # Add the rc script library - cp -f "$SCPT_LIB_DIR"/rc_scripts.lib "${PKG_BUILD_DIR}/${choiceId}"/Scripts - rsync -r --exclude=.* --exclude="*~" ${SRCROOT}/CloverV2/rcScripts/ ${PKG_BUILD_DIR}/${choiceId}/Root/ - local toolsdir="${PKG_BUILD_DIR}/${choiceId}"/Scripts/Tools - mkdir -p "$toolsdir" - (cd "${PKG_BUILD_DIR}/${choiceId}"/Root && find {etc,Library} -type f > "$toolsdir"/rc.files) - fixperms "${PKG_BUILD_DIR}/${choiceId}/Root/" - chmod 644 "${PKG_BUILD_DIR}/${choiceId}/Root/Library/LaunchDaemons/com.projectosx.clover.daemon.plist" - chmod 744 "${PKG_BUILD_DIR}/${choiceId}/Root/Library/Application Support/Clover/CloverDaemon" - chmod 744 "${PKG_BUILD_DIR}/${choiceId}/Root/Library/Application Support/Clover/CloverDaemon-stopservice" - chmod 755 "${PKG_BUILD_DIR}/${choiceId}/Root/etc"/rc.*.d/*.{local,local.disabled} - chmod 755 "${PKG_BUILD_DIR}/${choiceId}/Scripts/postinstall" - packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") - buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" - addChoice --start-visible="false" \ - --selected="choices['rc.scripts.on.target'].selected || choices['rc.scripts.on.all.volumes'].selected" \ - --pkg-refs="$packageRefId" "${choiceId}" +# choiceIdRcScriptsCore="rc.scripts.core" +# choiceId=$choiceIdRcScriptsCore +# mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root/Library/LaunchDaemons +# mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root/Library/Application\ Support/Clover +# mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root/etc +# mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Scripts +# addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ +# --subst="INSTALLER_ON_TARGET_REFID=$rcScriptsOnTargetPkgRefId" \ +# --subst="INSTALLER_ON_ALL_VOLUMES_REFID=$rcScriptsOnAllColumesPkgRefId" \ +# RcScripts +# # Add the rc script library +# cp -f "$SCPT_LIB_DIR"/rc_scripts.lib "${PKG_BUILD_DIR}/${choiceId}"/Scripts +# rsync -r --exclude=.* --exclude="*~" ${SRCROOT}/CloverV2/rcScripts/ ${PKG_BUILD_DIR}/${choiceId}/Root/ +# local toolsdir="${PKG_BUILD_DIR}/${choiceId}"/Scripts/Tools +# mkdir -p "$toolsdir" +# (cd "${PKG_BUILD_DIR}/${choiceId}"/Root && find {etc,Library} -type f > "$toolsdir"/rc.files) +# fixperms "${PKG_BUILD_DIR}/${choiceId}/Root/" +# chmod 644 "${PKG_BUILD_DIR}/${choiceId}/Root/Library/LaunchDaemons/com.projectosx.clover.daemon.plist" +# chmod 744 "${PKG_BUILD_DIR}/${choiceId}/Root/Library/Application Support/Clover/CloverDaemon" +# chmod 744 "${PKG_BUILD_DIR}/${choiceId}/Root/Library/Application Support/Clover/CloverDaemon-stopservice" +# chmod 755 "${PKG_BUILD_DIR}/${choiceId}/Root/etc"/rc.*.d/*.{local,local.disabled} +# chmod 755 "${PKG_BUILD_DIR}/${choiceId}/Scripts/postinstall" +# packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") +# buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" +# addChoice --start-visible="false" \ +# --selected="choices['rc.scripts.on.target'].selected || choices['rc.scripts.on.all.volumes'].selected" \ +# --pkg-refs="$packageRefId" "${choiceId}" # End build rc scripts package # build optional rc scripts package - echo "================= Optional RC Scripts ==================" - packagesidentity="$clover_package_identity".optional.rc.scripts - addGroupChoices --title="Optional RC Scripts" --description="Optional RC Scripts" \ - --enabled="choices['$choiceIdRcScriptsCore'].selected" \ - "OptionalRCScripts" - local scripts=($( find "${SRCROOT}/CloverV2/rcScripts/etc" -type f -name '*.disabled' -depth 2 )) - for (( i = 0 ; i < ${#scripts[@]} ; i++ )) - do - local script_rel_path=etc/"${scripts[$i]##*/etc/}" # ie: etc/rc.boot.d/70.xx_yy_zz.local.disabled - local script="${script_rel_path##*/}" # ie: 70.xx_yy_zz.local.disabled - local choiceId=$(echo "$script" | sed -E 's/^[0-9]*[.]?//;s/\.local\.disabled//') # ie: xx_yy_zz - local title=${choiceId//_/ } # ie: xx yy zz - packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") - mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root - addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ - --subst="RC_SCRIPT=$script_rel_path" \ - --subst="INSTALLER_ON_TARGET_REFID=$rcScriptsOnTargetPkgRefId" \ - --subst="INSTALLER_ON_ALL_VOLUMES_REFID=$rcScriptsOnAllColumesPkgRefId" \ - --subst="INSTALLER_CHOICE=$packageRefId" \ - OptRcScripts - # Add the rc script library - cp -f "$SCPT_LIB_DIR"/rc_scripts.lib "${PKG_BUILD_DIR}/${choiceId}"/Scripts - fixperms "${PKG_BUILD_DIR}/${choiceId}/Root/" - chmod 755 "${PKG_BUILD_DIR}/${choiceId}/Scripts/postinstall" - buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" - addChoice --group="OptionalRCScripts" --title="$title" \ - --start-selected="choicePreviouslySelected('$packageRefId')" \ - --enabled="choices['OptionalRCScripts'].enabled" \ - --pkg-refs="$packageRefId" "${choiceId}" - done +# echo "================= Optional RC Scripts ==================" +# packagesidentity="$clover_package_identity".optional.rc.scripts +# addGroupChoices --title="Optional RC Scripts" --description="Optional RC Scripts" \ +# --enabled="choices['$choiceIdRcScriptsCore'].selected" \ +# "OptionalRCScripts" +# local scripts=($( find "${SRCROOT}/CloverV2/rcScripts/etc" -type f -name '*.disabled' -depth 2 )) +# for (( i = 0 ; i < ${#scripts[@]} ; i++ )) +# do +# local script_rel_path=etc/"${scripts[$i]##*/etc/}" # ie: etc/rc.boot.d/70.xx_yy_zz.local.disabled +# local script="${script_rel_path##*/}" # ie: 70.xx_yy_zz.local.disabled +# local choiceId=$(echo "$script" | sed -E 's/^[0-9]*[.]?//;s/\.local\.disabled//') # ie: xx_yy_zz +# local title=${choiceId//_/ } # ie: xx yy zz +# packageRefId=$(getPackageRefId "${packagesidentity}" "${choiceId}") +# mkdir -p ${PKG_BUILD_DIR}/${choiceId}/Root +# addTemplateScripts --pkg-rootdir="${PKG_BUILD_DIR}/${choiceId}" \ +# --subst="RC_SCRIPT=$script_rel_path" \ +# --subst="INSTALLER_ON_TARGET_REFID=$rcScriptsOnTargetPkgRefId" \ +# --subst="INSTALLER_ON_ALL_VOLUMES_REFID=$rcScriptsOnAllColumesPkgRefId" \ +# --subst="INSTALLER_CHOICE=$packageRefId" \ +# OptRcScripts +# Add the rc script library +# cp -f "$SCPT_LIB_DIR"/rc_scripts.lib "${PKG_BUILD_DIR}/${choiceId}"/Scripts +# fixperms "${PKG_BUILD_DIR}/${choiceId}/Root/" +# chmod 755 "${PKG_BUILD_DIR}/${choiceId}/Scripts/postinstall" +# buildpackage "$packageRefId" "${choiceId}" "${PKG_BUILD_DIR}/${choiceId}" "/" +# addChoice --group="OptionalRCScripts" --title="$title" \ +# --start-selected="choicePreviouslySelected('$packageRefId')" \ +# --enabled="choices['OptionalRCScripts'].enabled" \ +# --pkg-refs="$packageRefId" "${choiceId}" +# done # End build optional rc scripts package -fi +# fi # build theme packages if [[ ${NOEXTRAS} != *"Clover Themes"* ]]; then