← LAB

ifolder-sync

Ver no GitHub →

A ideia desse projeto nasceu de uma coceira bem específica e bem pessoal: eu queria que uma única pasta — o meu vault do Obsidian — de uma conta do iCloud diferente da que está logada no meu Mac ficasse sincronizada localmente, em tempo real, sem eu precisar logar aquela conta inteira no sistema, criar outro usuário no macOS ou sincronizar o Drive inteiro dela. O macOS simplesmente não faz isso de forma nativa, então fui atrás de resolver na unha.

Principais Destaques

Pois bem, esse aqui é um projetinho de escopo bem menor que os outros do LAB, mas que tem mais engenharia escondida do que aparenta. Vamos por partes.

  1. O problema que ele resolve. O macOS só sincroniza nativamente o iCloud Drive da conta logada no sistema — não existe forma de montar localmente o Drive de uma segunda conta. O ifolder-sync alcança a outra conta pela API web (não-oficial) do iCloud, via pyicloud: você autentica com Apple ID + senha + 2FA da conta a sincronizar, a sessão é salva (o 2FA é pedido uma única vez, com confiança de ~90 dias na prática) e, a partir daí, a pasta escolhida espelha local ↔ remoto sozinha.

  2. Sincronização bidirecional de verdade, com baseline de três vias. O coração do engine é um baseline em SQLite que guarda a assinatura (tamanho + mtime) de cada arquivo como ele estava no último sync, por lado. Cada passada compara o estado atual de cada lado contra esse baseline — e é exatamente isso que diferencia “arquivo novo aqui” de “arquivo deletado lá” (sem o baseline, os dois pareceriam idênticos). Mudou de um lado só → copia pro outro; mudou dos dois → conflito (resolvido por política); sumiu de um lado sem ter mudado → propaga a deleção. Cada passada decide todas as ações primeiro e só depois aplica — é o que habilita o --dry-run e o threshold de deleção mais abaixo.

  3. Detecção de mudança em dois canais. No lado local, um watcher de FSEvents (watchdog) com debounce — quase instantâneo. No lado remoto, polling adaptativo: enquanto há mudança fluindo ele acelera (a cada 20s), e quando esfria volta pro intervalo ocioso (60s). Não existe webhook do iCloud, então mudança remota só aparece no próximo poll. O barato fica barato porque os etags das pastas do iCloud fingerprintam subtrees inteiras — uma passada sem novidade custa cerca de uma chamada de rede.

  4. O modelo de segurança — onde mora a engenharia de verdade. Sincronização bidirecional em cima de uma API reverse-engineered pode falhar de formas imprevisíveis, então o engine é defensivo por padrão. Os guard-rails que valem citar:

    1. Walk guard nos dois lados: se a listagem remota falhar (rede/auth) ou o scan local bater em erro de permissão / raiz do vault sumida, a passada aborta com zero deleções, em vez de confundir visibilidade parcial com “deletaram tudo”.

    2. Marcador de identidade do vault: um arquivo local .ifolder-sync-vault amarra o baseline à pasta que o originou. Moveu ou recriou o vault? O daemon para com “vault identity mismatch” em vez de deduzir deleções fantasma — você recupera com ifolder-sync rebaseline, e a próxima passada vira puramente aditiva (baixa e sobe, nunca deleta).

    3. Threshold de deleção: se uma única passada fosse apagar mais de 50% dos arquivos rastreados (ou mais de 100), ela pula as deleções e avisa. Override consciente com sync --force-delete. Se isso dispara logo após o start, ele sinaliza DRIFT SUSPECTED; depois de 3 trips seguidos, desacelera o polling até uma passada limpa.

    4. Bootstrap pass: a primeira passada após o start transfere conteúdo normalmente, mas defere todas as deleções pra próxima passada. Decisão destrutiva nunca anda em cima de uma visão de startup possivelmente velha.

    5. Soft-delete recuperável: deleção local vai pra um trash fora do vault; deleção remota vai pro “Recently Deleted” do iCloud (recuperável por ~30 dias). Nada some de verdade num primeiro momento.

  5. Tratamento inteligente do .obsidian/ (opt-in). A pasta de config do Obsidian mistura duas coisas que não podem ser sincronizadas do mesmo jeito: os assets compartilhados (código de plugin, temas, snippets, ícones — esses sincronizam, pra você ter os mesmos plugins em todo dispositivo) e a config volátil por-dispositivo (workspace.json, appearance.json, community-plugins.json e cia — esses nunca sincronizam, porque é justamente o que causa o tema “voltando” sozinho e a lista de plugins ficando incoerente entre desktop e mobile). Liga com ifolder-sync init --obsidian; numa pasta comum, sincroniza o .obsidian/ como qualquer outro conteúdo.

  6. Set-and-forget via launchd. O start --background entrega o daemon pro launchd, o próprio service manager do macOS: ele sobe em todo login (RunAtLoad), é reiniciado se crashar (KeepAlive) e sobrevive a logout e reboot sem nenhum passo extra de “adicionar à inicialização”. Um agente por profile. A única coisa que desliga é você (ifolder-sync stop).

Outras funcionalidades

  • Profiles (várias pastas): cada pasta sincronizada é um profile isolado, com config, state, lock e agente launchd próprios. Um scan que falha ou um crash num profile nunca toca o baseline de outro. As sessões são compartilhadas por Apple ID — profiles na mesma conta reaproveitam um único 2FA; contas diferentes ganham sessões separadas automaticamente.

  • Senha nunca em arquivo: a resolução segue uma ordem — variável de ambiente, depois o Keychain do macOS, e por fim o prompt interativo (que, no sucesso, salva no Keychain automaticamente pro daemon não-interativo conseguir conectar depois). Tem que ser a senha primária do Apple ID; app-specific passwords não funcionam no fluxo web/SRP que o iCloud Drive usa.

  • 2FA com fallback de verdade: o ifolder-sync dispara o próprio push e oferece reenvio, SMS e diagnóstico — matando o clássico “pede o código mas nenhum chega” dos logins por API.

  • Política de conflito configurável: newer (default — o mtime mais novo ganha e o perdedor é salvo como .conflict-…, localmente, sem perder nada), local, remote ou both.

  • Suíte de testes sem credencial: um iCloud fake em memória cobre a matriz bidirecional inteira (create/edit/delete dos dois lados, idempotência, conflito), a taxonomia do Obsidian e os guard-rails de segurança (walk guards, threshold, path traversal, locking, soft-delete, dry-run).

  • As limitações que eu assumo abertamente: isso é API reverse-engineered da Apple — pode quebrar quando a Apple mexer em algo, não tem SLA nem suporte oficial e está sujeita a rate limit. O Advanced Data Protection (ADP) é incompatível: ligar ADP no Apple ID encerra o acesso web ao iCloud Drive por completo (isso afeta toda ferramenta dessa categoria, não só essa). E é honestamente alpha, feito pro meu uso pessoal — não aponte pra um vault sem backup completo. Uso por sua conta e risco, como diz a licença MIT.

  • Documentação: o mapa completo de componentes, o diagrama de sequência da passada de sync, o modelo de segurança e o de performance estão em docs/ARCHITECTURE.md.

Posts relacionados

Ainda não citado em nenhum post.