Created
March 29, 2026 14:02
-
-
Save dertin/15f4ca60b7cd51a1fb073824b0e31125 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -Eeuo pipefail | |
| IFS=$'\n\t' | |
| # Acer Aspire 14 AI / Ubuntu 24.04 helper | |
| # Installs: | |
| # - DKMS-managed acer-wmi-battery kernel module | |
| # - Secure Boot MOK setup (if needed) | |
| # - Battery Health Charging GNOME extension manager package | |
| # - Battery Health Charging extension via GNOME Shell DBus (when possible) | |
| # | |
| # Safe to re-run. If Secure Boot enrollment is needed, the script will stop, | |
| # ask you to reboot, and you can run it again after enrolling the MOK key. | |
| SCRIPT_NAME="$(basename "$0")" | |
| BASE_VERSION="${BASE_VERSION:-0.2.0}" | |
| REPO_URL="https://github.com/frederik-h/acer-wmi-battery.git" | |
| REPO_REF="${REPO_REF:-v${BASE_VERSION}}" | |
| PKG_NAME="acer_wmi_battery" | |
| PKG_VERSION="${PKG_VERSION:-${BASE_VERSION}}" | |
| MODULE_OUTPUT_NAME="acer-wmi-battery" # upstream .ko basename | |
| MODULE_LOAD_NAME="acer_wmi_battery" # modprobe/module alias | |
| SYSFS_HEALTH_MODE="/sys/bus/wmi/drivers/acer-wmi-battery/health_mode" | |
| EXT_UUID="Battery-Health-Charging@maniacx.github.com" | |
| ENABLE_HEALTH_MODE="${ENABLE_HEALTH_MODE:-1}" | |
| REAL_USER="${SUDO_USER:-${USER:-$(id -un)}}" | |
| REAL_UID="$(id -u "$REAL_USER")" | |
| REAL_HOME="$(getent passwd "$REAL_USER" | cut -d: -f6)" | |
| XDG_RUNTIME_DIR_USER="/run/user/${REAL_UID}" | |
| USER_BUS="${XDG_RUNTIME_DIR_USER}/bus" | |
| WORKDIR="$(mktemp -d)" | |
| KEEP_WORKDIR="${KEEP_WORKDIR:-0}" | |
| cleanup() { | |
| if [[ "${KEEP_WORKDIR}" != "1" && -d "${WORKDIR}" ]]; then | |
| rm -rf "${WORKDIR}" | |
| fi | |
| } | |
| trap cleanup EXIT | |
| log() { printf '\033[1;32m[INFO]\033[0m %s\n' "$*"; } | |
| warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*" >&2; } | |
| err() { printf '\033[1;31m[ERR ]\033[0m %s\n' "$*" >&2; } | |
| die() { | |
| err "$*" | |
| exit 1 | |
| } | |
| need_root() { | |
| if [[ ${EUID} -ne 0 ]]; then | |
| die "Run with sudo: sudo bash ${SCRIPT_NAME}" | |
| fi | |
| } | |
| need_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" | |
| } | |
| check_os() { | |
| [[ -r /etc/os-release ]] || die "/etc/os-release not found" | |
| # shellcheck disable=SC1091 | |
| source /etc/os-release | |
| if [[ "${ID:-}" != "ubuntu" ]]; then | |
| die "This script is written for Ubuntu. Detected ID=${ID:-unknown}" | |
| fi | |
| if [[ "${VERSION_ID:-}" != "24.04" ]]; then | |
| warn "This script was written for Ubuntu 24.04. Detected VERSION_ID=${VERSION_ID:-unknown}." | |
| warn "Continuing anyway because the flow is likely still valid on nearby Ubuntu releases." | |
| fi | |
| } | |
| install_packages() { | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update | |
| apt-get install -y \ | |
| build-essential \ | |
| dkms \ | |
| git \ | |
| mokutil \ | |
| openssl \ | |
| rsync \ | |
| shim-signed \ | |
| linux-headers-"$(uname -r)" \ | |
| gnome-shell-extension-manager | |
| } | |
| secure_boot_enabled() { | |
| mokutil --sb-state 2>/dev/null | grep -qi 'SecureBoot enabled' | |
| } | |
| ensure_mok_files() { | |
| if [[ ! -f /var/lib/shim-signed/mok/MOK.der || ! -f /var/lib/shim-signed/mok/MOK.priv ]]; then | |
| log "Generating a Machine Owner Key (MOK) for third-party kernel modules..." | |
| update-secureboot-policy --new-key | |
| fi | |
| [[ -f /var/lib/shim-signed/mok/MOK.der ]] || die "MOK.der was not created" | |
| [[ -f /var/lib/shim-signed/mok/MOK.priv ]] || die "MOK.priv was not created" | |
| } | |
| mok_fingerprint() { | |
| openssl x509 -inform DER -in /var/lib/shim-signed/mok/MOK.der -noout -fingerprint -sha1 \ | |
| | sed 's/.*=//' | tr 'A-F' 'a-f' | |
| } | |
| mok_enrolled() { | |
| local fp | |
| fp="$(mok_fingerprint 2>/dev/null || true)" | |
| [[ -n "$fp" ]] || return 1 | |
| mokutil --list-enrolled 2>/dev/null | tr 'A-F' 'a-f' | grep -Fq "$fp" | |
| } | |
| prepare_secure_boot() { | |
| if ! secure_boot_enabled; then | |
| log "Secure Boot is disabled. No MOK enrollment is needed." | |
| return 0 | |
| fi | |
| log "Secure Boot is enabled. Verifying MOK setup for third-party kernel modules..." | |
| ensure_mok_files | |
| if mok_enrolled; then | |
| log "MOK is already enrolled. Good." | |
| return 0 | |
| fi | |
| warn "Your MOK exists locally but is not enrolled in firmware yet." | |
| warn "Importing it now. After that, you must reboot and finish enrollment in MOK Manager." | |
| mokutil --import /var/lib/shim-signed/mok/MOK.der | |
| cat <<'MSG' | |
| A reboot is now required. | |
| At the next boot: | |
| 1. Open MOK Manager | |
| 2. Choose 'Enroll MOK' | |
| 3. Confirm the key | |
| 4. Enter the password you just set | |
| 5. Reboot back into Ubuntu | |
| Then re-run this same script. | |
| MSG | |
| exit 20 | |
| } | |
| stage_source() { | |
| local src="${WORKDIR}/acer-wmi-battery" | |
| log "Cloning ${REPO_URL} (${REPO_REF})..." | |
| git clone --depth 1 --branch "${REPO_REF}" "${REPO_URL}" "$src" | |
| log "Installing source into /usr/src/${PKG_NAME}-${PKG_VERSION} for DKMS..." | |
| rm -rf "/usr/src/${PKG_NAME}-${PKG_VERSION}" | |
| mkdir -p "/usr/src/${PKG_NAME}-${PKG_VERSION}" | |
| rsync -a --delete "$src/" "/usr/src/${PKG_NAME}-${PKG_VERSION}/" | |
| } | |
| write_dkms_conf() { | |
| cat > "/usr/src/${PKG_NAME}-${PKG_VERSION}/dkms.conf" <<EOF_DKMS | |
| PACKAGE_NAME="${PKG_NAME}" | |
| PACKAGE_VERSION="${PKG_VERSION}" | |
| BUILT_MODULE_NAME[0]="${MODULE_OUTPUT_NAME}" | |
| DEST_MODULE_LOCATION[0]="/updates/dkms" | |
| AUTOINSTALL="yes" | |
| MAKE[0]="make -C /lib/modules/\${kernelver}/build M=\${dkms_tree}/\${PACKAGE_NAME}/\${PACKAGE_VERSION}/build modules" | |
| CLEAN="make -C /lib/modules/\${kernelver}/build M=\${dkms_tree}/\${PACKAGE_NAME}/\${PACKAGE_VERSION}/build clean" | |
| EOF_DKMS | |
| } | |
| configure_module_defaults() { | |
| log "Configuring persistent module options and autoload..." | |
| cat > /etc/modprobe.d/acer-wmi-battery.conf <<EOF_MODPROBE | |
| options ${MODULE_LOAD_NAME} enable_health_mode=${ENABLE_HEALTH_MODE} | |
| EOF_MODPROBE | |
| cat > /etc/modules-load.d/acer-wmi-battery.conf <<EOF_LOAD | |
| ${MODULE_LOAD_NAME} | |
| EOF_LOAD | |
| } | |
| reinstall_dkms_module() { | |
| if dkms status -m "${PKG_NAME}" -v "${PKG_VERSION}" >/dev/null 2>&1; then | |
| log "Removing existing DKMS registration for a clean reinstall..." | |
| dkms remove -m "${PKG_NAME}" -v "${PKG_VERSION}" --all || true | |
| fi | |
| log "Registering module in DKMS..." | |
| dkms add -m "${PKG_NAME}" -v "${PKG_VERSION}" | |
| log "Building DKMS module for kernel $(uname -r)..." | |
| dkms build -m "${PKG_NAME}" -v "${PKG_VERSION}" | |
| log "Installing DKMS module for kernel $(uname -r)..." | |
| dkms install -m "${PKG_NAME}" -v "${PKG_VERSION}" | |
| depmod -a "$(uname -r)" | |
| } | |
| module_signer() { | |
| modinfo -F signer "${MODULE_LOAD_NAME}" 2>/dev/null || true | |
| } | |
| load_module() { | |
| log "Loading module ${MODULE_LOAD_NAME}..." | |
| modprobe "${MODULE_LOAD_NAME}" | |
| } | |
| verify_health_mode() { | |
| if [[ -e "${SYSFS_HEALTH_MODE}" ]]; then | |
| local current | |
| current="$(cat "${SYSFS_HEALTH_MODE}" 2>/dev/null || true)" | |
| log "health_mode is visible at ${SYSFS_HEALTH_MODE}" | |
| log "Current health_mode value: ${current:-unknown} (1 = 80%, 0 = 100%)" | |
| else | |
| warn "${SYSFS_HEALTH_MODE} is still missing." | |
| warn "Check: dmesg | tail -n 100 ; modinfo ${MODULE_LOAD_NAME} ; dkms status" | |
| fi | |
| } | |
| run_as_real_user() { | |
| local cmd=("$@") | |
| sudo -H -u "${REAL_USER}" env \ | |
| XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR_USER}" \ | |
| DBUS_SESSION_BUS_ADDRESS="unix:path=${USER_BUS}" \ | |
| HOME="${REAL_HOME}" \ | |
| "${cmd[@]}" | |
| } | |
| install_battery_health_charging() { | |
| log "Preparing Battery Health Charging GNOME extension integration..." | |
| if ! command -v busctl >/dev/null 2>&1; then | |
| warn "busctl is not available. Skipping automatic GNOME extension install." | |
| return 0 | |
| fi | |
| if ! command -v gnome-extensions >/dev/null 2>&1; then | |
| warn "gnome-extensions CLI is not available. Skipping automatic extension install." | |
| warn "You still have Extension Manager installed, so you can install it graphically." | |
| return 0 | |
| fi | |
| if [[ ! -S "${USER_BUS}" ]]; then | |
| warn "No user DBus session was found at ${USER_BUS}." | |
| warn "Skipping automatic extension install. Open Extension Manager later and install '${EXT_UUID}'." | |
| return 0 | |
| fi | |
| if run_as_real_user gnome-extensions info "${EXT_UUID}" >/dev/null 2>&1; then | |
| log "GNOME extension already installed: ${EXT_UUID}" | |
| else | |
| log "Installing Battery Health Charging from the GNOME Extensions service..." | |
| if ! run_as_real_user busctl --user call \ | |
| org.gnome.Shell.Extensions \ | |
| /org/gnome/Shell/Extensions \ | |
| org.gnome.Shell.Extensions \ | |
| InstallRemoteExtension \ | |
| s "${EXT_UUID}"; then | |
| warn "Automatic extension install failed." | |
| warn "Open Extension Manager and search for 'Battery Health Charging'." | |
| return 0 | |
| fi | |
| fi | |
| if run_as_real_user gnome-extensions enable "${EXT_UUID}"; then | |
| log "GNOME extension enabled: ${EXT_UUID}" | |
| else | |
| warn "The extension was installed but could not be enabled automatically." | |
| warn "Open Extension Manager and enable it manually." | |
| fi | |
| } | |
| show_summary() { | |
| cat <<EOF_SUMMARY | |
| Done. | |
| What this script configured: | |
| - DKMS source: /usr/src/${PKG_NAME}-${PKG_VERSION} | |
| - Module autoload: /etc/modules-load.d/acer-wmi-battery.conf | |
| - Default boot mode: /etc/modprobe.d/acer-wmi-battery.conf | |
| - GNOME helper app: gnome-shell-extension-manager | |
| Useful checks: | |
| dkms status | |
| modinfo ${MODULE_LOAD_NAME} | grep -E 'filename|signer|version' | |
| cat ${SYSFS_HEALTH_MODE} | |
| mokutil --sb-state | |
| Expected behavior after future kernel updates: | |
| - DKMS rebuilds the module automatically | |
| - Ubuntu signs the rebuilt module using your enrolled MOK when Secure Boot is enabled | |
| - The module autoloads at boot | |
| - enable_health_mode=${ENABLE_HEALTH_MODE} keeps the Acer battery limit enabled at module load time | |
| If the GNOME extension was not installed automatically: | |
| - Open 'Extension Manager' | |
| - Search for: Battery Health Charging | |
| - Install and enable it | |
| EOF_SUMMARY | |
| } | |
| main() { | |
| need_root | |
| check_os | |
| need_cmd apt-get | |
| install_packages | |
| for cmd in git dkms mokutil openssl rsync modprobe modinfo sudo; do | |
| need_cmd "$cmd" | |
| done | |
| prepare_secure_boot | |
| stage_source | |
| write_dkms_conf | |
| configure_module_defaults | |
| reinstall_dkms_module | |
| load_module | |
| verify_health_mode | |
| install_battery_health_charging | |
| if secure_boot_enabled; then | |
| log "Current signer: $(module_signer || true)" | |
| fi | |
| show_summary | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment