Compare commits
2 Commits
d35d6d17d6
...
88f3b72343
| Author | SHA1 | Date |
|---|---|---|
|
|
88f3b72343 | |
|
|
dc3ffb68b7 |
|
|
@ -14,7 +14,10 @@ PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py"
|
|||
# Both factors required, with the normal password stack kept as a fallback so a
|
||||
# dead camera or absent key can never lock you out.
|
||||
PAM_TARGETS=(/etc/pam.d/sudo /etc/pam.d/hyprlock /etc/pam.d/login)
|
||||
PAM_HOWDY_SO="/usr/lib/security/pam_howdy.so"
|
||||
# howdy 2.x has no pam_howdy.so — it authenticates through pam_python.so loading
|
||||
# its own entrypoint script. pam_python.so ships in the AUR package `pam-python`.
|
||||
PAM_PYTHON_SO="/usr/lib/security/pam_python.so"
|
||||
HOWDY_PAM_PY="/usr/lib/security/howdy/pam.py"
|
||||
PAM_U2F_SO="/usr/lib/security/pam_u2f.so"
|
||||
U2F_MAP="/etc/u2f_mappings"
|
||||
PAM_MARK_BEGIN="# enroll-biometrics:begin howdy+u2f 2fa"
|
||||
|
|
@ -143,7 +146,16 @@ presence_test_camera() {
|
|||
}
|
||||
|
||||
# ── Howdy helpers ─────────────────────────────────────────────────────────────
|
||||
howdy_installed() { command -v howdy &>/dev/null; }
|
||||
# Detection must survive howdy 2.x's layout: its CLI lives under root-only
|
||||
# /usr/lib/security/howdy/ with /usr/bin/howdy symlinked into it, so a normal
|
||||
# user can neither resolve nor run the link — `command -v howdy` reports "not
|
||||
# found" even when it is installed. Every op here invokes it via `sudo howdy`
|
||||
# anyway, so fall back to the symlink itself and the package DB.
|
||||
howdy_installed() {
|
||||
command -v howdy &>/dev/null && return 0
|
||||
[[ -L /usr/bin/howdy || -e /usr/bin/howdy ]] && return 0
|
||||
command -v pacman &>/dev/null && pacman -Qq howdy &>/dev/null
|
||||
}
|
||||
|
||||
howdy_require() {
|
||||
howdy_installed && return 0
|
||||
|
|
@ -243,6 +255,42 @@ howdy_test() {
|
|||
fi
|
||||
}
|
||||
|
||||
# ── howdy PAM module (pam_python.so) ──────────────────────────────────────────
|
||||
# howdy's PAM entrypoint is a Python script loaded by pam_python.so. The module
|
||||
# is /usr/lib/security/pam_python.so directly (world-traversable, unlike howdy's
|
||||
# own root-only dir), so a plain existence test is reliable here; pacman -Qq is
|
||||
# the fallback in case it lands elsewhere.
|
||||
pam_python_installed() {
|
||||
[[ -e "$PAM_PYTHON_SO" ]] && return 0
|
||||
command -v pacman &>/dev/null && pacman -Qq pam-python &>/dev/null
|
||||
}
|
||||
|
||||
pam_python_require() {
|
||||
pam_python_installed && return 0
|
||||
|
||||
# pam-python is AUR-only, so it needs an AUR helper just like howdy.
|
||||
local helper
|
||||
for h in yay paru; do
|
||||
command -v "$h" &>/dev/null && { helper="$h"; break; }
|
||||
done
|
||||
if [[ -z "$helper" ]]; then
|
||||
msg "No AUR Helper" \
|
||||
"howdy's PAM module needs the AUR package 'pam-python', which requires an AUR helper (yay or paru).\n\nInstall yay first, then re-run." \
|
||||
11 62
|
||||
return 1
|
||||
fi
|
||||
|
||||
dialog --backtitle "$BACKTITLE" --title " pam-python Needed " \
|
||||
--yesno "\nhowdy authenticates via pam_python.so, provided by the AUR package 'pam-python'.\n\nInstall it now via ${helper}?" 10 62 || return 1
|
||||
clear
|
||||
printf "\nInstalling pam-python via %s...\n\n" "$helper"
|
||||
"$helper" -S --noconfirm --needed pam-python
|
||||
pam_python_installed || {
|
||||
msg "Install Failed" "pam-python installation failed.\nInstall it manually: ${helper} -S pam-python" 8 58
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# ── FIDO U2F helpers ────────────────────────────────────────────────────────────
|
||||
fido_installed() { [[ -e "$PAM_U2F_SO" ]] && command -v pamu2fcfg &>/dev/null; }
|
||||
|
||||
|
|
@ -291,7 +339,7 @@ fido_register() {
|
|||
# existing (password) auth lines below.
|
||||
pam_emit_block() {
|
||||
printf '%s\n' "$PAM_MARK_BEGIN"
|
||||
printf 'auth [success=ignore default=1] pam_howdy.so\n'
|
||||
printf 'auth [success=ignore default=1] pam_python.so %s\n' "$HOWDY_PAM_PY"
|
||||
printf 'auth sufficient pam_u2f.so authfile=%s cue\n' "$U2F_MAP"
|
||||
printf '%s\n' "$PAM_MARK_END"
|
||||
}
|
||||
|
|
@ -362,13 +410,9 @@ pam_remove_hyprlock() {
|
|||
}
|
||||
|
||||
pam_setup() {
|
||||
howdy_require || return
|
||||
fido_require || return
|
||||
|
||||
if [[ ! -e "$PAM_HOWDY_SO" ]]; then
|
||||
msg "Missing PAM Module" "pam_howdy.so not found at:\n$PAM_HOWDY_SO\n\nReinstall howdy and try again." 10 60
|
||||
return
|
||||
fi
|
||||
howdy_require || return
|
||||
pam_python_require || return
|
||||
fido_require || return
|
||||
|
||||
dialog --backtitle "$BACKTITLE" --title " Set Up PAM 2FA " --yesno \
|
||||
"\nThis will require BOTH face (howdy) and a FIDO key for:\n\n ${PAM_TARGETS[*]}\n\nYour password still works if a factor is unavailable.\nBackups of each file are saved as *.bak.<timestamp>.\n\nContinue?" 15 64 || return
|
||||
|
|
|
|||
29
sysupdate.sh
29
sysupdate.sh
|
|
@ -616,15 +616,30 @@ sync_configs() {
|
|||
[[ "$skip" == true ]] && continue
|
||||
|
||||
if [[ "$name" == "hypr" ]]; then
|
||||
# Copy hypr/ contents but preserve ~/.config/hypr/usr/
|
||||
# Copy hypr/ contents but preserve ~/.config/hypr/usr/.
|
||||
# Stage each item into a temp dir on the same filesystem, then mv it
|
||||
# into place — for files this is an atomic rename(2), so Hyprland's
|
||||
# live config watcher never sees hyprland.lua momentarily missing
|
||||
# (which otherwise makes it emit its fallback hyprland.conf stub).
|
||||
mkdir -p "$target/hypr"
|
||||
local hypr_ok=true
|
||||
while IFS= read -r -d '' hitem; do
|
||||
local hname; hname="$(basename "$hitem")"
|
||||
[[ "$hname" == "usr" ]] && continue
|
||||
rm -rf "${target}/hypr/${hname}" && cp -r "$hitem" "$target/hypr/" \
|
||||
|| hypr_ok=false
|
||||
done < <(find "$item" -maxdepth 1 -mindepth 1 -print0 | sort -z)
|
||||
local stage; stage="$(mktemp -d "${target}/hypr/.sync.XXXXXX")" || hypr_ok=false
|
||||
if $hypr_ok; then
|
||||
while IFS= read -r -d '' hitem; do
|
||||
local hname; hname="$(basename "$hitem")"
|
||||
[[ "$hname" == "usr" ]] && continue
|
||||
if cp -r "$hitem" "$stage/"; then
|
||||
# mv can't atomically replace a non-empty dir, so clear
|
||||
# an existing directory target first; files swap atomically.
|
||||
[[ -d "$stage/$hname" && -d "${target}/hypr/${hname}" ]] \
|
||||
&& rm -rf "${target}/hypr/${hname}"
|
||||
mv -f "$stage/$hname" "${target}/hypr/${hname}" || hypr_ok=false
|
||||
else
|
||||
hypr_ok=false
|
||||
fi
|
||||
done < <(find "$item" -maxdepth 1 -mindepth 1 -print0 | sort -z)
|
||||
rm -rf "$stage"
|
||||
fi
|
||||
if $hypr_ok; then
|
||||
ok "synced hypr ${DI}(usr/ preserved)${RS}"
|
||||
(( synced++ )) || true
|
||||
|
|
|
|||
Loading…
Reference in New Issue