Skip to content

Instantly share code, notes, and snippets.

@armenr
Created April 19, 2026 08:10
Show Gist options
  • Select an option

  • Save armenr/c186bcaa501235be36653f59d3d7c622 to your computer and use it in GitHub Desktop.

Select an option

Save armenr/c186bcaa501235be36653f59d3d7c622 to your computer and use it in GitHub Desktop.
hyprland/hyprlock + Nvidia "Oopsie Daisy" fix
#!/usr/bin/env bash
#
# hypr-nvidia-fix.sh
#
# Originally posted:
#
# Check / apply / revert the fixes that make hyprlock recoverable after
# suspend/resume on NVIDIA GPUs:
# 1. Hyprland: misc:allow_session_lock_restore = true (sourced conf)
# 2. NVIDIA modprobe: NVreg_PreserveVideoMemoryAllocations=1
# 3. systemd: nvidia-{suspend,hibernate,resume}.service enabled
# 4. Initramfs: mkinitcpio -P (or dracut) so #2 actually takes effect
#
# Usage: hypr-nvidia-fix.sh {check|apply|revert} [--no-regen]
#
# Safe to re-run. Tracks its own changes in $STATE_DIR so revert only undoes
# what this script applied; manually-configured state is left alone.
set -euo pipefail
# ---- Paths --------------------------------------------------------------
HYPR_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/hypr"
HYPR_CONF="$HYPR_DIR/hyprland.conf"
HYPR_FIX_CONF="$HYPR_DIR/conf/nvidia-session-lock-fix.conf"
MODPROBE_CONF="/etc/modprobe.d/nvidia-power-management.conf"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hypr-nvidia-fix"
BACKUP_DIR="$STATE_DIR/backups"
SOURCE_LINE="source = $HYPR_FIX_CONF"
SENTINEL_BEGIN="# hypr-nvidia-fix begin"
SENTINEL_END="# hypr-nvidia-fix end"
MODPROBE_LINE="options nvidia NVreg_PreserveVideoMemoryAllocations=1"
NVIDIA_SERVICES=(nvidia-suspend.service nvidia-hibernate.service nvidia-resume.service)
SUDO=""
[[ $EUID -ne 0 ]] && SUDO="sudo"
# ---- Output helpers -----------------------------------------------------
if [[ -t 1 ]]; then
c_reset=$'\033[0m'
c_bold=$'\033[1m'
c_dim=$'\033[2m'
c_green=$'\033[32m'
c_yellow=$'\033[33m'
c_red=$'\033[31m'
c_cyan=$'\033[36m'
else
c_reset=
c_bold=
c_dim=
c_green=
c_yellow=
c_red=
c_cyan=
fi
step() { printf "\n${c_bold}${c_cyan}== %s ==${c_reset}\n" "$*"; }
ok() { printf " ${c_green}[ OK ]${c_reset} %s\n" "$*"; }
miss() { printf " ${c_yellow}[MISS]${c_reset} %s\n" "$*"; }
add() { printf " ${c_green}[ADD ]${c_reset} %s\n" "$*"; }
undo() { printf " ${c_yellow}[UNDO]${c_reset} %s\n" "$*"; }
skip() { printf " ${c_dim}[SKIP]${c_reset} %s\n" "$*"; }
warn() { printf " ${c_yellow}[WARN]${c_reset} %s\n" "$*"; }
err() { printf " ${c_red}[ERR ]${c_reset} %s\n" "$*" >&2; }
note() { printf " ${c_dim}%s${c_reset}\n" "$*"; }
# ---- State markers ------------------------------------------------------
marker() { echo "$STATE_DIR/$1"; }
mark_set() { mkdir -p "$STATE_DIR" && : > "$(marker "$1")"; }
mark_exists() { [[ -e "$(marker "$1")" ]]; }
mark_unset() { rm -f "$(marker "$1")"; }
# =========================================================================
# 1. Hyprland: allow_session_lock_restore
# =========================================================================
check_hyprland_setting() {
local hit=""
if [[ -d "$HYPR_DIR" ]]; then
hit=$(grep -rhE '^[[:space:]]*allow_session_lock_restore[[:space:]]*=[[:space:]]*(true|1)[[:space:]]*$' \
"$HYPR_DIR" 2> /dev/null || true)
fi
if [[ -n "$hit" ]]; then
ok "allow_session_lock_restore found in Hyprland config tree"
note "match: $(printf '%s' "$hit" | head -n1 | sed 's/^[[:space:]]*//')"
if [[ -f "$HYPR_CONF" ]] && grep -qF "$SOURCE_LINE" "$HYPR_CONF"; then
ok "our source line is present in hyprland.conf"
elif [[ -f "$HYPR_FIX_CONF" ]]; then
warn "fix conf exists but hyprland.conf does not source it"
fi
return 0
fi
miss "allow_session_lock_restore is NOT set anywhere in $HYPR_DIR"
return 1
}
apply_hyprland_setting() {
if check_hyprland_setting > /dev/null 2>&1; then
skip "Hyprland setting already configured"
check_hyprland_setting || true
return 0
fi
if [[ ! -f "$HYPR_CONF" ]]; then
err "Hyprland config not found at $HYPR_CONF — aborting this step"
return 1
fi
mkdir -p "$(dirname "$HYPR_FIX_CONF")"
cat > "$HYPR_FIX_CONF" << 'EOF'
# Managed by hypr-nvidia-fix.sh — do not hand-edit.
# Lets the Hyprland "Oopsie daisy" screen re-spawn hyprlock cleanly after
# a session-lock crash (common on NVIDIA after suspend/resume).
# Ref: https://github.com/hyprwm/Hyprland/discussions/13184
misc {
allow_session_lock_restore = true
}
EOF
add "Created $HYPR_FIX_CONF"
mark_set "hyprland_fix_conf_created"
if ! grep -qF "$SOURCE_LINE" "$HYPR_CONF"; then
mkdir -p "$BACKUP_DIR"
cp -L --preserve=mode,timestamps "$HYPR_CONF" "$BACKUP_DIR/hyprland.conf"
add "Backed up $HYPR_CONF -> $BACKUP_DIR/hyprland.conf"
{
echo ""
echo "$SENTINEL_BEGIN"
echo "$SOURCE_LINE"
echo "$SENTINEL_END"
} >> "$HYPR_CONF"
add "Appended sourced line to $HYPR_CONF (between sentinel tags)"
mark_set "hyprland_conf_modified"
else
skip "source line already in $HYPR_CONF"
fi
}
revert_hyprland_setting() {
if [[ -f "$HYPR_CONF" ]] && grep -qF "$SENTINEL_BEGIN" "$HYPR_CONF"; then
# Remove block between sentinels inclusive
sed -i "/$SENTINEL_BEGIN/,/$SENTINEL_END/d" "$HYPR_CONF"
undo "Removed sentinel block from $HYPR_CONF"
mark_unset "hyprland_conf_modified"
elif mark_exists "hyprland_conf_modified"; then
warn "marker says we edited $HYPR_CONF but sentinels are gone — not touching"
note "backup available at: $BACKUP_DIR/hyprland.conf"
else
skip "no sentinel block in $HYPR_CONF — nothing to undo"
fi
if [[ -f "$HYPR_FIX_CONF" ]]; then
if grep -q "Managed by hypr-nvidia-fix.sh" "$HYPR_FIX_CONF" 2> /dev/null; then
rm -f "$HYPR_FIX_CONF"
undo "Deleted $HYPR_FIX_CONF"
mark_unset "hyprland_fix_conf_created"
else
warn "$HYPR_FIX_CONF exists but not managed by us — leaving alone"
fi
fi
}
# =========================================================================
# 2. NVIDIA modprobe: NVreg_PreserveVideoMemoryAllocations=1
# =========================================================================
check_modprobe() {
local configured=1
if ! grep -qE '^[[:space:]]*options[[:space:]]+nvidia([[:space:]]+.*)?[[:space:]]+NVreg_PreserveVideoMemoryAllocations=1' \
/etc/modprobe.d/*.conf 2> /dev/null; then
configured=0
fi
if [[ $configured -eq 1 ]]; then
ok "NVreg_PreserveVideoMemoryAllocations=1 configured in /etc/modprobe.d/"
else
miss "NVreg_PreserveVideoMemoryAllocations=1 NOT configured in /etc/modprobe.d/"
fi
# Runtime check is informational only — some driver versions don't expose
# this parameter via sysfs even when the option is active. The modprobe.d
# file is the source of truth; initramfs regen makes it take effect.
local runtime="/sys/module/nvidia/parameters/NVreg_PreserveVideoMemoryAllocations"
if [[ -r "$runtime" ]]; then
local val
val=$(cat "$runtime" 2> /dev/null || echo "?")
case "$val" in
Y | 1) ok "runtime sysfs: active ($val)" ;;
N | 0) note "runtime sysfs: $val — regen initramfs and reboot to apply" ;;
*) note "runtime sysfs: value is '$val'" ;;
esac
else
note "runtime sysfs: param not exposed (normal for some driver versions)"
fi
[[ $configured -eq 1 ]]
}
apply_modprobe() {
if check_modprobe > /dev/null 2>&1; then
skip "modprobe option already set"
check_modprobe || true
return 0
fi
mkdir -p "$BACKUP_DIR"
if [[ -f "$MODPROBE_CONF" ]]; then
$SUDO cp -L --preserve=mode,ownership,timestamps "$MODPROBE_CONF" \
"$BACKUP_DIR/nvidia-power-management.conf"
add "Backed up $MODPROBE_CONF"
mark_set "modprobe_existed_before"
fi
add "Writing $MODPROBE_CONF (requires $SUDO)"
printf '%s\n' "$MODPROBE_LINE" | $SUDO tee "$MODPROBE_CONF" > /dev/null
mark_set "modprobe_written"
note "initramfs needs regen to bake this into the early-boot image"
note "step 4 handles that unless you passed --no-regen"
}
# =========================================================================
# 4. Initramfs regeneration
# =========================================================================
# Runs the same command pacman runs automatically on kernel/nvidia updates.
# No-op if the modprobe step didn't actually change anything.
detect_initramfs_tool() {
if command -v mkinitcpio > /dev/null 2>&1; then
echo "mkinitcpio"
elif command -v dracut > /dev/null 2>&1; then
echo "dracut"
else
echo "none"
fi
}
check_initramfs() {
local tool
tool=$(detect_initramfs_tool)
case "$tool" in
mkinitcpio) ok "mkinitcpio detected ($(mkinitcpio --version 2> /dev/null | awk '{print $2}' | head -1))" ;;
dracut) ok "dracut detected" ;;
none) miss "no supported initramfs generator found" ;;
esac
if [[ -f "$STATE_DIR/last_regen_at" ]]; then
note "last regen by this script: $(cat "$STATE_DIR/last_regen_at")"
fi
}
apply_initramfs_regen() {
local tool
tool=$(detect_initramfs_tool)
if [[ "$tool" == "none" ]]; then
err "no initramfs tool found — skipping"
note "install mkinitcpio (or dracut) and regen manually before reboot"
return 1
fi
if [[ "${SKIP_REGEN:-0}" == "1" ]]; then
warn "--no-regen passed; skipping initramfs regen"
note "REMEMBER: sudo $tool $([[ $tool == mkinitcpio ]] && echo '-P' || echo '--regenerate-all --force')"
note "must be run before reboot or the modprobe option will not apply"
return 0
fi
# Skip if nothing actually changed in this session AND a prior regen is recorded
if ! mark_exists "modprobe_written" && [[ -f "$STATE_DIR/last_regen_at" ]]; then
skip "modprobe unchanged and a prior regen is on record"
return 0
fi
local logfile="$STATE_DIR/${tool}.log"
add "Running: sudo $tool (logging to $logfile)"
note "this takes 30-90s and may print firmware/hook warnings — normal"
local rc=0
case "$tool" in
mkinitcpio)
$SUDO mkinitcpio -P 2>&1 | tee "$logfile"
rc="${PIPESTATUS[0]}"
;;
dracut)
$SUDO dracut --regenerate-all --force 2>&1 | tee "$logfile"
rc="${PIPESTATUS[0]}"
;;
esac
if [[ "$rc" -eq 0 ]]; then
ok "initramfs regenerated successfully"
date -Iseconds > "$STATE_DIR/last_regen_at"
mark_set "initramfs_regenerated"
else
err "$tool exited with status $rc"
note "prior initramfs image is still in place; system remains bootable"
note "review the tail of: $logfile"
return 1
fi
}
revert_initramfs_regen() {
local tool
tool=$(detect_initramfs_tool)
if [[ "$tool" == "none" ]]; then
skip "no initramfs tool — nothing to regen"
return 0
fi
if [[ "${SKIP_REGEN:-0}" == "1" ]]; then
warn "--no-regen passed; skipping regen on revert"
note "run the regen manually before next reboot to complete the revert"
return 0
fi
# Only regen on revert if we actually changed the modprobe file
if ! mark_exists "modprobe_written" 2> /dev/null && [[ ! -f "$STATE_DIR/last_regen_at" ]]; then
# modprobe_written marker was cleared by revert_modprobe already; check if
# this run actually reverted anything observable by checking last_regen_at
:
fi
local logfile="$STATE_DIR/${tool}.revert.log"
add "Running: sudo $tool to bake the revert into initramfs (logging to $logfile)"
local rc=0
case "$tool" in
mkinitcpio)
$SUDO mkinitcpio -P 2>&1 | tee "$logfile"
rc="${PIPESTATUS[0]}"
;;
dracut)
$SUDO dracut --regenerate-all --force 2>&1 | tee "$logfile"
rc="${PIPESTATUS[0]}"
;;
esac
if [[ "$rc" -eq 0 ]]; then
ok "initramfs regenerated after revert"
mark_unset "initramfs_regenerated"
else
err "$tool exited with status $rc — prior initramfs still in place"
note "review the tail of: $logfile"
fi
}
revert_modprobe() {
if ! mark_exists "modprobe_written"; then
skip "no modprobe change to revert"
return 0
fi
if mark_exists "modprobe_existed_before" && [[ -f "$BACKUP_DIR/nvidia-power-management.conf" ]]; then
$SUDO cp -L --preserve=mode,ownership,timestamps \
"$BACKUP_DIR/nvidia-power-management.conf" "$MODPROBE_CONF"
undo "Restored $MODPROBE_CONF from backup"
else
$SUDO rm -f "$MODPROBE_CONF"
undo "Removed $MODPROBE_CONF (we created it)"
fi
mark_unset "modprobe_written"
mark_unset "modprobe_existed_before"
warn "Regenerate initramfs and reboot to fully restore previous kernel module state"
}
# =========================================================================
# 3. NVIDIA systemd services
# =========================================================================
check_services() {
local all=1
for svc in "${NVIDIA_SERVICES[@]}"; do
if ! systemctl list-unit-files "$svc" > /dev/null 2>&1; then
miss "$svc not installed on this system"
all=0
continue
fi
if systemctl is-enabled --quiet "$svc" 2> /dev/null; then
ok "$svc is enabled"
else
local state
state=$(systemctl is-enabled "$svc" 2> /dev/null || true)
[[ -z "$state" ]] && state="unknown"
miss "$svc is NOT enabled (state: $state)"
all=0
fi
done
[[ $all -eq 1 ]]
}
apply_services() {
for svc in "${NVIDIA_SERVICES[@]}"; do
if ! systemctl list-unit-files "$svc" > /dev/null 2>&1; then
err "$svc is not installed — skipping (install nvidia-utils?)"
continue
fi
if systemctl is-enabled --quiet "$svc" 2> /dev/null; then
skip "$svc already enabled"
continue
fi
add "Enabling $svc (requires $SUDO)"
if $SUDO systemctl enable "$svc"; then
mark_set "service_enabled.$svc"
else
err "Failed to enable $svc"
fi
done
}
revert_services() {
for svc in "${NVIDIA_SERVICES[@]}"; do
if ! mark_exists "service_enabled.$svc"; then
skip "we did not enable $svc — leaving alone"
continue
fi
undo "Disabling $svc (requires $SUDO)"
if $SUDO systemctl disable "$svc"; then
mark_unset "service_enabled.$svc"
else
err "Failed to disable $svc"
fi
done
}
# =========================================================================
# Dispatch
# =========================================================================
cmd_check() {
printf '%sHyprland + NVIDIA session-lock fix — status%s\n' "$c_bold" "$c_reset"
note "state dir: $STATE_DIR"
step "1. Hyprland: misc:allow_session_lock_restore"
check_hyprland_setting || true
step "2. NVIDIA modprobe: NVreg_PreserveVideoMemoryAllocations=1"
check_modprobe || true
step "3. NVIDIA suspend/resume systemd services"
check_services || true
step "4. Initramfs generator"
check_initramfs || true
if [[ -f "$STATE_DIR/applied_at" ]]; then
printf "\n"
note "last apply run: $(cat "$STATE_DIR/applied_at")"
fi
}
cmd_apply() {
printf '%sHyprland + NVIDIA session-lock fix — applying%s\n' "$c_bold" "$c_reset"
mkdir -p "$STATE_DIR" "$BACKUP_DIR"
note "state dir: $STATE_DIR"
[[ "${SKIP_REGEN:-0}" == "1" ]] && note "regen: DISABLED via --no-regen"
step "1. Hyprland: misc:allow_session_lock_restore"
apply_hyprland_setting
step "2. NVIDIA modprobe: NVreg_PreserveVideoMemoryAllocations=1"
apply_modprobe
step "3. NVIDIA suspend/resume systemd services"
apply_services
step "4. Regenerate initramfs"
apply_initramfs_regen || true
date -Iseconds > "$STATE_DIR/applied_at"
printf '\n%sDone.%s Next steps:\n' "$c_bold" "$c_reset"
note "1. Reload Hyprland: hyprctl reload"
note "2. Reboot to activate the kernel module option"
note "3. Verify afterwards: $0 check"
}
cmd_revert() {
printf '%sHyprland + NVIDIA session-lock fix — reverting%s\n' "$c_bold" "$c_reset"
if [[ ! -d "$STATE_DIR" ]]; then
warn "no state dir at $STATE_DIR — nothing to revert (or state lost)"
return 0
fi
note "state dir: $STATE_DIR"
[[ "${SKIP_REGEN:-0}" == "1" ]] && note "regen: DISABLED via --no-regen"
step "1. Hyprland: misc:allow_session_lock_restore"
revert_hyprland_setting
step "2. NVIDIA modprobe"
revert_modprobe
step "3. NVIDIA suspend/resume systemd services"
revert_services
step "4. Regenerate initramfs (to bake in reverted modprobe)"
revert_initramfs_regen || true
printf '\n%sRevert complete.%s\n' "$c_bold" "$c_reset"
note "Reload Hyprland: hyprctl reload"
note "Reboot to fully restore previous kernel module state"
note "Backups kept in: $BACKUP_DIR"
}
usage() {
cat << EOF
Usage: $(basename "$0") {check|apply|revert} [--no-regen]
check Read-only status of all 4 fixes
apply Apply missing fixes; originals backed up to:
$BACKUP_DIR
revert Undo exactly what a previous apply did (uses state markers
and sentinel tags so manual config is never touched)
--no-regen Skip the initramfs regen step in apply/revert. You MUST run
'sudo mkinitcpio -P' (or equivalent) yourself before reboot.
State: $STATE_DIR
EOF
}
# ---- Parse args ----
SKIP_REGEN=0
CMD=""
for arg in "$@"; do
case "$arg" in
--no-regen) SKIP_REGEN=1 ;;
check | apply | revert) CMD="$arg" ;;
-h | --help | "")
usage
exit 0
;;
*)
usage
exit 2
;;
esac
done
case "$CMD" in
check) cmd_check ;;
apply) cmd_apply ;;
revert) cmd_revert ;;
"")
usage
exit 2
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment