#!/bin/bash # ============================================================ # DM-BLOQUEIO-UPDATE — impede o Mac de se atualizar sozinho # # Serial JHWLTJF67J · MacBook Pro 14" M4 · macOS 27 # # RODAR NO SISTEMA BOOTADO, com sudo: # sudo bash dm-bloqueio-update.sh # # POR QUE ISSO IMPORTA: # Um update GRANDE do macOS pode reescrever o /etc/hosts e # apagar o bloqueio de enrollment -> o MDM volta. Este script # fecha essa porta em 5 camadas + instala um auto-heal que # re-aplica tudo a cada boot. # # CONTEXTO macOS 27: # - `softwareupdate --ignore` FOI REMOVIDO (Big Sur+). Nao existe. # - Deferrals via perfil MDM de Restricoes foram REMOVIDOS no 27.0. # - O que sobra: preferencias em /Library/Preferences + CatalogURL # + bloqueio de rede + auto-heal. # # REVERTER DEPOIS (quando quiser atualizar de novo): # sudo bash dm-bloqueio-update.sh --reverter # ============================================================ set -u GRN='\033[0;32m'; RED='\033[0;31m'; YEL='\033[1;33m'; CYN='\033[0;36m'; NC='\033[0m' ok() { echo -e "${GRN} OK${NC} $*"; } bad() { echo -e "${RED} ERRO${NC} $*"; } wrn() { echo -e "${YEL} !!${NC} $*"; } inf() { echo -e "${CYN} -->${NC} $*"; } hdr() { echo ""; echo -e "${CYN}================================================${NC}"; echo -e "${CYN} $*${NC}"; echo -e "${CYN}================================================${NC}"; } HEAL_SCRIPT="/usr/local/bin/dm-heal.sh" HEAL_PLIST="/Library/LaunchDaemons/com.dm.heal.plist" PREFS="/Library/Preferences/com.apple.SoftwareUpdate" HOSTS="/etc/hosts" if [ "$(id -u)" != "0" ]; then echo -e "${RED} Rode com sudo: sudo bash $0${NC}" exit 1 fi # ============================================================ # MODO REVERTER # ============================================================ if [ "${1:-}" = "--reverter" ]; then echo "" echo -e "${YEL} DM-BLOQUEIO-UPDATE — REVERTENDO${NC}" hdr "[R1] Removendo o auto-heal" launchctl bootout system "$HEAL_PLIST" 2>/dev/null || launchctl unload "$HEAL_PLIST" 2>/dev/null rm -f "$HEAL_PLIST" "$HEAL_SCRIPT" 2>/dev/null ok "LaunchDaemon e script removidos" hdr "[R2] Restaurando as preferencias de update" defaults delete "$PREFS" AutomaticCheckEnabled 2>/dev/null defaults delete "$PREFS" AutomaticDownload 2>/dev/null defaults delete "$PREFS" AutomaticallyInstallMacOSUpdates 2>/dev/null defaults delete "$PREFS" CriticalUpdateInstall 2>/dev/null defaults delete "$PREFS" CatalogURL 2>/dev/null defaults delete "$PREFS" MajorOSUserNotificationDate 2>/dev/null defaults delete "$PREFS" UserNotificationDate 2>/dev/null defaults delete /Library/Preferences/com.apple.commerce AutoUpdate 2>/dev/null killall -HUP cfprefsd 2>/dev/null ok "preferencias limpas (volta ao padrao da Apple)" hdr "[R3] Removendo bloqueio de update do /etc/hosts" if [ -f "$HOSTS" ]; then cp "$HOSTS" "/etc/hosts.bak-dm-reverter-$(date +%Y%m%d_%H%M%S)" # grep -v + mv: portatil (sed -i '' e sintaxe BSD, quebra fora do macOS) grep -v '# DM-UPDATE-BLOCK' "$HOSTS" > "$HOSTS.dmtmp" 2>/dev/null \ && mv "$HOSTS.dmtmp" "$HOSTS" grep -vE '[[:space:]](swscan|swdist|swcdn|swdownload|mesu|appldnld)\.apple\.com$' \ "$HOSTS" > "$HOSTS.dmtmp" 2>/dev/null \ && mv "$HOSTS.dmtmp" "$HOSTS" chmod 644 "$HOSTS" 2>/dev/null ok "dominios de update liberados (o bloqueio de ENROLLMENT continua)" fi hdr "[R4] Estado final" echo " softwareupdate --schedule:" softwareupdate --schedule 2>&1 | sed 's/^/ /' echo "" echo " Dominios de update no /etc/hosts:" grep -cE 'swscan|swdist|swcdn|swdownload|mesu|appldnld' "$HOSTS" 2>/dev/null | sed 's/^/ /' echo "" echo -e "${GRN} Revertido. O Mac volta a poder atualizar.${NC}" echo " (O bloqueio de ENROLLMENT/DEP segue ativo — isso e proposital.)" echo "" exit 0 fi echo "" echo -e "${YEL} DM-BLOQUEIO-UPDATE — blindagem contra update automatico${NC}" # ============================================================ # [1] ESTADO ANTES # ============================================================ hdr "[1] Estado ATUAL (antes de mexer)" echo " macOS: $(sw_vers -productVersion 2>/dev/null) (build $(sw_vers -buildVersion 2>/dev/null))" echo "" echo " -- softwareupdate --schedule:" softwareupdate --schedule 2>&1 | sed 's/^/ /' echo "" echo " -- preferencias atuais:" defaults read "$PREFS" 2>/dev/null | sed 's/^/ /' || echo " (nenhuma)" echo "" echo " -- o que o DAEMON acha (fonte da verdade):" grep autoUpdatesEnabled /var/log/install.log 2>/dev/null | tail -1 | sed 's/^/ /' || echo " (sem registro)" echo "" # ============================================================ # [2] DESLIGAR AS PREFERENCIAS DE UPDATE # ============================================================ hdr "[2] Desligando o update automatico" set_pref() { local key="$1"; local val="$2" if defaults write "$PREFS" "$key" -bool "$val" 2>/dev/null; then ok "$key = $val" else bad "falhou: $key" fi } set_pref AutomaticCheckEnabled false # nao checar sozinho set_pref AutomaticDownload false # nao baixar sozinho set_pref AutomaticallyInstallMacOSUpdates false # nao instalar macOS sozinho set_pref CriticalUpdateInstall false # nao instalar criticos sozinho echo "" inf "App Store (auto-update de apps):" defaults write /Library/Preferences/com.apple.commerce AutoUpdate -bool false 2>/dev/null \ && ok "com.apple.commerce AutoUpdate = false" \ || wrn "nao consegui escrever com.apple.commerce" echo "" inf "scheduler do softwareupdate:" if softwareupdate --schedule off 2>&1 | grep -qiE "off|disabled"; then ok "scheduler desligado" else wrn "softwareupdate --schedule off nao confirmou (Apple pode ter removido no 27)" fi killall -HUP cfprefsd 2>/dev/null ok "cfprefsd recarregado" # ============================================================ # [3] SUPRIMIR A NOTIFICACAO DE UPGRADE # ============================================================ hdr "[3] Suprimindo a notificacao de upgrade" FUTURO="2035-01-01 00:00:00 +0000" defaults write "$PREFS" MajorOSUserNotificationDate -date "$FUTURO" 2>/dev/null \ && ok "MajorOSUserNotificationDate = 2035" || wrn "falhou MajorOSUserNotificationDate" defaults write "$PREFS" UserNotificationDate -date "$FUTURO" 2>/dev/null \ && ok "UserNotificationDate = 2035" || wrn "falhou UserNotificationDate" echo "" inf "descarregando o agente de notificacao de update:" if launchctl bootout gui/501/com.apple.softwareupdate_notify_agent 2>/dev/null \ || launchctl unload -w /System/Library/LaunchAgents/com.apple.softwareupdate_notify_agent.plist 2>/dev/null; then ok "notify_agent descarregado" else wrn "nao consegui descarregar (normal se SIP protege o plist)" fi # ============================================================ # [4] APONTAR O CATALOGO PARA UM URL MORTO # ============================================================ hdr "[4] CatalogURL (o Mac nao acha mais updates)" inf "apontando o catalogo de software para um URL inexistente" inf "isso sobrevive a reset de /etc/hosts — e a defesa mais forte" defaults write "$PREFS" CatalogURL "https://dm.invalid/nonexistent.sucatalog" 2>/dev/null \ && ok "CatalogURL = dm.invalid (morto)" \ || wrn "nao consegui setar CatalogURL" echo "" echo " Para reverter so isso: sudo defaults delete $PREFS CatalogURL" # ============================================================ # [5] BLOQUEAR DOMINIOS DE UPDATE NO /etc/hosts # ============================================================ hdr "[5] Bloqueando dominios de update no /etc/hosts" [ -f "$HOSTS" ] || { touch "$HOSTS"; } cp "$HOSTS" "/etc/hosts.bak-dm-update-$(date +%Y%m%d_%H%M%S)" inf "backup do hosts salvo" grep -q "DM-UPDATE-BLOCK" "$HOSTS" 2>/dev/null || { echo "" >> "$HOSTS" echo "# DM-UPDATE-BLOCK — impede update automatico do macOS" >> "$HOSTS" } N=0 for d in swscan.apple.com swdist.apple.com swcdn.apple.com \ swdownload.apple.com mesu.apple.com appldnld.apple.com; do if grep -qiE "[[:space:]]$d($|[[:space:]])" "$HOSTS" 2>/dev/null; then continue fi printf '0.0.0.0 %s\n:: %s\n' "$d" "$d" >> "$HOSTS" && N=$((N+1)) done ok "$N dominios de update bloqueados" echo " total de linhas 0.0.0.0 no hosts: $(grep -c '0.0.0.0' "$HOSTS" 2>/dev/null)" # ============================================================ # [6] AUTO-HEAL (a defesa que sobrevive a tudo) # ============================================================ hdr "[6] Instalando o auto-heal (re-aplica a cada boot)" cat > "$HEAL_SCRIPT" <<'HEALEOF' #!/bin/bash # DM-HEAL — re-aplica o bloqueio de MDM/update a cada boot. # Instalado por dm-bloqueio-update.sh. Nao editar a mao. exec >>/var/log/dm-heal.log 2>&1 echo "=== $(date '+%Y-%m-%d %H:%M:%S') dm-heal iniciando ===" HOSTS="/etc/hosts" PREFS="/Library/Preferences/com.apple.SoftwareUpdate" # --- 1. hosts: enrollment + update --- for d in iprofiles.apple.com deviceenrollment.apple.com mdmenrollment.apple.com \ acmdm.apple.com axm-adm-mdm.apple.com albert.apple.com gdmf.apple.com \ configuration.apple.com xp.apple.com init-content.apple.com \ swscan.apple.com swdist.apple.com swcdn.apple.com swdownload.apple.com \ mesu.apple.com appldnld.apple.com; do grep -qiE "[[:space:]]$d($|[[:space:]])" "$HOSTS" 2>/dev/null && continue printf '0.0.0.0 %s\n:: %s\n' "$d" "$d" >> "$HOSTS" echo "hosts: bloqueado $d" done # --- 2. marcadores DEP --- CFG="/var/db/ConfigurationProfiles/Settings" mkdir -p "$CFG" rm -f "$CFG/.cloudConfigHasActivationRecord" \ "$CFG/.cloudConfigRecordFound" \ "$CFG/.cloudConfigTimerCheck" 2>/dev/null touch "$CFG/.cloudConfigRecordNotFound" echo "dep: marcadores re-aplicados" # --- 3. daemons de enrollment --- LDP="/var/db/com.apple.xpc.launchd/disabled.plist" if [ ! -f "$LDP" ]; then printf '\n\n\n' > "$LDP" fi for l in com.apple.ManagedClient.enroll com.apple.ManagedClient.cloudConfiguration \ com.apple.mdmclient.daemon.runatboot com.apple.activationd; do /usr/libexec/PlistBuddy -c "Add :$l bool true" "$LDP" 2>/dev/null \ || /usr/libexec/PlistBuddy -c "Set :$l true" "$LDP" 2>/dev/null done echo "daemons: re-desativados" # --- 4. preferencias de update --- defaults write "$PREFS" AutomaticCheckEnabled -bool false 2>/dev/null defaults write "$PREFS" AutomaticDownload -bool false 2>/dev/null defaults write "$PREFS" AutomaticallyInstallMacOSUpdates -bool false 2>/dev/null defaults write "$PREFS" CriticalUpdateInstall -bool false 2>/dev/null defaults write "$PREFS" CatalogURL "https://dm.invalid/nonexistent.sucatalog" 2>/dev/null echo "prefs: update desligado" # --- 5. matar daemons de MDM se estiverem rodando --- for p in mdmclient ManagedClient cloudconfigurationd; do if pgrep -x "$p" >/dev/null 2>&1; then pkill -x "$p" 2>/dev/null && echo "processo morto: $p" fi done echo "=== $(date '+%Y-%m-%d %H:%M:%S') dm-heal concluido ===" HEALEOF chmod 755 "$HEAL_SCRIPT" chown root:wheel "$HEAL_SCRIPT" ok "script instalado em $HEAL_SCRIPT" cat > "$HEAL_PLIST" <<'PLISTEOF' Label com.dm.heal ProgramArguments /bin/bash /usr/local/bin/dm-heal.sh RunAtLoad StartInterval 3600 StandardErrorPath /var/log/dm-heal.err PLISTEOF chmod 644 "$HEAL_PLIST" chown root:wheel "$HEAL_PLIST" ok "LaunchDaemon instalado em $HEAL_PLIST" if launchctl bootstrap system "$HEAL_PLIST" 2>/dev/null || launchctl load -w "$HEAL_PLIST" 2>/dev/null; then ok "auto-heal carregado e rodando" else wrn "carregado no proximo boot (RunAtLoad cuida disso)" fi echo "" inf "rodando o auto-heal AGORA para testar:" bash "$HEAL_SCRIPT" echo " (log completo em /var/log/dm-heal.log)" # ============================================================ # [7] VERIFICACAO FINAL # ============================================================ hdr "[7] VERIFICACAO FINAL" echo " -- preferencias de update:" for k in AutomaticCheckEnabled AutomaticDownload AutomaticallyInstallMacOSUpdates CriticalUpdateInstall; do V=$(defaults read "$PREFS" "$k" 2>/dev/null) if [ "$V" = "0" ]; then ok "$k = 0 (desligado)"; else wrn "$k = ${V:-ausente}"; fi done echo "" echo " -- CatalogURL:" defaults read "$PREFS" CatalogURL 2>/dev/null | sed 's/^/ /' || wrn "nao setado" echo "" echo " -- softwareupdate --schedule:" softwareupdate --schedule 2>&1 | sed 's/^/ /' echo "" echo " -- dominios bloqueados no /etc/hosts:" grep -c '0.0.0.0' "$HOSTS" 2>/dev/null | sed 's/^/ /' echo "" echo " -- auto-heal instalado:" [ -x "$HEAL_SCRIPT" ] && ok "script presente" || bad "script AUSENTE" [ -f "$HEAL_PLIST" ] && ok "LaunchDaemon presente" || bad "LaunchDaemon AUSENTE" launchctl list 2>/dev/null | grep -i "com.dm.heal" | sed 's/^/ /' || echo " (nao listado — sobe no proximo boot)" echo "" echo " -- daemons de MDM rodando agora:" ps aux 2>/dev/null | grep -iE "mdmclient|ManagedClient|cloudconfigurationd" | grep -v grep | sed 's/^/ /' echo " (vazio = nenhum)" echo "" echo " -- TESTE REAL: o Mac ainda ve updates?" inf "rodando 'softwareupdate -l' (pode demorar ~30s se estiver bloqueado)" TIMEOUT_CMD="" command -v timeout >/dev/null 2>&1 && TIMEOUT_CMD="timeout 45" $TIMEOUT_CMD softwareupdate -l 2>&1 | head -12 | sed 's/^/ /' echo "" hdr "RESUMO" echo " Camada 1: preferencias desligadas (check/download/install)" echo " Camada 2: notificacao de upgrade suprimida (2035)" echo " Camada 3: CatalogURL morto (nao acha updates)" echo " Camada 4: dominios de update bloqueados no /etc/hosts" echo " Camada 5: auto-heal a cada boot + a cada hora" echo "" echo -e "${GRN} O Mac nao atualiza mais sozinho.${NC}" echo "" echo -e "${YEL} ATENCAO: isso tambem bloqueia updates de SEGURANCA.${NC}" echo " Quando quiser atualizar de proposito:" echo " sudo bash dm-bloqueio-update.sh --reverter" echo " (atualiza, e depois roda sem --reverter de novo)" echo "" echo -e "${YEL} E lembre: se um update grande passar, o /etc/hosts pode ser" echo " resetado — o auto-heal conserta no proximo boot/hora.${NC}" echo ""