Single-script builder (nosignal.sh) that turns a stock Arch Linux ISO into a fully-offline installer for a themed Hyprland + caelestia (Quickshell) desktop: matching SDDM greeter, Btrfs/Limine bootable snapshots, chwd-style GPU detection, and a curated "os updates" layer (keybind cheatsheet, settings panels, system polish, on-box management skill). See README.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
134 lines
5.9 KiB
Bash
Executable file
134 lines
5.9 KiB
Bash
Executable file
#!/bin/sh
|
|
# nosignal-sudo-toggle — time-boxed passwordless sudo ("sudoless" mode).
|
|
#
|
|
# enable : grant the human user NOPASSWD: ALL for WINDOW_MIN minutes, then
|
|
# auto-revert. Requires root (run via `sudo`); you authenticate ONCE.
|
|
# disable: revert now (remove the drop-in, cancel the timer). Requires root,
|
|
# but inside an active window `sudo -n` works without a password.
|
|
# status : print active/inactive (+minutes remaining). Needs NO root — reads a
|
|
# world-readable stamp, so the panel toggle can poll it.
|
|
#
|
|
# Safety:
|
|
# - the sudoers drop-in is validated with `visudo -cf` BEFORE it is installed;
|
|
# a malformed file is never moved into place (a broken sudoers locks you out).
|
|
# - auto-revert is a root-owned transient systemd timer (no password needed).
|
|
# - a boot-time oneshot (nosignal-sudoless-boot-clean.service) removes the
|
|
# drop-in on every boot, so a reboot during the window can never leave
|
|
# passwordless sudo enabled (transient timers don't survive reboot).
|
|
set -eu
|
|
|
|
DROPIN=/etc/sudoers.d/01-nosignal-sudoless
|
|
STAMP=/run/nosignal-sudoless.stamp # tmpfs: world-readable, cleared on reboot
|
|
WINDOW_MIN=15
|
|
REVERT_UNIT=nosignal-sudoless-revert
|
|
SELF=/usr/local/bin/nosignal-sudo-toggle
|
|
|
|
die() { echo "nosignal-sudo-toggle: $*" >&2; exit 1; }
|
|
need_root() { [ "$(id -u)" -eq 0 ] || die "must run as root (use: sudo $SELF $1)"; }
|
|
|
|
target_user() {
|
|
# who to grant: the invoking human (via sudo), never root
|
|
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
|
|
printf '%s\n' "$SUDO_USER"; return 0
|
|
fi
|
|
# fallback: the lowest normal login account (uid 1000-60000) with a shell
|
|
awk -F: '$3>=1000 && $3<60000 && $7 !~ /(nologin|false)$/ {print $1; exit}' /etc/passwd
|
|
}
|
|
|
|
cancel_timer() {
|
|
# Stop ONLY the .timer (disarm a pending revert). NEVER stop the .service:
|
|
# when disable() is invoked BY that service (the timer fired), stopping it
|
|
# would SIGTERM us mid-run — which is exactly the bug that left the drop-in
|
|
# in place. reset-failed both is harmless cleanup.
|
|
systemctl stop "${REVERT_UNIT}.timer" 2>/dev/null || true
|
|
systemctl reset-failed "${REVERT_UNIT}.timer" "${REVERT_UNIT}.service" 2>/dev/null || true
|
|
}
|
|
|
|
cmd_enable() {
|
|
need_root enable
|
|
user=$(target_user)
|
|
[ -n "$user" ] || die "could not determine target user"
|
|
|
|
tmp=$(mktemp /tmp/nosignal-sudoless.XXXXXX) || die "mktemp failed"
|
|
trap 'rm -f "$tmp"' EXIT
|
|
cat > "$tmp" <<EOF
|
|
# Managed by nosignal-sudo-toggle — TIME-BOXED, auto-reverts after ${WINDOW_MIN} min.
|
|
# Do not edit by hand; run \`sudo nosignal-sudo-toggle disable\` to revert now.
|
|
$user ALL=(ALL:ALL) NOPASSWD: ALL
|
|
EOF
|
|
# validate BEFORE installing — never move a broken sudoers file into place
|
|
visudo -cf "$tmp" >/dev/null 2>&1 || die "sudoers validation failed; aborting (no change made)"
|
|
|
|
install -m 0440 -o root -g root "$tmp" "$DROPIN"
|
|
rm -f "$tmp"; trap - EXIT
|
|
date +%s > "$STAMP"; chmod 0644 "$STAMP"
|
|
|
|
# arm the auto-revert (fresh 15-min window each enable)
|
|
cancel_timer
|
|
systemd-run --quiet --unit="$REVERT_UNIT" --on-active="${WINDOW_MIN}min" \
|
|
--description="Revert NoSignal timed passwordless sudo" \
|
|
"$SELF" disable >/dev/null 2>&1 \
|
|
|| echo "WARNING: could not arm auto-revert timer; run 'sudo $SELF disable' manually" >&2
|
|
|
|
echo "sudoless ENABLED for $user — ${WINDOW_MIN} min (auto-reverts; reboot also clears it)"
|
|
}
|
|
|
|
cmd_enable_tui() {
|
|
# Runs as the human user inside the floating prompt terminal (class
|
|
# nosignal-sudo). Drives the SINGLE password prompt for `enable`, then keeps
|
|
# the window readable on failure so the user sees why — the old flow let the
|
|
# terminal vanish on failure, which read as "the switch is broken".
|
|
# -k : ignore any cached/stale credential so the prompt is always fresh and
|
|
# a pam_faillock lockout surfaces here instead of silently reverting.
|
|
printf '\n Enable passwordless sudo for %s minutes.\n\n' "$WINDOW_MIN"
|
|
if sudo -k -p " [sudo] password for %p: " "$SELF" enable; then
|
|
printf '\n Done — closing.\n'
|
|
sleep 1
|
|
return 0
|
|
fi
|
|
# reached only when sudo failed (wrong password, or faillock lockout)
|
|
printf '\n Could not enable passwordless sudo.\n'
|
|
printf ' If you mistyped a few times the account may be briefly locked\n'
|
|
printf ' (~10 min by pam_faillock). Wait and retry, or from a TTY:\n'
|
|
printf ' su - then faillock --user %s --reset\n' "$(id -un)"
|
|
printf '\n Press Enter to close. '
|
|
read -r _ || true
|
|
return 1
|
|
}
|
|
|
|
cmd_disable() {
|
|
need_root disable
|
|
# who held the grant — needed to clear their cached sudo ticket below
|
|
u=$(awk '/NOPASSWD/{print $1; exit}' "$DROPIN" 2>/dev/null || true)
|
|
# Remove the grant FIRST. If we're running from inside the revert service,
|
|
# cancel_timer used to stop our own unit and kill us before this line ran,
|
|
# leaving the drop-in in place. Drop the privilege before touching units.
|
|
rm -f "$DROPIN" "$STAMP"
|
|
sync
|
|
# Invalidate the user's cached sudo credential too: sudo caches an auth
|
|
# ticket (~15 min, timestamp_timeout) independent of the drop-in, so without
|
|
# this a sudo run just before revert would stay passwordless past the window.
|
|
[ -n "$u" ] && rm -f "/run/sudo/ts/$u" 2>/dev/null || true
|
|
cancel_timer
|
|
echo "sudoless DISABLED — password required again"
|
|
}
|
|
|
|
cmd_status() {
|
|
if [ -f "$STAMP" ]; then
|
|
start=$(cat "$STAMP" 2>/dev/null || echo 0)
|
|
now=$(date +%s)
|
|
left=$(( start + WINDOW_MIN*60 - now ))
|
|
[ "$left" -lt 0 ] && left=0
|
|
echo "active $(( (left + 59) / 60 ))" # "active <minutes-remaining>"
|
|
else
|
|
echo "inactive 0"
|
|
fi
|
|
}
|
|
|
|
case "${1:-}" in
|
|
enable) cmd_enable ;;
|
|
enable-tui) cmd_enable_tui ;;
|
|
disable) cmd_disable ;;
|
|
status) cmd_status ;;
|
|
*) echo "usage: nosignal-sudo-toggle {enable|enable-tui|disable|status}" >&2; exit 2 ;;
|
|
esac
|