#!/bin/sh # aldermon-install.sh - one-stop installer / uninstaller for aldermon. # # Flow: RECON (checks, nothing changed) -> printed plan -> confirm -> # ONE root escalation (doas/sudo) -> per-component install with status # lines -> verify. # # Install components (each checked first; skipped when already right, except # watts + the optional VID answer): # [1/4] binary+helper install if missing or older than the built # version; identical/newer left alone (--clean forces). # The helper FILE is inert without its capability. # [2/4] /etc config installed only if missing (system-conf edits are # never clobbered unless --clean). # [3/4] watts powercap udev rule + RUN chmod helper (energy_uj # is 0400 root-only and powercap has no devnode). # Always done - the TUI power panel needs it. # [4/4] VID (optional) asked at the root stage (y/n): msr udev rule # (nodes 0440 adm) + cap_sys_rawio+ep on aldermon-msr # only. open /dev/cpu/N/msr takes TWO gates: DAC # file-mode first (setcap alone gets EACCES - the cap # is not CAP_DAC_OVERRIDE), then msr_open()'s # capable(CAP_SYS_RAWIO) (chmod alone gets EPERM). # Declining leaves VID showing "no msr access"; # vCore/temps/freqs stay fully unprivileged either # way. # Install also removes pre-rename leftovers (60-adlermon-powercap.rules, # sbin/adlermon-powercap-chmod) and superseded installer scripts. # # Uninstall removes what install created (binaries, capability, udev rules, # RUN helper) and restores the device nodes' stock permissions, so recon # reports everything missing again. The /etc config is KEPT (your edits) # unless --purge. # # usage: aldermon-install.sh [MODE|OPTION] # (default) recon -> plan -> confirm -> install # --clean, -c force-reinstall every component (resets /etc config to stock) # --vid plan VID as ON; no prompt # --no-vid plan VID as SKIP; no prompt # --setup privileged parts only (rules, setcap) for staged/package # installs; still plan+confirm # --uninstall remove binaries, capability, rules, RUN helper; restore node # perms (keeps config) # --purge uninstall AND delete the /etc config # --del-adm on uninstall, also remove the original user from group adm # (default: ask at the root stage) # --keep-adm on uninstall, leave the adm group alone; don't ask # Install adds the original user to group adm if missing; activate it with a # re-login or `newgrp adm` (current shell only). # --check status report; changes nothing # --yes, -y accept the plan without prompting # --help, -h this text # # env: PREFIX (default /usr/local), SYSCONF (/etc), DATADIR (/usr/share) set -eu SRCDIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PREFIX=${PREFIX:-/usr/local} SYSCONF=${SYSCONF:-/etc} DATADIR=${DATADIR:-/usr/share} BIN=$PREFIX/bin/aldermon MSRBIN=$PREFIX/bin/aldermon-msr CONF=$SYSCONF/aldermon/aldermon.conf STOCK_CONF=$SRCDIR/aldermon.conf HELPER=$PREFIX/sbin/aldermon-powercap-chmod RULE=$SYSCONF/udev/rules.d/60-aldermon-powercap.rules MSRRULE=$SYSCONF/udev/rules.d/60-aldermon-msr.rules REL=$SRCDIR/target/release _seen_section=0 section() { if [ "$_seen_section" = 1 ]; then printf '\n'; fi; printf '== %s ==\n' "$1"; _seen_section=1; } item() { printf ' %s\n' "$*"; } sub() { printf ' %s\n' "$*"; } say() { printf '%s\n' "$*"; } die() { printf 'aldermon-install: error: %s\n' "$*" >&2; exit 1; } usage() { sed -n '/^# usage:/,/^# env:/p' "$0" | sed 's/^# //;s/^#$//'; } ver_older() { # a b -> true if a is a strictly older dotted version [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ] } installed_ver() { # echoes "" | version | 0.0.0 (pre-version build) [ -x "$BIN" ] || return 0 _v=$("$BIN" --version 2>/dev/null | sed -n 's/^aldermon //p') || true echo "${_v:-0.0.0}" } src_ver() { # version the tree builds (from binary if built, else Cargo.toml) if [ -x "$REL/aldermon" ]; then "$REL/aldermon" --version | sed -n 's/^aldermon //p' else sed -n 's/^version = "\([^"]*\)"/\1/p' "$SRCDIR/Cargo.toml" 2>/dev/null || echo "" fi } helper_state() { # "missing" | "installed, no capability" | "installed + capable" if [ ! -x "$MSRBIN" ]; then echo missing elif getcap "$MSRBIN" 2>/dev/null | grep -q cap_sys_rawio; then echo "installed + capable" else echo "installed, no capability"; fi } # echoes the [1/4] gate action for FORCE INST SRC bin_action() { if [ "$1" = clean ]; then echo "install (forced)" elif [ -z "$2" ]; then echo "install (missing)" elif ver_older "$2" "$3"; then echo "upgrade $2 -> $3" elif [ "$2" = "$3" ]; then echo "skip (up to date)" else echo "keep (installed $2 newer than source $3)" fi } write_if_changed() { # FILE MODE, content on stdin _f=$1 _m=$2 _t="$_f.tmp-aldermon-install" install -d "$(dirname "$_f")" cat >"$_t" if [ -f "$_f" ] && cmp -s "$_t" "$_f"; then sub "kept (unchanged): $_f" rm -f "$_t" else install -D -m "$_m" "$_t" "$_f" sub "installed: $_f" rm -f "$_t" fi } write_powercap() { # watts: RUN-chmod helper + rule, applied now install -d "$(dirname "$HELPER")" cat < plain MODE/GROUP printf '%s\n' 'KERNEL=="msr[0-9]*", SUBSYSTEM=="msr", MODE="0440", GROUP="adm"' | write_if_changed "$MSRRULE" 644 udevadm control --reload-rules # required: write_powercap reloaded BEFORE this file existed udevadm trigger --subsystem-match=msr sub "udev msr nodes triggered (0440 adm)" } # adm group membership: watts (energy_uj 0440 adm) and VID msr nodes both # need it. Adds USER if missing so a fresh install works after one re-login. ensure_adm() { # USER _user=${1:-} [ -n "$_user" ] && [ "$_user" != root ] || return 0 if ! getent group adm >/dev/null 2>&1; then sub "warning: no group adm on this system; grant node access to $_user's group instead" elif id -nG "$_user" 2>/dev/null | tr ' ' '\n' | grep -qx adm; then sub "user $_user: already in group adm" elif usermod -aG adm "$_user"; then sub "added $_user to group adm - re-login (or 'newgrp adm') to activate" else sub "warning: could not add $_user to adm; do it manually: doas usermod -aG adm $_user" fi } # Pre-rename (adlermon) and superseded installer leftovers - the old rule # still RUNs its old chmod helper, so remove the pair once ours is in. clean_legacy() { for _l in "$SYSCONF/udev/rules.d/60-adlermon-powercap.rules" \ "$PREFIX/sbin/adlermon-powercap-chmod" \ "$DATADIR/aldermon/powercap-pl-rules.sh" \ "$DATADIR/aldermon/aldermon-setup.sh"; do if [ -e "$_l" ]; then rm -f "$_l" sub "removed legacy leftover: $_l" fi done } setcap_helper() { # FORCE - VID gate 2: capability on the minimal helper only [ -x "$MSRBIN" ] || die "$MSRBIN not found - install the binary first (or use 'aldermon --vid' under root)" if [ "$1" != clean ] && getcap "$MSRBIN" 2>/dev/null | grep -q 'cap_sys_rawio'; then sub "kept (already capable): $MSRBIN" else setcap cap_sys_rawio+ep "$MSRBIN" sub "setcap cap_sys_rawio+ep: $MSRBIN" fi } vid_choice() { # root side: ask Y/n unless pre-answered case "$VIDMODE" in on) return 0 ;; off) sub "skipped (declined): VID stays unreadable - the TUI shows 'VID n/a (no msr access)'. Re-run with --vid to add it."; return 1 ;; esac printf 'enable VID extras (msr rule + helper setcap)? [Y/n] ' read -r _ans || _ans=y case "$_ans" in [nN]*) sub "skipped (declined): VID stays unreadable - the TUI shows 'VID n/a (no msr access)'. Re-run with --vid to add it."; return 1 ;; *) return 0 ;; esac } verify() { # ORIGUSER VIDENABLED _u=${1:-$(id -un)} _vid=${2:-yes} section verify if [ -x "$BIN" ]; then item "binary: $("$BIN" --version 2>/dev/null || echo 'installed, pre-version')"; fi _c=$(getcap "$MSRBIN" 2>/dev/null || true) if [ "$_vid" = yes ]; then item "helper caps: ${_c:-NONE (VID stays unreadable - run --setup)}" else item "VID: not wanted this run - TUI shows 'VID n/a (no msr access)'" if [ -f "$MSRRULE" ]; then item "note: an msr rule from a previous install is still present (left alone; VID still off until the helper is setcap'd)" fi fi if [ -e /dev/cpu/0/msr ]; then item "msr nodes: $(ls -l /dev/cpu/0/msr | awk '{print $1, $3, $4}')" else item "warning: /dev/cpu/0/msr absent - msr module not loaded" fi _e=$(ls /sys/class/powercap/intel-rapl:0/energy_uj 2>/dev/null || true) if [ -n "$_e" ]; then item "energy_uj: $(ls -l "$_e" | awk '{print $1, $3, $4}')"; fi if [ -f "$CONF" ]; then item "config: $CONF present"; else item "config: $CONF MISSING"; fi if id -nG "$_u" 2>/dev/null | tr ' ' '\n' | grep -qx adm; then item "user $_u: in group adm (existing shells need re-login or 'newgrp adm')" else item "user $_u: NOT in group adm" fi } recon() { # install/setup: status + planned actions, BEFORE any root action _src=$(src_ver) _inst=$(installed_ver) section recon item "installed: ${_inst:-none} | source: ${_src:-unknown}" item "helper: $(helper_state)" item "rules: powercap $([ -f "$RULE" ] && echo present || echo missing), msr $([ -f "$MSRRULE" ] && echo present || echo missing)" item "config: $CONF $([ -f "$CONF" ] && echo present || echo missing)" _e=$(ls -l /sys/class/powercap/intel-rapl:0/energy_uj 2>/dev/null | awk '{print $1, $3, $4}') item "watts source: energy_uj ${_e:-unreadable}" item "planned actions:" if [ "$MODE" != setup ]; then _act=$(bin_action "$FORCE" "$_inst" "$_src") if [ "$MODE" = install ] && [ ! -x "$REL/aldermon" ]; then _act="$_act (after release build)"; fi sub "[1/4] binary+helper: $_act" if [ "$FORCE" = clean ]; then sub "[2/4] config: reset $CONF to stock" elif [ -f "$CONF" ]; then sub "[2/4] config: keep existing $CONF" else sub "[2/4] config: install stock $CONF"; fi fi sub "[3/4] watts: ensure powercap rule + $HELPER (group adm; needs root)" _u=$(id -un) if id -nG "$_u" 2>/dev/null | tr ' ' '\n' | grep -qx adm; then sub "groups: $_u already in adm" else sub "groups: add $_u to adm (needs root; re-login or 'newgrp adm' after)" fi case "$VIDMODE" in off) sub "[4/4] VID: SKIP (--no-vid)" ;; on) sub "[4/4] VID: msr rule + setcap helper (needs root)" ;; *) sub "[4/4] VID: OPTIONAL - asked after the root prompt; msr rule 0440 adm + cap_sys_rawio+ep on aldermon-msr only. Decline keeps 'VID n/a'; vCore/temps/freqs need no privileges either way." ;; esac } recon_uninstall() { section recon item "binary: $([ -x "$BIN" ] && echo "$BIN ($("$BIN" --version 2>/dev/null || echo 'pre-version'))" || echo "not installed")" item "helper: $(helper_state)" item "rules: powercap $([ -f "$RULE" ] && echo present || echo missing), msr $([ -f "$MSRRULE" ] && echo present || echo missing)" item "config: $CONF $([ -f "$CONF" ] && echo present || echo missing)" item "planned actions:" if [ "$PURGE" = yes ]; then sub "remove binaries, capability, both udev rules, RUN helper, installer copy" sub "restore /dev/cpu/*/msr to 0600 root:root and energy_uj to 0400 root:root" sub "remove config $CONF (--purge)" else sub "remove binaries, capability, both udev rules, RUN helper, installer copy" sub "restore /dev/cpu/*/msr to 0600 root:root and energy_uj to 0400 root:root" sub "keep config $CONF (add --purge to remove)" fi case "$ADMREMOVE" in yes) sub "remove $(id -un) from group adm" ;; no) sub "leave group adm alone" ;; *) sub "ask at the root stage whether to remove $(id -un) from group adm" ;; esac } confirm_or_go() { # plan acceptance before escalating [ "$ASSUME" = yes ] && return 0 printf 'proceed? [Y/n] ' read -r _r || die "no tty for confirmation - use --yes" case "$_r" in [nN]*) say "aborted, nothing changed"; exit 0 ;; esac } root_install() { # FORCE VIDMODE PURGE ORIGUSER PREFIX SYSCONF DATADIR FORCE=$1; VIDMODE=$2; _orig=$4; PREFIX=$5; SYSCONF=$6; DATADIR=$7 BIN=$PREFIX/bin/aldermon; MSRBIN=$PREFIX/bin/aldermon-msr CONF=$SYSCONF/aldermon/aldermon.conf; STOCK_CONF=$SRCDIR/aldermon.conf HELPER=$PREFIX/sbin/aldermon-powercap-chmod RULE=$SYSCONF/udev/rules.d/60-aldermon-powercap.rules MSRRULE=$SYSCONF/udev/rules.d/60-aldermon-msr.rules [ -x "$REL/aldermon" ] || die "no release build in $SRCDIR - run the script unprivileged (it builds) or cargo build --release first" SRCVER=$(src_ver) INSTVER=$(installed_ver) section install item "[1/4] binary (installed: ${INSTVER:-none}, source: $SRCVER)" _act=$(bin_action "$FORCE" "$INSTVER" "$SRCVER") case $_act in install* | upgrade*) install -Dm755 "$REL/aldermon" "$BIN" install -Dm755 "$REL/aldermon-msr" "$MSRBIN" sub "installed: $BIN + $MSRBIN" ;; skip*) if [ ! -x "$MSRBIN" ]; then install -Dm755 "$REL/aldermon-msr" "$MSRBIN" sub "skipped binary (up to date); installed missing helper" else sub "skipped: up to date (use --clean to force)" fi ;; keep*) sub "$_act" ;; esac item "[2/4] config" if [ "$FORCE" = clean ] || [ ! -e "$CONF" ]; then [ -f "$STOCK_CONF" ] || die "stock config $STOCK_CONF not found (installed copy? use --setup)" install -Dm644 "$STOCK_CONF" "$CONF" sub "installed stock: $CONF" else sub "kept existing: $CONF (--clean resets to stock)" fi item "[3/4] watts (powercap)" write_powercap item "[4/4] VID (optional)" _vid=no if vid_choice; then _vid=yes write_msr_rule setcap_helper "$FORCE" fi item "group membership" ensure_adm "$_orig" verify "$_orig" "$_vid" } root_setup() { # FORCE VIDMODE PURGE ORIGUSER PREFIX SYSCONF DATADIR FORCE=$1; VIDMODE=$2; _orig=$4; PREFIX=$5; SYSCONF=$6; DATADIR=$7 BIN=$PREFIX/bin/aldermon; MSRBIN=$PREFIX/bin/aldermon-msr CONF=$SYSCONF/aldermon/aldermon.conf HELPER=$PREFIX/sbin/aldermon-powercap-chmod RULE=$SYSCONF/udev/rules.d/60-aldermon-powercap.rules MSRRULE=$SYSCONF/udev/rules.d/60-aldermon-msr.rules section install item "setup: watts (powercap)" write_powercap item "setup: VID (optional)" _vid=no if vid_choice; then _vid=yes write_msr_rule setcap_helper "$FORCE" fi item "group membership" ensure_adm "$_orig" verify "$_orig" "$_vid" } root_uninstall() { # FORCE VIDMODE PURGE ORIGUSER PREFIX SYSCONF DATADIR ADMREMOVE PURGE=$3; _orig=$4; PREFIX=$5; SYSCONF=$6; DATADIR=$7; ADMREMOVE=$8 BIN=$PREFIX/bin/aldermon; MSRBIN=$PREFIX/bin/aldermon-msr CONF=$SYSCONF/aldermon/aldermon.conf HELPER=$PREFIX/sbin/aldermon-powercap-chmod RULE=$SYSCONF/udev/rules.d/60-aldermon-powercap.rules MSRRULE=$SYSCONF/udev/rules.d/60-aldermon-msr.rules section uninstall if [ -x "$MSRBIN" ]; then setcap -r "$MSRBIN" 2>/dev/null && item "dropped capability: $MSRBIN" || item "capability already absent" fi for _f in "$BIN" "$MSRBIN" "$HELPER" "$RULE" "$MSRRULE" "$DATADIR/aldermon/aldermon-install.sh"; do if [ -e "$_f" ]; then rm -f "$_f"; item "removed: $_f"; fi done clean_legacy # Restore stock device perms so recon reports them unset again. if [ -e /dev/cpu/0/msr ]; then chgrp root /dev/cpu/*/msr 2>/dev/null || true chmod 0600 /dev/cpu/*/msr 2>/dev/null || true item "restored /dev/cpu/*/msr -> 0600 root:root" fi _e=$(ls /sys/class/powercap/intel-rapl:0/energy_uj 2>/dev/null || true) if [ -n "$_e" ]; then chgrp root "$_e" 2>/dev/null || true chmod 0400 "$_e" 2>/dev/null || true item "restored energy_uj -> 0400 root:root" fi udevadm control --reload-rules item "udev rules reloaded" if [ "$PURGE" = yes ]; then if [ -e "$CONF" ]; then rm -f "$CONF"; item "removed config: $CONF"; fi else item "kept config: $CONF (--purge removes it)" fi rmdir -p --ignore-fail-on-non-empty "$DATADIR/aldermon" 2>/dev/null || true verify_uninstall "$_orig" "$ADMREMOVE" } verify_uninstall() { # ORIGUSER ADMREMOVE _u=${1:-$(id -un)} _adm=${2:-no} section verify item "binary: $([ -e "$BIN" ] && echo STILL PRESENT || echo gone)" item "helper: $([ -e "$MSRBIN" ] && echo STILL PRESENT || echo gone)" item "powercap rule: $([ -e "$RULE" ] && echo STILL PRESENT || echo gone)" item "msr rule: $([ -e "$MSRRULE" ] && echo STILL PRESENT || echo gone)" if [ -e /dev/cpu/0/msr ]; then item "msr node: $(ls -l /dev/cpu/0/msr | awk '{print $1, $3, $4}') (stock 0600 root root)" fi _e=$(ls /sys/class/powercap/intel-rapl:0/energy_uj 2>/dev/null || true) if [ -n "$_e" ]; then item "energy_uj: $(ls -l "$_e" | awk '{print $1, $3, $4}')"; fi item "config: $CONF $([ -e "$CONF" ] && echo present || echo gone)" if id -nG "$_u" 2>/dev/null | tr ' ' '\n' | grep -qx adm; then _do_adm=no case "$_adm" in yes) _do_adm=yes ;; no) item "user $_u: still in group adm (use --del-adm to remove; adm also grants log access)" ;; *) printf 'remove %s from group adm? [y/N] ' "$_u" read -r _r || _r=n case "$_r" in [yY]*) _do_adm=yes ;; esac ;; esac if [ "$_do_adm" = yes ]; then if gpasswd -d "$_u" adm >/dev/null 2>&1; then item "removed $_u from group adm (re-login for it to take effect)" else item "could not remove $_u from adm; do it manually: doas gpasswd -d $_u adm" fi elif [ "$_adm" = ask ]; then item "kept $_u in group adm (adm also grants log access)" fi fi } escalate() { # INTERNALMODE _mode=$1 _u=$(id -un) if [ "$(id -u)" -eq 0 ]; then "$0" "$_mode" "$FORCE" "$VIDMODE" "$PURGE" "$_u" "$PREFIX" "$SYSCONF" "$DATADIR" "$ADMREMOVE" else PRIV=$(command -v doas 2>/dev/null || command -v sudo 2>/dev/null || true) [ -n "$PRIV" ] || die "no doas or sudo found - run the privileged steps as root" say "requesting root via $PRIV" exec "$PRIV" "$0" "$_mode" "$FORCE" "$VIDMODE" "$PURGE" "$_u" "$PREFIX" "$SYSCONF" "$DATADIR" "$ADMREMOVE" fi } check_status() { section status if [ -x "$BIN" ]; then item "binary: $("$BIN" --version 2>/dev/null || echo 'installed, pre-version (<= 2026-09)')" if [ -x "$REL/aldermon" ]; then _s=$("$REL/aldermon" --version | sed -n 's/^aldermon //p') _i=$("$BIN" --version 2>/dev/null | sed -n 's/^aldermon //p') [ -n "$_i" ] || _i=0.0.0 if ver_older "$_i" "$_s"; then item "update available: source builds $_s" else item "source build: $_s $([ "$_i" = "$_s" ] && echo '(match)' || echo '(source is older)')"; fi fi else item "binary: NOT INSTALLED ($BIN)" fi item "helper: $(helper_state)" item "powercap rule: $([ -f "$RULE" ] && echo present || echo MISSING)" item "msr rule: $([ -f "$MSRRULE" ] && echo present || echo MISSING)" if [ -e /dev/cpu/0/msr ]; then item "msr node: $(ls -l /dev/cpu/0/msr | awk '{print $1, $3, $4}') (needs 0440 adm + helper cap)" fi item "config: $CONF $([ -f "$CONF" ] && echo present || echo MISSING)" if id -nG "$(id -un)" | tr ' ' '\n' | grep -qx adm; then item "user $(id -un): in adm" else item "user $(id -un): NOT in adm" fi } MODE=install FORCE=plain VIDMODE=ask PURGE=no ADMREMOVE=ask ASSUME=no while [ $# -gt 0 ]; do case $1 in -h | --help) usage; exit 0 ;; --check) MODE=check ;; --setup) MODE=setup ;; --uninstall) MODE=uninstall ;; --purge) MODE=uninstall; PURGE=yes ;; --clean | -c) FORCE=clean ;; --yes | -y) ASSUME=yes ;; --no-vid) VIDMODE=off ;; --vid) VIDMODE=on ;; --del-adm) ADMREMOVE=yes ;; --keep-adm) ADMREMOVE=no ;; _root) shift; root_install "$@"; exit 0 ;; _setup) shift; root_setup "$@"; exit 0 ;; _unroot) shift; root_uninstall "$@"; exit 0 ;; *) die "unknown argument: $1 (see --help)" ;; esac shift done case $MODE in check) check_status ;; uninstall) recon_uninstall confirm_or_go escalate _unroot ;; setup) recon confirm_or_go escalate _setup ;; install) [ -f "$SRCDIR/Cargo.toml" ] || die "$SRCDIR is not a source tree - run from the checkout, or use --setup/--check" recon confirm_or_go if [ ! -x "$REL/aldermon" ]; then command -v cargo >/dev/null || die "cargo not found and no release build present" say "building release binaries (as $(id -un))" (cd "$SRCDIR" && cargo build --release) fi escalate _root ;; esac