Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save dertin/15f4ca60b7cd51a1fb073824b0e31125 to your computer and use it in GitHub Desktop.

Select an option

Save dertin/15f4ca60b7cd51a1fb073824b0e31125 to your computer and use it in GitHub Desktop.
#!/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