Monitor de backup do Time Machine na barra de menus do Mac
Quem usa o Time Machine geralmente só percebe que algo deu errado quando precisa restaurar um arquivo. O backup roda em segundo plano, quase não avisa e, quando está em andamento, a interface mostra apenas um ícone girando. No meu caso, os backups vão para um servidor Ubuntu na rede, e eu queria mais informação: a porcentagem real, quantos arquivos faltam e quanto tempo ainda resta. Então montei um monitor próprio na barra de menus.
Por que monitorar o backup
Backups pela rede são mais lentos e mais sujeitos a interrupções do que um disco externo ligado direto ao Mac. Uma oscilação no Wi-Fi ou um servidor ocupado podem atrasar tudo sem que você perceba. Ter essas informações à vista, sem abrir as preferências do sistema, faz diferença no dia a dia.
O que o tmutil status entrega
O macOS já traz a ferramenta de linha de comando tmutil, e o subcomando status mostra o estado atual do backup em formato de texto estruturado. Ele informa se há um backup em execução, em qual fase ele está e alguns valores numéricos, como o progresso e o tempo restante estimado. É exatamente o tipo de saída que um script consegue processar.
O problema é que essa saída não é pensada para leitura humana. Os valores vêm misturados com nomes de chaves, e a porcentagem normalmente aparece como fração entre 0 e 1. Para resolver isso, usei o awk para filtrar as linhas certas e fazer as contas: transformar a fração em porcentagem e converter os segundos restantes em um formato legível, como “2 h 14 min”.
Montando o plugin no SwiftBar
O SwiftBar é um aplicativo gratuito e de código aberto que transforma scripts em itens da barra de menus. Você pode baixá-lo em swiftbar.app. Cada script é um arquivo executável cujo nome define o intervalo de atualização, e tudo o que ele imprime vira o conteúdo do item. O texto antes da linha separadora aparece sempre visível na barra; o que vem depois forma o menu suspenso.
No meu script, a barra mostra um ícone e a porcentagem atual enquanto o backup roda. Quando não há nada em andamento, o ícone muda para uma marca de verificação com a data do último backup concluído. No menu suspenso aparecem os detalhes: fase atual, arquivos processados, tempo restante e um atalho para abrir o log.
Log contínuo para ter histórico
Além do monitor visual, uma lógica em background registra tudo em um arquivo de log contínuo. Cada linha guarda data, hora, fase e progresso, então dá para reconstruir o histórico de qualquer backup depois.
O ponto que mais me ajudou foi medir a duração de cada fase. Quando um backup demora mais que o normal, o log mostra exatamente onde o tempo foi gasto. Assim deixei de chutar a causa da lentidão.
- Início e fim de cada fase, com data e hora
- Porcentagem e quantidade de arquivos em cada registro
- Duração exata de cada etapa, calculada pela diferença de tempo
Script completo
Salve o conteúdo abaixo como um arquivo na pasta de plugins do SwiftBar, com a extensão que define o intervalo de atualização (por exemplo, tm.10s.sh para atualizar a cada 10 segundos). Depois, torne o arquivo executável com chmod +x.
#!/bin/bash
# Forçar a localidade para evitar problemas com ponto flutuante
export LC_ALL=C
# Configurações do Histórico (Caminho Automático)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
LOG_FILE="$SCRIPT_DIR/tm_historico.log"
STATE_FILE="/tmp/tm_last_phase.txt"
START_FILE="/tmp/tm_phase_start.txt"
# Cria o arquivo de log se não existir
touch "$LOG_FILE"
STATUS=$(tmutil status)
RUNNING=$(echo "$STATUS" | awk '/Running =/ {print $3}' | tr -d ';')
# Captura a fase atual ou define como Ocioso
if [ "$RUNNING" == "0" ] || [ -z "$RUNNING" ]; then
PHASE="Ocioso"
else
PHASE=$(echo "$STATUS" | awk -F'"' '/BackupPhase/ {print $2}')
if [ -z "$PHASE" ]; then
PHASE=$(echo "$STATUS" | awk '/BackupPhase =/ {print $3}' | tr -d '";')
fi
fi
# Tradução das Fases para o Log e Interface
case "$PHASE" in
"Copying") PHASE_PT="Copiando"; ICON="💾" ;;
"HealthCheckFsck") PHASE_PT="Verificando FS"; ICON="🩺" ;;
"ThinningPreBackup") PHASE_PT="Limpando"; ICON="🧹" ;;
"ThinningPostBackup") PHASE_PT="Limpeza final"; ICON="🧹" ;;
"FindingBackupFiles") PHASE_PT="Procurando"; ICON="🔍" ;;
"FindingChanges") PHASE_PT="Procurando alterações"; ICON="🔍" ;;
"Preparing") PHASE_PT="Preparando"; ICON="⚙️" ;;
"PreparingSourceVolumes") PHASE_PT="Preparando discos"; ICON="💽" ;;
"Mounting") PHASE_PT="Montando"; ICON="💿" ;;
"MountingDiskImage") PHASE_PT="Montando imagem"; ICON="💿" ;;
"Ocioso") PHASE_PT="Ocioso"; ICON="⏱️" ;;
*) PHASE_PT="$PHASE"; ICON="🔄" ;;
esac
# ---------------------------------------------------------
# LÓGICA DO HISTÓRICO E ROTAÇÃO DE LOG
# ---------------------------------------------------------
CURRENT_TIME=$(date +%s)
CURRENT_DATE=$(date +"%d/%m/%Y %H:%M:%S")
LAST_PHASE_PT=$(cat "$STATE_FILE" 2>/dev/null)
PHASE_START=$(cat "$START_FILE" 2>/dev/null)
# Se a fase mudou, registra o tempo da fase anterior e inicia a nova
if [ "$PHASE_PT" != "$LAST_PHASE_PT" ]; then
# Se existia uma fase anterior registrada, calcula a duração
if [ -n "$LAST_PHASE_PT" ] && [ -n "$PHASE_START" ]; then
DURATION=$((CURRENT_TIME - PHASE_START))
H=$((DURATION / 3600))
M=$(((DURATION % 3600) / 60))
S=$((DURATION % 60))
FORMATTED_DURATION=""
[ $H -gt 0 ] && FORMATTED_DURATION="${H}h "
[ $M -gt 0 ] && FORMATTED_DURATION="${FORMATTED_DURATION}${M}m "
FORMATTED_DURATION="${FORMATTED_DURATION}${S}s"
echo "[$CURRENT_DATE] ⏹ Concluído: $LAST_PHASE_PT (Duração: $FORMATTED_DURATION)" >> "$LOG_FILE"
fi
# Registra o início da nova fase
if [ "$PHASE_PT" != "Ocioso" ]; then
echo "[$CURRENT_DATE] ▶ Iniciado: $PHASE_PT" >> "$LOG_FILE"
else
echo "[$CURRENT_DATE] ✅ Backup Finalizado / Inativo" >> "$LOG_FILE"
echo "---" >> "$LOG_FILE"
fi
# Rotação automática do log: limite de 1000 linhas
MAX_LINES=1000
LINES=$(wc -l < "$LOG_FILE" | awk '{print $1}')
if [ -n "$LINES" ] && [ "$LINES" -gt "$MAX_LINES" ]; then
tail -n 500 "$LOG_FILE" > "${LOG_FILE}.tmp"
mv "${LOG_FILE}.tmp" "$LOG_FILE"
fi
# Atualiza os arquivos de estado
echo "$PHASE_PT" > "$STATE_FILE"
echo "$CURRENT_TIME" > "$START_FILE"
fi
# ---------------------------------------------------------
# Pegando as variáveis do sistema para a interface
PERCENT=$(echo "$STATUS" | awk -F'=' '/^[[:space:]]*Percent =/ {print $2}' | tr -d ' ";')
BYTES=$(echo "$STATUS" | awk -F'=' '/^[[:space:]]*bytes =/ {print $2}' | tr -d ' ";')
TOTAL_BYTES=$(echo "$STATUS" | awk -F'=' '/^[[:space:]]*totalBytes =/ {print $2}' | tr -d ' ";')
FILES=$(echo "$STATUS" | awk -F'=' '/^[[:space:]]*files =/ {print $2}' | tr -d ' ";')
TIME_REMAINING=$(echo "$STATUS" | awk -F'=' '/^[[:space:]]*TimeRemaining =/ {print $2}' | tr -d ' ";')
FILES_TEXT=""
if [ -n "$FILES" ]; then
FILES_TEXT=" ($FILES arqs)"
fi
TIME_TEXT=""
if [ -n "$TIME_REMAINING" ] && [ "$TIME_REMAINING" != "0" ]; then
TIME_TEXT=$(awk -v t="$TIME_REMAINING" 'BEGIN {
t = int(t)
h = int(t / 3600)
m = int((t % 3600) / 60)
if (h > 0) printf " ~%dh%02dm", h, m
else if (m > 0) printf " ~%dm", m
else printf " <1m"
}')
fi
# Exibição principal na barra de menus
if [ "$PHASE_PT" == "Ocioso" ]; then
echo "$ICON TM: Ocioso"
elif [ -z "$PERCENT" ] || [ "$PERCENT" == "-1" ] || [ "$PERCENT" == "-1.0" ]; then
echo "$ICON TM: $PHASE_PT"
else
PCT_TEXT=$(awk -v p="$PERCENT" 'BEGIN { printf "%.1f%%", p * 100 }')
echo "$ICON TM: $PCT_TEXT$FILES_TEXT$TIME_TEXT"
fi
# Conteúdo do Dropdown (ao clicar)
echo "---"
if [ "$PHASE_PT" == "Ocioso" ]; then
echo "Nenhum backup em andamento."
else
echo "Fase Atual: $PHASE_PT"
if [ -n "$BYTES" ] && [ -n "$TOTAL_BYTES" ] && [ "$TOTAL_BYTES" != "0" ]; then
COPIED_GB=$(awk -v b="$BYTES" 'BEGIN { printf "%.2f", b / 1000000000 }')
TOTAL_GB=$(awk -v t="$TOTAL_BYTES" 'BEGIN { printf "%.2f", t / 1000000000 }')
echo "Tamanho: $COPIED_GB GB de $TOTAL_GB GB"
fi
fi
# Exibe o histórico diretamente no menu
echo "---"
echo "Histórico Recente:"
if [ -s "$LOG_FILE" ]; then
tail -n 15 "$LOG_FILE" | while read -r line; do
echo "$line | font=Menlo size=11 trim=false"
done
else
echo "Nenhum histórico registrado ainda."
fi
echo "---"
if [ "$PHASE_PT" == "Ocioso" ]; then
echo "Iniciar Backup | bash='tmutil' param1='startbackup' terminal=false"
else
echo "Parar Backup | bash='tmutil' param1='stopbackup' terminal=false"
fi
echo "Abrir Log Completo | bash='open' param1=\"$LOG_FILE\" terminal=false"
Resultado na prática
Hoje consigo olhar para a barra de menus e saber, em um segundo, se o backup está andando e quanto falta. Não preciso mais abrir o Terminal nem ficar adivinhando se algo travou.
Dois aprendizados ficaram. O primeiro é que o formato de saída do tmutil pode variar entre versões do macOS, então vale testar o script no terminal antes de colocá-lo no SwiftBar. O segundo é que o log cresce com o tempo, e uma rotação simples evita que ele ocupe espaço à toa.
Se você quiser replicar
- Instale o SwiftBar e defina uma pasta para os plugins.
- Escreva o script primeiro no terminal, imprimindo apenas os dados que precisa.
- Escolha um intervalo de atualização razoável para não consumir recursos desnecessários.
- Crie o processo de log separado, para que o registro continue mesmo com o menu fechado.
Se você usa o Time Machine com um servidor próprio, esse pequeno projeto dá uma tranquilidade que o sistema não oferece de fábrica. Em poucas horas dá para ter algo funcional, e depois é só ajustar ao seu gosto.


