{"id":384,"date":"2026-10-30T17:34:49","date_gmt":"2026-10-30T20:34:49","guid":{"rendered":"https:\/\/www.bernabauer.com\/blog\/monitor-de-backup-do-time-machine-na-barra-de-menus-do-mac\/"},"modified":"2026-10-30T18:16:31","modified_gmt":"2026-10-30T21:16:31","slug":"monitor-de-backup-do-time-machine-na-barra-de-menus-do-mac","status":"publish","type":"post","link":"https:\/\/www.bernabauer.com\/blog\/monitor-de-backup-do-time-machine-na-barra-de-menus-do-mac\/","title":{"rendered":"Monitor de backup do Time Machine na barra de menus do Mac"},"content":{"rendered":"<p>Quem usa o Time Machine geralmente s&oacute; percebe que algo deu errado quando precisa restaurar um arquivo. O backup roda em segundo plano, quase n&atilde;o avisa e, quando est&aacute; em andamento, a interface mostra apenas um &iacute;cone girando. No meu caso, os backups v&atilde;o para um servidor Ubuntu na rede, e eu queria mais informa&ccedil;&atilde;o: a porcentagem real, quantos arquivos faltam e quanto tempo ainda resta. Ent&atilde;o montei um monitor pr&oacute;prio na barra de menus.<\/p>\n<h2>Por que monitorar o backup<\/h2>\n<p>Backups pela rede s&atilde;o mais lentos e mais sujeitos a interrup&ccedil;&otilde;es do que um disco externo ligado direto ao Mac. Uma oscila&ccedil;&atilde;o no Wi-Fi ou um servidor ocupado podem atrasar tudo sem que voc&ecirc; perceba. Ter essas informa&ccedil;&otilde;es &agrave; vista, sem abrir as prefer&ecirc;ncias do sistema, faz diferen&ccedil;a no dia a dia.<\/p>\n<h2>O que o tmutil status entrega<\/h2>\n<p>O macOS j&aacute; traz a ferramenta de linha de comando <em>tmutil<\/em>, e o subcomando <em>status<\/em> mostra o estado atual do backup em formato de texto estruturado. Ele informa se h&aacute; um backup em execu&ccedil;&atilde;o, em qual fase ele est&aacute; e alguns valores num&eacute;ricos, como o progresso e o tempo restante estimado. &Eacute; exatamente o tipo de sa&iacute;da que um script consegue processar.<\/p>\n<p><figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/www.bernabauer.com\/blog\/wp-content\/uploads\/2026\/10\/monitor-de-backup-do-time-machine-na-barra-de-menus-do-mac-1.jpg\" alt=\"External hard drive connected to a laptop, showcasing portable storage solution.\"\/><figcaption><a href=\"https:\/\/www.pexels.com\/photo\/portable-laptop-power-bank-15882042\/\" rel=\"noopener\">Foto: Budget Bizar \/ Pexels<\/a><\/figcaption><\/figure>\n<\/p>\n<p>O problema &eacute; que essa sa&iacute;da n&atilde;o &eacute; pensada para leitura humana. Os valores v&ecirc;m misturados com nomes de chaves, e a porcentagem normalmente aparece como fra&ccedil;&atilde;o entre 0 e 1. Para resolver isso, usei o <strong>awk<\/strong> para filtrar as linhas certas e fazer as contas: transformar a fra&ccedil;&atilde;o em porcentagem e converter os segundos restantes em um formato leg&iacute;vel, como &ldquo;2 h 14 min&rdquo;.<\/p>\n<h2>Montando o plugin no SwiftBar<\/h2>\n<p>O SwiftBar &eacute; um aplicativo gratuito e de c&oacute;digo aberto que transforma scripts em itens da barra de menus. Voc&ecirc; pode baix&aacute;-lo em <a href=\"https:\/\/swiftbar.app\">swiftbar.app<\/a>. Cada script &eacute; um arquivo execut&aacute;vel cujo nome define o intervalo de atualiza&ccedil;&atilde;o, e tudo o que ele imprime vira o conte&uacute;do do item. O texto antes da linha separadora aparece sempre vis&iacute;vel na barra; o que vem depois forma o menu suspenso.<\/p>\n<p>No meu script, a barra mostra um &iacute;cone e a porcentagem atual enquanto o backup roda. Quando n&atilde;o h&aacute; nada em andamento, o &iacute;cone muda para uma marca de verifica&ccedil;&atilde;o com a data do &uacute;ltimo backup conclu&iacute;do. No menu suspenso aparecem os detalhes: fase atual, arquivos processados, tempo restante e um atalho para abrir o log.<\/p>\n<h2>Log cont&iacute;nuo para ter hist&oacute;rico<\/h2>\n<p>Al&eacute;m do monitor visual, uma l&oacute;gica em background registra tudo em um arquivo de log cont&iacute;nuo. Cada linha guarda data, hora, fase e progresso, ent&atilde;o d&aacute; para reconstruir o hist&oacute;rico de qualquer backup depois.<\/p>\n<p>O ponto que mais me ajudou foi medir a dura&ccedil;&atilde;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&atilde;o.<\/p>\n<ul>\n<li>In&iacute;cio e fim de cada fase, com data e hora<\/li>\n<li>Porcentagem e quantidade de arquivos em cada registro<\/li>\n<li>Dura&ccedil;&atilde;o exata de cada etapa, calculada pela diferen&ccedil;a de tempo<\/li>\n<\/ul>\n<h2>Script completo<\/h2>\n<p>Salve o conte&uacute;do abaixo como um arquivo na pasta de plugins do SwiftBar, com a extens&atilde;o que define o intervalo de atualiza&ccedil;&atilde;o (por exemplo, tm.10s.sh para atualizar a cada 10 segundos). Depois, torne o arquivo execut&aacute;vel com <em>chmod +x<\/em>.<\/p>\n<pre>#!\/bin\/bash<br><br># For&ccedil;ar a localidade para evitar problemas com ponto flutuante<br>export LC_ALL=C<br><br># Configura&ccedil;&otilde;es do Hist&oacute;rico (Caminho Autom&aacute;tico)<br>SCRIPT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" &amp;&gt; \/dev\/null &amp;&amp; pwd )\"<br>LOG_FILE=\"$SCRIPT_DIR\/tm_historico.log\"<br>STATE_FILE=\"\/tmp\/tm_last_phase.txt\"<br>START_FILE=\"\/tmp\/tm_phase_start.txt\"<br><br># Cria o arquivo de log se n&atilde;o existir<br>touch \"$LOG_FILE\"<br><br>STATUS=$(tmutil status)<br>RUNNING=$(echo \"$STATUS\" | awk '\/Running =\/ {print $3}' | tr -d ';')<br><br># Captura a fase atual ou define como Ocioso<br>if [ \"$RUNNING\" == \"0\" ] || [ -z \"$RUNNING\" ]; then<br>&nbsp; &nbsp; PHASE=\"Ocioso\"<br>else<br>&nbsp; &nbsp; PHASE=$(echo \"$STATUS\" | awk -F'\"' '\/BackupPhase\/ {print $2}')<br>&nbsp; &nbsp; if [ -z \"$PHASE\" ]; then<br>&nbsp; &nbsp; &nbsp; &nbsp; PHASE=$(echo \"$STATUS\" | awk '\/BackupPhase =\/ {print $3}' | tr -d '\";')<br>&nbsp; &nbsp; fi<br>fi<br><br># Tradu&ccedil;&atilde;o das Fases para o Log e Interface<br>case \"$PHASE\" in<br>&nbsp; &nbsp; \"Copying\") PHASE_PT=\"Copiando\"; ICON=\"\ud83d\udcbe\" ;;<br>&nbsp; &nbsp; \"HealthCheckFsck\") PHASE_PT=\"Verificando FS\"; ICON=\"\ud83e\ude7a\" ;;<br>&nbsp; &nbsp; \"ThinningPreBackup\") PHASE_PT=\"Limpando\"; ICON=\"\ud83e\uddf9\" ;;<br>&nbsp; &nbsp; \"ThinningPostBackup\") PHASE_PT=\"Limpeza final\"; ICON=\"\ud83e\uddf9\" ;;<br>&nbsp; &nbsp; \"FindingBackupFiles\") PHASE_PT=\"Procurando\"; ICON=\"\ud83d\udd0d\" ;;<br>&nbsp; &nbsp; \"FindingChanges\") PHASE_PT=\"Procurando altera&ccedil;&otilde;es\"; ICON=\"\ud83d\udd0d\" ;;<br>&nbsp; &nbsp; \"Preparing\") PHASE_PT=\"Preparando\"; ICON=\"\u2699\ufe0f\" ;;<br>&nbsp; &nbsp; \"PreparingSourceVolumes\") PHASE_PT=\"Preparando discos\"; ICON=\"\ud83d\udcbd\" ;;<br>&nbsp; &nbsp; \"Mounting\") PHASE_PT=\"Montando\"; ICON=\"\ud83d\udcbf\" ;;<br>&nbsp; &nbsp; \"MountingDiskImage\") PHASE_PT=\"Montando imagem\"; ICON=\"\ud83d\udcbf\" ;;<br>&nbsp; &nbsp; \"Ocioso\") PHASE_PT=\"Ocioso\"; ICON=\"\u23f1\ufe0f\" ;;<br>&nbsp; &nbsp; *) PHASE_PT=\"$PHASE\"; ICON=\"\ud83d\udd04\" ;;<br>esac<br><br># ---------------------------------------------------------<br># L&Oacute;GICA DO HIST&Oacute;RICO E ROTA&Ccedil;&Atilde;O DE LOG<br># ---------------------------------------------------------<br>CURRENT_TIME=$(date +%s)<br>CURRENT_DATE=$(date +\"%d\/%m\/%Y %H:%M:%S\")<br><br>LAST_PHASE_PT=$(cat \"$STATE_FILE\" 2&gt;\/dev\/null)<br>PHASE_START=$(cat \"$START_FILE\" 2&gt;\/dev\/null)<br><br># Se a fase mudou, registra o tempo da fase anterior e inicia a nova<br>if [ \"$PHASE_PT\" != \"$LAST_PHASE_PT\" ]; then<br>&nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; # Se existia uma fase anterior registrada, calcula a dura&ccedil;&atilde;o<br>&nbsp; &nbsp; if [ -n \"$LAST_PHASE_PT\" ] &amp;&amp; [ -n \"$PHASE_START\" ]; then<br>&nbsp; &nbsp; &nbsp; &nbsp; DURATION=$((CURRENT_TIME - PHASE_START))<br>&nbsp; &nbsp; &nbsp; &nbsp; H=$((DURATION \/ 3600))<br>&nbsp; &nbsp; &nbsp; &nbsp; M=$(((DURATION % 3600) \/ 60))<br>&nbsp; &nbsp; &nbsp; &nbsp; S=$((DURATION % 60))<br>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp; &nbsp; &nbsp; FORMATTED_DURATION=\"\"<br>&nbsp; &nbsp; &nbsp; &nbsp; [ $H -gt 0 ] &amp;&amp; FORMATTED_DURATION=\"${H}h \"<br>&nbsp; &nbsp; &nbsp; &nbsp; [ $M -gt 0 ] &amp;&amp; FORMATTED_DURATION=\"${FORMATTED_DURATION}${M}m \"<br>&nbsp; &nbsp; &nbsp; &nbsp; FORMATTED_DURATION=\"${FORMATTED_DURATION}${S}s\"<br><br>&nbsp; &nbsp; &nbsp; &nbsp; echo \"[$CURRENT_DATE] \u23f9 Conclu&iacute;do: $LAST_PHASE_PT (Dura&ccedil;&atilde;o: $FORMATTED_DURATION)\" &gt;&gt; \"$LOG_FILE\"<br>&nbsp; &nbsp; fi<br><br>&nbsp; &nbsp; # Registra o in&iacute;cio da nova fase<br>&nbsp; &nbsp; if [ \"$PHASE_PT\" != \"Ocioso\" ]; then<br>&nbsp; &nbsp; &nbsp; &nbsp; echo \"[$CURRENT_DATE] \u25b6 Iniciado: $PHASE_PT\" &gt;&gt; \"$LOG_FILE\"<br>&nbsp; &nbsp; else<br>&nbsp; &nbsp; &nbsp; &nbsp; echo \"[$CURRENT_DATE] \u2705 Backup Finalizado \/ Inativo\" &gt;&gt; \"$LOG_FILE\"<br>&nbsp; &nbsp; &nbsp; &nbsp; echo \"---\" &gt;&gt; \"$LOG_FILE\"<br>&nbsp; &nbsp; fi<br><br>&nbsp; &nbsp; # Rota&ccedil;&atilde;o autom&aacute;tica do log: limite de 1000 linhas<br>&nbsp; &nbsp; MAX_LINES=1000<br>&nbsp; &nbsp; LINES=$(wc -l &lt; \"$LOG_FILE\" | awk '{print $1}')<br>&nbsp; &nbsp; if [ -n \"$LINES\" ] &amp;&amp; [ \"$LINES\" -gt \"$MAX_LINES\" ]; then<br>&nbsp; &nbsp; &nbsp; &nbsp; tail -n 500 \"$LOG_FILE\" &gt; \"${LOG_FILE}.tmp\"<br>&nbsp; &nbsp; &nbsp; &nbsp; mv \"${LOG_FILE}.tmp\" \"$LOG_FILE\"<br>&nbsp; &nbsp; fi<br><br>&nbsp; &nbsp; # Atualiza os arquivos de estado<br>&nbsp; &nbsp; echo \"$PHASE_PT\" &gt; \"$STATE_FILE\"<br>&nbsp; &nbsp; echo \"$CURRENT_TIME\" &gt; \"$START_FILE\"<br>fi<br># ---------------------------------------------------------<br><br># Pegando as vari&aacute;veis do sistema para a interface<br>PERCENT=$(echo \"$STATUS\" | awk -F'=' '\/^[[:space:]]*Percent =\/ {print $2}' | tr -d ' \";')<br>BYTES=$(echo \"$STATUS\" | awk -F'=' '\/^[[:space:]]*bytes =\/ {print $2}' | tr -d ' \";')<br>TOTAL_BYTES=$(echo \"$STATUS\" | awk -F'=' '\/^[[:space:]]*totalBytes =\/ {print $2}' | tr -d ' \";')<br>FILES=$(echo \"$STATUS\" | awk -F'=' '\/^[[:space:]]*files =\/ {print $2}' | tr -d ' \";')<br>TIME_REMAINING=$(echo \"$STATUS\" | awk -F'=' '\/^[[:space:]]*TimeRemaining =\/ {print $2}' | tr -d ' \";')<br><br>FILES_TEXT=\"\"<br>if [ -n \"$FILES\" ]; then<br>&nbsp; &nbsp; FILES_TEXT=\" ($FILES arqs)\"<br>fi<br><br>TIME_TEXT=\"\"<br>if [ -n \"$TIME_REMAINING\" ] &amp;&amp; [ \"$TIME_REMAINING\" != \"0\" ]; then<br>&nbsp; &nbsp; TIME_TEXT=$(awk -v t=\"$TIME_REMAINING\" 'BEGIN {<br>&nbsp; &nbsp; &nbsp; &nbsp; t = int(t)<br>&nbsp; &nbsp; &nbsp; &nbsp; h = int(t \/ 3600)<br>&nbsp; &nbsp; &nbsp; &nbsp; m = int((t % 3600) \/ 60)<br>&nbsp; &nbsp; &nbsp; &nbsp; if (h &gt; 0) printf \" ~%dh%02dm\", h, m<br>&nbsp; &nbsp; &nbsp; &nbsp; else if (m &gt; 0) printf \" ~%dm\", m<br>&nbsp; &nbsp; &nbsp; &nbsp; else printf \" &lt;1m\"<br>&nbsp; &nbsp; }')<br>fi<br><br># Exibi&ccedil;&atilde;o principal na barra de menus<br>if [ \"$PHASE_PT\" == \"Ocioso\" ]; then<br>&nbsp; &nbsp; echo \"$ICON TM: Ocioso\"<br>elif [ -z \"$PERCENT\" ] || [ \"$PERCENT\" == \"-1\" ] || [ \"$PERCENT\" == \"-1.0\" ]; then<br>&nbsp; &nbsp; echo \"$ICON TM: $PHASE_PT\"<br>else<br>&nbsp; &nbsp; PCT_TEXT=$(awk -v p=\"$PERCENT\" 'BEGIN { printf \"%.1f%%\", p * 100 }')<br>&nbsp; &nbsp; echo \"$ICON TM: $PCT_TEXT$FILES_TEXT$TIME_TEXT\"<br>fi<br><br># Conte&uacute;do do Dropdown (ao clicar)<br>echo \"---\"<br>if [ \"$PHASE_PT\" == \"Ocioso\" ]; then<br>&nbsp; &nbsp; echo \"Nenhum backup em andamento.\"<br>else<br>&nbsp; &nbsp; echo \"Fase Atual: $PHASE_PT\"<br>&nbsp; &nbsp; if [ -n \"$BYTES\" ] &amp;&amp; [ -n \"$TOTAL_BYTES\" ] &amp;&amp; [ \"$TOTAL_BYTES\" != \"0\" ]; then<br>&nbsp; &nbsp; &nbsp; &nbsp; COPIED_GB=$(awk -v b=\"$BYTES\" 'BEGIN { printf \"%.2f\", b \/ 1000000000 }')<br>&nbsp; &nbsp; &nbsp; &nbsp; TOTAL_GB=$(awk -v t=\"$TOTAL_BYTES\" 'BEGIN { printf \"%.2f\", t \/ 1000000000 }')<br>&nbsp; &nbsp; &nbsp; &nbsp; echo \"Tamanho: $COPIED_GB GB de $TOTAL_GB GB\"<br>&nbsp; &nbsp; fi<br>fi<br><br># Exibe o hist&oacute;rico diretamente no menu<br>echo \"---\"<br>echo \"Hist&oacute;rico Recente:\"<br>if [ -s \"$LOG_FILE\" ]; then<br>&nbsp; &nbsp; tail -n 15 \"$LOG_FILE\" | while read -r line; do<br>&nbsp; &nbsp; &nbsp; &nbsp; echo \"$line | font=Menlo size=11 trim=false\"<br>&nbsp; &nbsp; done<br>else<br>&nbsp; &nbsp; echo \"Nenhum hist&oacute;rico registrado ainda.\"<br>fi<br><br>echo \"---\"<br>if [ \"$PHASE_PT\" == \"Ocioso\" ]; then<br>&nbsp; &nbsp; echo \"Iniciar Backup | bash='tmutil' param1='startbackup' terminal=false\"<br>else<br>&nbsp; &nbsp; echo \"Parar Backup | bash='tmutil' param1='stopbackup' terminal=false\"<br>fi<br>echo \"Abrir Log Completo | bash='open' param1=\\\"$LOG_FILE\\\" terminal=false\"<\/pre>\n<h2>Resultado na pr&aacute;tica<\/h2>\n<p>Hoje consigo olhar para a barra de menus e saber, em um segundo, se o backup est&aacute; andando e quanto falta. N&atilde;o preciso mais abrir o Terminal nem ficar adivinhando se algo travou.<\/p>\n<p><figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/www.bernabauer.com\/blog\/wp-content\/uploads\/2026\/10\/monitor-de-backup-do-time-machine-na-barra-de-menus-do-mac-2.jpg\" alt=\"Dual external hard drives on a docking station with an orange backdrop.\"\/><figcaption><a href=\"https:\/\/www.pexels.com\/photo\/external-hard-drives-on-docking-station-28461160\/\" rel=\"noopener\">Foto: Alpha En \/ Pexels<\/a><\/figcaption><\/figure>\n<\/p>\n<p>Dois aprendizados ficaram. O primeiro &eacute; que o formato de sa&iacute;da do tmutil pode variar entre vers&otilde;es do macOS, ent&atilde;o vale testar o script no terminal antes de coloc&aacute;-lo no SwiftBar. O segundo &eacute; que o log cresce com o tempo, e uma rota&ccedil;&atilde;o simples evita que ele ocupe espa&ccedil;o &agrave; toa.<\/p>\n<h2>Se voc&ecirc; quiser replicar<\/h2>\n<ul>\n<li>Instale o SwiftBar e defina uma pasta para os plugins.<\/li>\n<li>Escreva o script primeiro no terminal, imprimindo apenas os dados que precisa.<\/li>\n<li>Escolha um intervalo de atualiza&ccedil;&atilde;o razo&aacute;vel para n&atilde;o consumir recursos desnecess&aacute;rios.<\/li>\n<li>Crie o processo de log separado, para que o registro continue mesmo com o menu fechado.<\/li>\n<\/ul>\n<p>Se voc&ecirc; usa o Time Machine com um servidor pr&oacute;prio, esse pequeno projeto d&aacute; uma tranquilidade que o sistema n&atilde;o oferece de f&aacute;brica. Em poucas horas d&aacute; para ter algo funcional, e depois &eacute; s&oacute; ajustar ao seu gosto.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Montei um monitor pr\u00f3prio com SwiftBar e um script shell para acompanhar em tempo real os backups do Time Machine no meu servidor Ubuntu. Veja como extrair porcentagem, arquivos e tempo restante e manter um log com a dura\u00e7\u00e3o de cada fase.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[7],"tags":[13,110,79,136,137,138],"class_list":["post-384","post","type-post","status-publish","format-standard","hentry","category-produtividade","tag-automacao","tag-backup","tag-macos","tag-shell-script-2","tag-swiftbar-2","tag-time-machine-2"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/posts\/384","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/comments?post=384"}],"version-history":[{"count":1,"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/posts\/384\/revisions"}],"predecessor-version":[{"id":386,"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/posts\/384\/revisions\/386"}],"wp:attachment":[{"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/media?parent=384"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/categories?post=384"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bernabauer.com\/blog\/wp-json\/wp\/v2\/tags?post=384"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}