Force Sleep en macOS — LaunchAgent con pmset sleepnow
Dejar en suspensión un Mac de no es tan fácil como antes sobre todo si es un equipo estático con discos externos. Discos de backup externos, el micro exterrnbo, los altavoces,...
Y él no dejar que descanse tu Mac, le envejece prematuramente. Es como las personas necesitan dormir descansar.
Solución para forzar el sleep del Mac ignorando assertions de
bluetoothd,powerdy otros procesos que bloquean el sleep normal.
Variables de configuración
Edita solo este bloque antes de ejecutar cualquier comando.
O usa sed para reemplazar en bloque (ver sección al final).
MAC_USER="abkrim" # tu usuario macOS
IDLE_MINUTES=30 # minutos de inactividad antes de dormir
CHECK_INTERVAL=300 # cada cuántos segundos comprueba (300 = 5 min)
SCRIPT_PATH="/Users/$MAC_USER/Library/Scripts/force-sleep.sh"
PLIST_PATH="/Users/$MAC_USER/Library/LaunchAgents/com.user.forcesleep.plist"
LABEL="com.user.forcesleep"
El problema
pmset sleep 30 respeta las sleep assertions: procesos como bluetoothd o powerd pueden decirle al sistema "no te duermas" y el Mac ignora el timer configurado.
# Diagnóstico: ver qué está bloqueando el sleep
pmset -g assertions
# El output típico del problema:
# sleep 0 (sleep prevented by bluetoothd, powerd, cloudd...)
pmset sleepnow ignora esas assertions y duerme el Mac sin negociación.
Solución: script + LaunchAgent
1 — Crear el script
gral
cat > "$SCRIPT_PATH" << 'EOF'
#!/bin/bash
# Rutas absolutas — los LaunchAgents arrancan sin $PATH normal
IOREG=/usr/sbin/ioreg
PMSET=/usr/bin/pmset
AWK=/usr/bin/awk
IDLE=$($IOREG -c IOHIDSystem | $AWK '/HIDIdleTime/ {print int($NF/1000000000); exit}')
# Salir si IDLE está vacío (evita errores de comparación)
[[ -z "$IDLE" ]] && exit 0
if [ "$IDLE" -ge IDLE_SECONDS ]; then
$PMSET sleepnow
fi
EOF
# Sustituir IDLE_SECONDS con el valor real (30 min = 1800 s)
IDLE_SECONDS=$(( IDLE_MINUTES * 60 ))
sed -i '' "s/IDLE_SECONDS/$IDLE_SECONDS/" "$SCRIPT_PATH"
chmod +x "$SCRIPT_PATH"
¿Por qué rutas absolutas?
Los LaunchAgents arrancan con un$PATHmínimo (/usr/bin:/bin).
Sin ruta absoluta,ioregypmsetdan Error 127 (command not found).
2 — Verificar que los binarios existen
which ioreg pmset awk
# Esperado:
# /usr/sbin/ioreg
# /usr/bin/pmset
# /usr/bin/awk
3 — Crear el plist
⚠️ El nombre del archivo debe terminar en
.plist. Sin la extensión o con sufijos extra (ej..plist-e) macOS rechaza el agente con Error 127.
cat > "$PLIST_PATH" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>$SCRIPT_PATH</string>
</array>
<key>StartInterval</key>
<integer>$CHECK_INTERVAL</integer>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>
EOF
4 — Cargar el agente
launchctl load "$PLIST_PATH"
# Verificar: la segunda columna debe ser 0 (sin error)
launchctl list | grep forcesleep
Gestión del agente
# Desactivar (sin eliminar)
launchctl unload "$PLIST_PATH"
# Reactivar
launchctl load "$PLIST_PATH"
# Eliminar completamente
launchctl unload "$PLIST_PATH"
rm "$SCRIPT_PATH"
rm "$PLIST_PATH"
# Probar el script manualmente
bash "$SCRIPT_PATH"
# Ver idle time actual (en segundos)
/usr/sbin/ioreg -c IOHIDSystem | /usr/bin/awk '/HIDIdleTime/ {print int($NF/1000000000); exit}'
Diagnóstico de errores comunes
| Error | Causa | Solución |
|---|---|---|
Error 127 |
Binario no encontrado por $PATH vacío |
Usar rutas absolutas en el script |
Error 127 en LaunchControl |
Nombre del plist sin extensión o con sufijo extra | Recrear con nombre exacto *.plist |
IDLE vacío |
ioreg no devuelve HIDIdleTime |
Comprobar which ioreg, verificar ruta |
| Sleep no ocurre | pmset sleep respeta assertions |
Usar pmset sleepnow en su lugar |
Adaptar a otro usuario con sed
Para usar esta guía en otra máquina, reemplaza el usuario en bloque:
# Reemplazar "abkrim" por el nuevo usuario en todos los comandos
sed 's/abkrim/NUEVO_USUARIO/g' force-sleep-mac.md > force-sleep-mac-custom.md
O directamente al ejecutar, exporta las variables al inicio de tu sesión de terminal y copia/pega los bloques de código — las variables $MAC_USER, $SCRIPT_PATH, etc. se resolverán solas.
Cómo funciona (resumen)
LaunchAgent (cada 5 min)
│
▼
force-sleep.sh
│
├─ Lee idle time desde IOHIDSystem (ioreg)
│
├─ ¿IDLE >= 1800s? ──No──► exit 0
│
└──Sí──► pmset sleepnow ◄── ignora bluetoothd, powerd, etc.
Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris
Si necesitas soporte profesional puedes contratar con Castris soporte profesional.