#!/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 ""