Skip to main content

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, powerd y 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 $PATH mínimo (/usr/bin:/bin).
Sin ruta absoluta, ioreg y pmset dan 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.