Narrativa real de campo
Por que eu deixei o backup “oficial” de lado
Tem ambiente de TI que, olhando de fora, parece perfeito: servidor dedicado, SQL Server licenciado, sistema em produção há anos e processos já estabelecidos. Mesmo assim, um tropeço pode levar tudo ao caos quando o backup vira promessa vaga. A frase "depois a gente vê" sempre parece inofensiva até o dia em que o disco volta sem sincronizar.
Não se trata de expor cliente nem de falar mal de ninguém. Este caso poderia ser de qualquer empresa brasileira que depende do “acho que está fazendo backup sim". O servidor é on-premise, roda Microsoft SQL Server e, se cai, a operação trava. O plano de backup do banco em si seguia o básico que qualquer DBA cobraria: rotinas diárias bem definidas, janelas específicas, retenção mínima combinada (por exemplo, alguns ciclos de full + diferenciais), arquivos .bak versionados e testes de restore em ambiente separado para não descobrir integridade quebrada só depois do desastre. Sem entrar nos detalhes técnicos, para quem não é da área vale registrar que esse “básico” envolve, no mínimo, ter cópias completas em uma cadência compatível com o negócio, backups também dos bancos de sistema (onde vivem logins, jobs e configurações) e uma rotina de verificação periódica de restauração. Ou seja: a parte de gerar e recuperar backup do SQL Server estava sob controle.
O recorte deste relato não é “como fazer backup de SQL Server do zero”, mas sim o que vem depois: o backup do backup. A última camada, aquela cópia externa que segura o negócio quando tudo o mais falha. E aí entra a parte sensível: por custo, o cliente escolheu o caminho mais barato possível — Google Drive. Especialistas podem torcer o nariz, eu também torci, mas existia um fato incontornável: era isso ou nada. Sem appliance, sem storage dedicado, sem mensalidade adicional. Minha missão foi fazer caber no orçamento zero, sem rasgar completamente os princípios de segurança e recuperabilidade, mantendo um RPO/RTO minimamente honestos para a realidade do cliente.
O problema é que o “jeito oficial” — o cliente de sincronização do Google Drive instalado no próprio servidor — começou a falhar em silêncio. Em alguns momentos, sincronizava tudo direitinho; em outros, por instabilidade de conexão ou por motivos que o software não se dava ao trabalho de explicar, simplesmente parava. Sem aviso, sem erro claro, sem log minimamente amigável. Num ambiente sem interface gráfica, administrado exclusivamente por SSH, isso é uma armadilha clássica: o backup era gerado, os arquivos estavam lá no disco, mas a cópia para a nuvem simplesmente não acontecia. E é bom reforçar: aqui não estou discutindo toda a estratégia de backup da rede, apenas a última camada de proteção — a replicação desses arquivos para a cloud.
E, antes que alguém pergunte: “Mas se Windows Server tem interface gráfica, por que você não usa?”. Porque não é pra usar. Por política e por segurança, o acesso administrativo é feito apenas via SSH, e tudo o que é supérfluo (incluindo serviços gráficos desnecessários) foi desabilitado. Em resumo: depender de um cliente gráfico de sincronização num servidor tratado como servidor de verdade foi a receita perfeita para um backup que parecia existir, mas não saía da máquina.
A partir daqui, a ideia foi transformar um risco silencioso em um fluxo mais honesto e observável: usar rclone, PowerShell e Task Scheduler para automatizar esse “backup do backup”, com logs claros, execução em background e rodando mesmo sem ninguém logado. O banco continua seguindo o plano de backup tradicional; o que eu faço é garantir que esses arquivos cheguem, de forma confiável, à camada externa de proteção, dentro das restrições de custo que o próprio cliente definiu.
Contexto e inventário
O problema concreto
Antes de qualquer linha de script, precisei mapear o que realmente acontecia no servidor: um Windows Server 2019 acessado só via SSH/PowerShell, com o Google Drive File Stream instalado e uma política explícita de não habilitar GUI. O resultado: ninguém percebia quando o “backup para nuvem” simplesmente não acontecia. Quer dizer, percebia sim, quando acessava o Google Drive de outro lugar e via que os arquivos não estavam lá.
🖥️ Estado inicial
📁 Estrutura de backups local
Z:\Arquivos.C:\Program Files\Microsoft SQL Server\...\Backup.🎯 Requisitos inegociáveis
Tentativas e erros
Por que abandonei o cliente oficial do Google Drive
Quando percebi a falha na sincronização, minha primeira reação foi tentar ressuscitar o Google Drive Desktop: matar o processo, procurar o executável, subir a versão mais nova e torcer para tudo normalizar. A ideia era simples: zerar o estado e forçar um recomeço limpo.
Vasculhei as pastas de instalação padrão, encontrei mais de uma versão do cliente e identifiquei qual era a build mais recente instalada no Program Files. A partir daí, tentei subir deliberadamente essa versão mais nova, como se estivesse dando um “reset consciente” no serviço.
Na prática, isso significou: encerrar o processo antigo, localizar todas as instâncias do cliente oficial e iniciar manualmente a última versão disponível, esperando que a sincronização voltasse ao normal. Foi o último esforço para reaproveitar a solução original antes de aceitar o óbvio: depender de um cliente gráfico, projetado para desktop, em um servidor administrado só por SSH não era sustentável. A partir desse ponto, a decisão foi abandonar o cliente oficial e partir para uma solução scriptável com rclone.
Os logs em C:\Users\<usuario>\AppData\Local\Google\DriveFS\logs são binários e impraticáveis via SSH. O fluxo do app pressupõe navegador local, prompts visuais e cliques. Servidor tratado como servidor não pode depender disso. A conclusão ficou óbvia: entrar em campo com o rclone, ferramenta nascida para automação, CLI pura e multiplataforma.
Ferramenta certa
Instalando e preparando o rclone
Baixei o rclone para Windows, extraí o binário em C:\Tools\rclone\ e testei. Um executável, sem dependências de GUI e pronto para conversar com o Drive.
$rcloneUrl = "https://downloads.rclone.org/rclone-current-windows-amd64.zip"
$destDir = "C:\Tools\rclone"
$zipPath = Join-Path $destDir "rclone.zip"
# 1) Garante a pasta de instalação
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
# 2) Baixa o rclone em formato ZIP
Invoke-WebRequest -Uri $rcloneUrl -OutFile $zipPath
# 3) Descompacta o ZIP para a pasta de destino
Expand-Archive -Path $zipPath -DestinationPath $destDir -Force
# 4) Localiza o executável rclone.exe dentro da pasta extraída
$rcloneExe = Get-ChildItem $destDir -Recurse -Filter "rclone.exe" |
Select-Object -First 1 -ExpandProperty FullName
# 5) Verifica a instalação
& $rcloneExe version
# 6) Configura o remote "gdrive" e valida o acesso
& $rcloneExe config
& $rcloneExe lsd gdrive:
Executei o rclone config, criei o remote gdrive, defini o escopo completo (opção 1) e finalizei a autenticação a partir de outra máquina. Tudo salvo no rclone.conf.
O resultado listou as pastas do cliente e confirmou que o remote estava operacional. A partir daí, ficou claro o que eu precisava subir: um diretório lógico para os backups do SQL Server (gdrive:MSSQL Backup) e outro para os arquivos compactados (gdrive:Arquivos Backup).
Automação de verdade
Do upload manual ao daemon em PowerShell
Antes de automatizar, conferi se o rclone copiava tudo certo. Criei Z:\logs, rodei um rclone copy para o MSSQL e outro para os arquivos. Os arquivos estavam lá e o log mostrou transferências 100% concluídas. Moral da história: no mundo CLI, log é lei.
New-Item -ItemType Directory -Path "Z:\logs" -Force | Out-Null
& "C:\Tools\rclone\rclone.exe" copy `
"C:\Program Files\Microsoft SQL Server\...\Backup" `
"gdrive:MSSQL Backup" `
--create-empty-src-dirs `
--transfers 4 `
--checkers 8 `
--log-file "Z:\logs\rclone-mssql-inicial.log" `
--log-level INFO `
--stats=30s
& "C:\Tools\rclone\rclone.exe" copy `
"Z:\Arquivos" `
"gdrive:Arquivos Backup" `
--create-empty-src-dirs `
--transfers 4 `
--checkers 8 `
--log-file "Z:\logs\rclone-arquivos-inicial.log" `
--log-level INFO `
--stats=30s
Get-Content "Z:\logs\rclone-arquivos-inicial.log" -Tail 20
Mas como “manual” não escala, escrevi um script PowerShell que virou um mini-serviço: sync inicial, dois FileSystemWatcher (Arquivos recursivo e MSSQL focado em .bak), flags para silêncio de 120 segundos e disparo automático do rclone copy. Logs separados para cada rodada.
param(
[string]$CobianDir = "Z:\Arquivos",
[string]$SqlBackupDir = "C:\Program Files\Microsoft SQL Server\...\Backup",
[int]$QuietSeconds = 120
)
$Rclone = "C:\Tools\rclone\rclone.exe"
$LogDir = "Z:\logs"
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
function Write-Log([string]$msg) {
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "$stamp - $msg"
}
function Sync-Files {
Write-Log "Iniciando sync Arquivos..."
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$logFile = Join-Path $LogDir "rclone-arquivos-$timestamp.log"
& $Rclone copy $CobianDir 'gdrive:Arquivos Backup' `
--create-empty-src-dirs `
--transfers 4 `
--checkers 8 `
--log-file=$logFile `
--log-level INFO `
--stats=30s
Write-Log "Sync Arquivos concluído."
}
function Sync-Sql {
Write-Log "Iniciando sync MSSQL..."
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$logFile = Join-Path $LogDir "rclone-mssql-$timestamp.log"
& $Rclone copy $SqlBackupDir 'gdrive:MSSQL Backup' `
--create-empty-src-dirs `
--transfers 4 `
--checkers 8 `
--log-file=$logFile `
--log-level INFO `
--stats=30s
Write-Log "Sync MSSQL concluído."
}
if (-not (Test-Path $CobianDir)) { Write-Log "AVISO: pasta Arquivos não encontrada: $CobianDir" }
if (-not (Test-Path $SqlBackupDir)) { Write-Log "AVISO: pasta MSSQL não encontrada: $SqlBackupDir" }
Sync-Files
Sync-Sql
$fswFiles = New-Object System.IO.FileSystemWatcher $CobianDir, "*.*"
$fswFiles.IncludeSubdirectories = $true
$fswFiles.EnableRaisingEvents = $true
$fswFiles.NotifyFilter = [IO.NotifyFilters]'FileName, DirectoryName, LastWrite, Size'
$fswSql = New-Object System.IO.FileSystemWatcher $SqlBackupDir, "*.bak"
$fswSql.IncludeSubdirectories = $false
$fswSql.EnableRaisingEvents = $true
$fswSql.NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite, Size'
$script:FilesPending = $false
$script:FilesLastEvent = Get-Date
$script:SqlPending = $false
$script:SqlLastEvent = Get-Date
$actionFiles = {
param($src, $eventArgs)
$script:FilesPending = $true
$script:FilesLastEvent = Get-Date
Write-Host "$(Get-Date -Format 'u') - Arquivos: $($eventArgs.ChangeType) $($eventArgs.FullPath)"
}
$actionSql = {
param($src, $eventArgs)
$script:SqlPending = $true
$script:SqlLastEvent = Get-Date
Write-Host "$(Get-Date -Format 'u') - MSSQL: $($eventArgs.ChangeType) $($eventArgs.FullPath)"
}
$regFilesCreated = Register-ObjectEvent -InputObject $fswFiles -EventName Created -Action $actionFiles
$regFilesChanged = Register-ObjectEvent -InputObject $fswFiles -EventName Changed -Action $actionFiles
$regFilesDeleted = Register-ObjectEvent -InputObject $fswFiles -EventName Deleted -Action $actionFiles
$regFilesRenamed = Register-ObjectEvent -InputObject $fswFiles -EventName Renamed -Action $actionFiles
$regSqlCreated = Register-ObjectEvent -InputObject $fswSql -EventName Created -Action $actionSql
$regSqlChanged = Register-ObjectEvent -InputObject $fswSql -EventName Changed -Action $actionSql
$regSqlDeleted = Register-ObjectEvent -InputObject $fswSql -EventName Deleted -Action $actionSql
$regSqlRenamed = Register-ObjectEvent -InputObject $fswSql -EventName Renamed -Action $actionSql
try {
Write-Log "Watchers iniciados. QuietSeconds = $QuietSeconds."
while ($true) {
Start-Sleep -Seconds 5
if ($script:FilesPending) {
$elapsed = (Get-Date) - $script:FilesLastEvent
if ($elapsed.TotalSeconds -ge $QuietSeconds) {
$script:FilesPending = $false
Sync-Files
}
}
if ($script:SqlPending) {
$elapsed = (Get-Date) - $script:SqlLastEvent
if ($elapsed.TotalSeconds -ge $QuietSeconds) {
$script:SqlPending = $false
Sync-Sql
}
}
}
}
finally {
foreach($reg in @(
$regFilesCreated, $regFilesChanged, $regFilesDeleted, $regFilesRenamed,
$regSqlCreated, $regSqlChanged, $regSqlDeleted, $regSqlRenamed
)) {
if ($reg) { Unregister-Event -SubscriptionId $reg.Id }
}
$fswFiles.Dispose()
$fswSql.Dispose()
}
Executei manualmente com powershell -ExecutionPolicy Bypass -File "C:\Users\...\Scripts\backup-sync-daemon.ps1". O log mostrou o sync inicial, a criação dos watchers e o loop eterno, exatamente como eu precisava.
Faltava só garantir que o daemon subisse com o Windows e rodasse sem usuário logado. O Agendador de Tarefas foi escolha natural, mas carrega três armadilhas clássicas: quebrar linha do comando, esquecer a senha do /RU e misturar PowerShell com ^ do cmd.exe. Depois de tomar uns tombos, acertei a receita.
schtasks /Delete /TN "RcloneBackupDaemon" /F
schtasks /Create /TN "RcloneBackupDaemon" `
/SC ONSTART `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Users\...\Scripts\backup-sync-daemon.ps1" `
/RU "zerocopia" `
/RP *
schtasks /Run /TN "RcloneBackupDaemon"
schtasks /Query /TN "RcloneBackupDaemon" /V /FO LIST
Com o modo de logon ajustado para “Interativo/segundo plano”, o status passou a ser “Em execução”. Pronto: daemon ativo, logs vivos e Drive alimentado sem intervenção humana.
Fechamento
Lições e resultado final
Resultado final: arquivos em Z:\Arquivos replicando para gdrive:Arquivos Backup, SQL Server em ...\MSSQL\Backup replicando para gdrive:MSSQL Backup, script subindo com o Windows e toda execução registrada em Z:\logs. Não é arquitetura perfeita para um corporate gigante, mas, dentro das restrições reais (zero custo extra, acesso via SSH, política rígida), tirou o backup do campo da esperança e levou para o terreno da observabilidade.
Se você está em situação parecida — Windows Server enxuto, pouco orçamento e Google Drive como destino possível — este combo rclone + PowerShell + Task Scheduler é um ponto de partida honesto. E, quando o orçamento chegar, basta trocar o destino: a orquestração continua a mesma.