TrulyInsane1 icon

Proxmox - Disable laptop lid and sleep

TrulyInsane1 | PRO | 01/04/26 10:56:42 PM UTC | 0 ⭐ | 19341 👁️ | Never ⏰ | []
Bash |

7.35 KB

|

None

|

0 👍

/

0 👎

#!/usr/bin/env bash
set -euo pipefail
 
# -----------------------------------------------------------------------------
# disable-lid-sleep-proxmox.sh
#
# Purpose:
#   Configure a Proxmox/Debian host (especially laptops) to ignore the lid switch
#   and prevent any suspend/hibernate when the lid is closed.
#
# What it does:
#   1) Configures systemd-logind to ignore lid close events
#   2) Masks sleep/suspend/hibernate targets (hard stop)
#   3) Disables console blanking via GRUB (consoleblank=0)
#   4) Installs a systemd oneshot service to disable setterm blanking/powersave
#
# Safe features:
#   - Makes backups before editing files
#   - Idempotent: re-running won't duplicate config
#   - Verifies systemd + grub tools exist
#
# Usage:
#   sudo ./disable-lid-sleep-proxmox.sh
#
# Optional:
#   sudo ./disable-lid-sleep-proxmox.sh --undo
# -----------------------------------------------------------------------------
 
SCRIPT_NAME="$(basename "$0")"
STAMP="$(date +%Y%m%d-%H%M%S)"
 
require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    echo "ERROR: Must be run as root. Use: sudo ./${SCRIPT_NAME}"
    exit 1
  fi
}
 
has_cmd() { command -v "$1" >/dev/null 2>&1; }
 
backup_file() {
  local f="$1"
  if [[ -f "$f" ]]; then
    cp -a "$f" "${f}.bak.${STAMP}"
    echo "Backup created: ${f}.bak.${STAMP}"
  fi
}
 
ensure_line_in_file() {
  local file="$1"
  local line="$2"
 
  mkdir -p "$(dirname "$file")"
  touch "$file"
 
  if ! grep -Fxq "$line" "$file"; then
    echo "$line" >> "$file"
    echo "Added to $file: $line"
  else
    echo "Already present in $file: $line"
  fi
}
 
set_or_replace_keyvalue() {
  # Sets key=value in a config file (logind.conf style), uncommenting if needed.
  local file="$1"
  local key="$2"
  local value="$3"
 
  mkdir -p "$(dirname "$file")"
  touch "$file"
 
  if grep -Eq "^[#[:space:]]*${key}=" "$file"; then
    sed -i -E "s|^[#[:space:]]*(${key}=).*|\1${value}|" "$file"
    echo "Set $key=$value in $file"
  else
    echo "${key}=${value}" >> "$file"
    echo "Appended $key=$value to $file"
  fi
}
 
mask_sleep_targets() {
  local targets=(
    sleep.target
    suspend.target
    hibernate.target
    hybrid-sleep.target
  )
 
  for t in "${targets[@]}"; do
    systemctl mask "$t" >/dev/null 2>&1 || true
    echo "Masked: $t"
  done
}
 
unmask_sleep_targets() {
  local targets=(
    sleep.target
    suspend.target
    hibernate.target
    hybrid-sleep.target
  )
 
  for t in "${targets[@]}"; do
    systemctl unmask "$t" >/dev/null 2>&1 || true
    echo "Unmasked: $t"
  done
}
 
install_dpms_service() {
  local svc="/etc/systemd/system/disable-dpms.service"
 
  backup_file "$svc"
 
  cat > "$svc" <<'EOF'
[Unit]
Description=Disable monitor sleep / console blanking on boot
 
[Service]
Type=oneshot
ExecStart=/usr/bin/setterm -blank 0 -powersave off -powerdown 0
 
[Install]
WantedBy=multi-user.target
EOF
 
  systemctl daemon-reload
  systemctl enable --now disable-dpms.service
  echo "Installed & enabled: disable-dpms.service"
}
 
remove_dpms_service() {
  local svc="/etc/systemd/system/disable-dpms.service"
  if [[ -f "$svc" ]]; then
    systemctl disable --now disable-dpms.service >/dev/null 2>&1 || true
    rm -f "$svc"
    systemctl daemon-reload
    echo "Removed: disable-dpms.service"
  else
    echo "disable-dpms.service not found; skipping."
  fi
}
 
configure_logind() {
  local f="/etc/systemd/logind.conf"
  backup_file "$f"
 
  set_or_replace_keyvalue "$f" "HandleLidSwitch" "ignore"
  set_or_replace_keyvalue "$f" "HandleLidSwitchExternalPower" "ignore"
  set_or_replace_keyvalue "$f" "HandleLidSwitchDocked" "ignore"
  set_or_replace_keyvalue "$f" "LidSwitchIgnoreInhibited" "no"
 
  systemctl restart systemd-logind
  echo "Restarted: systemd-logind"
}
 
undo_logind() {
  # We don't "restore defaults" perfectly (depends on distro), but we do remove
  # the explicit settings by commenting them out if present.
  local f="/etc/systemd/logind.conf"
  if [[ -f "$f" ]]; then
    backup_file "$f"
    sed -i -E \
      -e 's|^(HandleLidSwitch=)|#\1|' \
      -e 's|^(HandleLidSwitchExternalPower=)|#\1|' \
      -e 's|^(HandleLidSwitchDocked=)|#\1|' \
      -e 's|^(LidSwitchIgnoreInhibited=)|#\1|' \
      "$f"
    systemctl restart systemd-logind
    echo "Commented out lid settings in $f and restarted logind."
  else
    echo "$f not found; skipping."
  fi
}
 
configure_grub_consoleblank() {
  if ! has_cmd update-grub && ! has_cmd grub-mkconfig; then
    echo "WARNING: Neither update-grub nor grub-mkconfig found; skipping GRUB step."
    return 0
  fi
 
  local f="/etc/default/grub.d/nolidsleep.cfg"
  mkdir -p /etc/default/grub.d
  backup_file "$f"
 
  # Ensure the commandline includes consoleblank=0 without duplicating.
  if [[ -f "$f" ]] && grep -q 'consoleblank=0' "$f"; then
    echo "GRUB consoleblank already configured in $f"
  else
    cat > "$f" <<'EOF'
# Keep the local console from blanking (useful on laptop Proxmox installs)
GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT consoleblank=0"
EOF
    echo "Wrote GRUB config snippet: $f"
  fi
 
  if has_cmd update-grub; then
    update-grub
    echo "Ran: update-grub"
  else
    # Fallback (uncommon on Debian/Proxmox, but harmless)
    grub-mkconfig -o /boot/grub/grub.cfg
    echo "Ran: grub-mkconfig -o /boot/grub/grub.cfg"
  fi
}
 
undo_grub_consoleblank() {
  local f="/etc/default/grub.d/nolidsleep.cfg"
  if [[ -f "$f" ]]; then
    backup_file "$f"
    rm -f "$f"
    echo "Removed: $f"
 
    if has_cmd update-grub; then
      update-grub
      echo "Ran: update-grub"
    elif has_cmd grub-mkconfig; then
      grub-mkconfig -o /boot/grub/grub.cfg
      echo "Ran: grub-mkconfig -o /boot/grub/grub.cfg"
    else
      echo "WARNING: No grub update tool found; GRUB not regenerated."
    fi
  else
    echo "$f not found; skipping."
  fi
}
 
show_status() {
  echo
  echo "---- STATUS CHECKS ----"
  echo "logind.conf lid settings:"
  grep -E '^(#\s*)?(HandleLidSwitch|HandleLidSwitchExternalPower|HandleLidSwitchDocked|LidSwitchIgnoreInhibited)=' \
    /etc/systemd/logind.conf 2>/dev/null || echo "(no matching lines found)"
 
  echo
  echo "Masked sleep targets:"
  for t in sleep.target suspend.target hibernate.target hybrid-sleep.target; do
    systemctl is-enabled "$t" 2>/dev/null || true
  done
 
  echo
  echo "DPMS service:"
  systemctl status disable-dpms.service --no-pager -l 2>/dev/null || echo "(service not installed)"
 
  echo
  echo "GRUB snippet:"
  if [[ -f /etc/default/grub.d/nolidsleep.cfg ]]; then
    cat /etc/default/grub.d/nolidsleep.cfg
  else
    echo "(not present)"
  fi
  echo "-----------------------"
  echo
}
 
main() {
  require_root
 
  local mode="apply"
  if [[ "${1:-}" == "--undo" ]]; then
    mode="undo"
  fi
 
  if [[ "$mode" == "apply" ]]; then
    echo "Applying lid-close ignore + anti-sleep configuration..."
    configure_logind
    mask_sleep_targets
    configure_grub_consoleblank
    install_dpms_service
    show_status
    echo "DONE. Reboot recommended to ensure GRUB consoleblank=0 takes effect:"
    echo "  reboot"
  else
    echo "Undoing configuration..."
    undo_logind
    unmask_sleep_targets
    undo_grub_consoleblank
    remove_dpms_service
    show_status
    echo "DONE. Reboot recommended:"
    echo "  reboot"
  fi
}
 
main "$@"
 

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  • TrulyInsane1 icon
    02/25/26 08:52:48 PM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎