diff --git a/README.md b/README.md index c826611..736349c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # maclaunch -Lists and controls your macOS startup items and their startup policy. +Lists and controls all your macOS startup items and their startup policy. Take back control of your macOS system! @@ -31,8 +31,13 @@ Take back control of your macOS system! ## How does it work? -Lists XML/json/binary plist files in LaunchAgents and LaunchDaemons folders which are loaded by launchctl. -When disabling an item, it uses launchctl to natively stop loading that service. +maclaunch will list 3 distinct types of entries on your macOS system that can be persistently installed: + +1. Configuration files for LaunchAgents and LaunchDaemons which are loaded by launchctl. +2. Kernel extensions loaded in the kernel. +3. System extensions loaded in userspace. + +When disabling an item, it uses `launchctl`, `kextutil` or `systemextensionsctl` to natively stop loading that service. It does **not** alter the contents in any way or moves the file, so it should work with practically any service. The name you provide can either be specific to that service or function as a filter to work on multiple services simultaneously. diff --git a/maclaunch.sh b/maclaunch.sh index c76c8f0..b24c082 100755 --- a/maclaunch.sh +++ b/maclaunch.sh @@ -1,8 +1,5 @@ #!/usr/bin/env bash -#set -e -#set -x - startup_dirs=(/Library/LaunchAgents /Library/LaunchDaemons ~/Library/LaunchAgents ~/Library/LaunchDaemons) system_dirs=(/System/Library/LaunchAgents /System/Library/LaunchDaemons) @@ -12,6 +9,10 @@ YELLOW='\033[1;33m' NC='\033[0m' BOLD='\033[1m' +# +#-------------------------------------------------------------------------------------------------------------------------------------- +# + function join_by { local IFS="$1"; shift; echo "$*"; } function usage { @@ -50,7 +51,182 @@ function getScriptUser { grep 'UserName' -C1 "$scriptPath" | tail -n1 | cut -d '>' -f 2 | cut -d '<' -f 1 } -function listItems { +function getKernelExtensions { + kmutil showloaded --no-kernel-components --list-only --sort --show loaded 2>/dev/null | tr -s ' ' | grep -v 'com\.apple\.' +} + +function listKernelExtensions { + local filter="$1" + + getKernelExtensions | while IFS= read -r kextLine; do + + kextLoaded="$(echo "$kextLine" | cut -d ' ' -f 3)" + kextName="$(echo "$kextLine" | cut -d ' ' -f 7)" + kextVersion="$(echo "$kextLine" | grep -o '\((.*)\)')" + + if [ "$filter" == "disabled" ] && [ "$kextLoaded" != "0" ]; then + continue + fi + + if [ "$filter" == "enabled" ] && [ "$kextLoaded" == "0" ]; then + continue + fi + + if [ -n "$filter" ] && [ "$filter" != "system" ] && [ "$filter" != "enabled" ] && [ "$filter" != "disabled" ]; then + if [[ "$kextName" != *"$filter"* ]]; then + continue + fi + fi + + kernelPath="$(kextfind -system-extensions "$kextName" 2>/dev/null)" + if [ -z "$kernelPath" ]; then + kernelPath="n/a" + fi + + local loaded + if [ "$kextLoaded" == "0" ]; then + loaded="${GREEN}${BOLD}disabled${NC}" + else + loaded="${RED}Always${NC}" + fi + + echo -e "${BOLD}> ${kextName}${NC} ${kextVersion}" + echo -e " Type : ${RED}kernel extension${NC}" + echo -e " User : ${RED}root${NC}" + echo -e " Launch: ${loaded}" + echo " File : ${kernelPath}" + + done +} + +function disableKernelExtensions { + local filter="$1" + + getKernelExtensions | while IFS= read -r kextLine; do + + kextLoaded="$(echo "$kextLine" | cut -d ' ' -f 3)" + kextName="$(echo "$kextLine" | cut -d ' ' -f 7)" + + if ! [[ "$kextName" =~ $filter ]]; then + continue + fi + + if [ "$kextLoaded" == "0" ]; then + #error "kernel extension is already unloaded" + continue + fi + + if ! kmutil load -b "$kextName" 1>/dev/null; then + error "could not disable kernel extension" + fi + + echo -e "${GREEN}Disabled ${STRONG}${kextName}${NC}" + done +} + +function enableKernelExtensions { + local filter="$1" + + getKernelExtensions | while IFS= read -r kextLine; do + + kextLoaded="$(echo "$kextLine" | cut -d ' ' -f 3)" + kextName="$(echo "$kextLine" | cut -d ' ' -f 7)" + + if ! [[ "$kextName" =~ $filter ]]; then + continue + fi + + if ! [ "$kextLoaded" == "0" ]; then + #error "kernel extension is already loaded" + continue + fi + + if ! kmutil unload -b "$kextName" 1>/dev/null; then + error "could not disable kernel extension" + fi + + echo -e "${GREEN}Enabled ${STRONG}${kextName}${NC}" + done +} + +function getSystemExtensions { + systemextensionsctl list 2>/dev/null | tail -n+2 | grep -v '^---' | grep -v '^enabled' | tr -s ' ' +} + +function listSystemExtensions { + local filter="$1" + + getSystemExtensions | while IFS= read -r extLine; do + + fullName="$(echo "$extLine" | cut -d$'\t' -f 4)" + extName="$(echo "$fullName" | cut -d ' ' -f 1)" + extVersion="$(echo "$fullName" | grep -o '\((.*)\)')" + + if [ -n "$filter" ] && ! [[ "$extName" =~ $filter ]]; then + continue + fi + + local loaded + if [ "$(echo "$extLine" | cut -d$'\t' -f 2)" == "*" ]; then + loaded="${ORANGE}enabled${NC}" + else + loaded="${GREEN}disabled${NC}" + fi + + echo -e "${BOLD}> ${extName}${NC} ${extVersion}" + echo -e " Type : system extension" + echo -e " User : $(whoami)" + echo -e " Launch: ${loaded}" + echo " File : n/a" + done +} + +function enableSystemExtensions { + local filter="$1" + + getSystemExtensions | while IFS= read -r extLine; do + + extName="$(echo "$extLine" | cut -d$'\t' -f 4 | cut -d ' ' -f 1)" + + if ! [[ "$extName" =~ $filter ]]; then + continue + fi + + if [ "$(echo "$extLine" | cut -d$'\t' -f 2)" == "*" ]; then + # error "this system extension is already enabled" + continue + fi + + #TODO: implement load system extension via CLI + error "enabling system extensions is not yet implemented" + done +} + +function disableSystemExtensions { + local filter="$1" + + getSystemExtensions | while IFS= read -r extLine; do + + extName="$(echo "$extLine" | cut -d$'\t' -f 4 | cut -d ' ' -f 1)" + + if ! [[ "$extName" =~ $filter ]]; then + continue + fi + + if ! [ "$(echo "$extLine" | cut -d$'\t' -f 2)" == "*" ]; then + # error "this system extension is already disabled" + continue + fi + + if ! systemextensionsctl uninstall '-' "$extName"; then + error "could not disable system extension" + fi + + echo -e "${GREEN}Enabled ${STRONG}${extName}${NC}" + done +} + +function listLaunchItems { local filter="$2" itemDirectories=("${startup_dirs[@]}") @@ -186,7 +362,7 @@ function listItems { done< <(find "${itemDirectories[@]}" -type f -iname '*.plist*' -print0 2>/dev/null) } -function enableItems { +function enableLaunchItems { disabled_items="$(launchctl print-disabled user/"$(id -u)")" while IFS= read -r -d '' startupFile; do @@ -220,7 +396,7 @@ function enableItems { done< <(find "${startup_dirs[@]}" "${system_dirs[@]}" \( -iname "*$1*.plist" -o -iname "*$1*.plist.disabled" \) -print0 2>/dev/null) } -function disableItems { +function disableLaunchItems { disabled_items="$(launchctl print-disabled user/"$(id -u)")" while IFS= read -r -d '' startupFile; do @@ -254,6 +430,10 @@ function disableItems { done< <(find "${startup_dirs[@]}" "${system_dirs[@]}" \( -iname "*$1*.plist" -o -iname "*$1*.plist.disabled" \) -print0 2>/dev/null) } +# +#-------------------------------------------------------------------------------------------------------------------------------------- +# + if [ $# -lt 1 ] || [ $# -gt 2 ]; then usage fi @@ -265,20 +445,29 @@ case "$1" in usage fi fi - listItems "$1" "$2" + listLaunchItems "$1" "$2" + listKernelExtensions "$2" + listSystemExtensions "$2" ;; + "disable") if [ $# -ne 2 ]; then usage fi - disableItems "$2" + disableLaunchItems "$2" + disableKernelExtensions "$2" + disableSystemExtensions "$2" ;; + "enable") if [ $# -ne 2 ]; then usage fi - enableItems "$2" + enableLaunchItems "$2" + enableKernelExtensions "$2" + enableSystemExtensions "$2" ;; + *) usage ;;