Uso o Syncthing há anos. É um daqueles raros pedaços de software que faz exatamente uma coisa e faz direito: mantém pastas sincronizadas entre seus próprios dispositivos, direto e com criptografia ponta a ponta, sem conta na nuvem e sem uma empresa sentada no meio. Meu MacBook, meus servidores e meus backups falam Syncthing.

Meu iPhone não falava.

Essa é a história de como eu resolvi isso em cerca de dois dias, trabalhando com o Claude, o assistente de código da Anthropic, e por que o resultado, o SuperSynch, é open source sob a mesma licença do próprio Syncthing.

Por que uma versão iOS open source importa

O Syncthing roda em Linux, Windows, macOS, nos BSDs e mais. No Android tem apps da comunidade. No iOS a situação sempre foi mais difícil. Não existe app oficial, porque as regras de plataforma da Apple deixam um daemon de sincronização em segundo plano desconfortável: apps não podem rodar continuamente, não podem varrer a rede livremente, e vivem dentro de uma sandbox.

Tem gente boa trabalhando nisso. O Synctrain, por exemplo, é um app iOS open source construído sobre o Syncthing. Mas quando olhei pro que eu queria, algumas coisas ficaram claras:

  1. O telefone deveria ser um dispositivo Syncthing de verdade. Nada de controle remoto nem serviço de relay meu: o iPhone e o Mac deveriam conversar entre si diretamente, do mesmo jeito que minhas outras máquinas fazem.
  2. Tem que ser open source. Software que segura meus arquivos, minhas chaves de dispositivo e minhas conexões de rede é software que eu quero poder ler. E o Syncthing é um bem comum; qualquer coisa construída sobre ele deveria devolver pra esse bem comum.
  3. Eu queria entender cada camada. Da engine em Go às views em SwiftUI, eu queria algo que pudesse manter, explicar e mudar.

Então decidi construir um, e fazer isso abertamente.

Um começo errado, e por que ele foi útil

Minha primeira especificação estava, francamente, errada. Pedi um “controlador remoto”: um app nativo que fala com instâncias do Syncthing rodando em outro lugar (um NAS, um servidor, um Mac) através da REST API do Syncthing.

O Claude construiu exatamente isso, e construiu bem:

  • um app SwiftUI pra iPhone e iPad,
  • suporte a múltiplos servidores,
  • chaves de API no Keychain,
  • um fluxo trust-on-first-use pros certificados HTTPS autoassinados do Syncthing (mostrar o fingerprint, confirmar, fixar),
  • um dashboard ao vivo movido pelo stream de eventos do Syncthing,
  • testes que rodavam contra uma instância Syncthing real.

Daí olhei pra coisa e percebi que ela derrotava o propósito inteiro. Pra usar, eu teria que expor a GUI do Syncthing do MacBook pro telefone. E ainda assim não colocaria um único arquivo no telefone. Eu não queria um controle remoto. Eu queria que o telefone fosse um nó.

Então pivotamos. O que me surpreendeu foi quanto sobreviveu ao pivot: os modelos, a lógica de processamento de eventos, a maior parte da UI. Mais sobre isso abaixo.

Como funciona: Syncthing, compilado pra iOS

O Syncthing é escrito em Go, e Go pode ser compilado num framework iOS usando o gomobile. O plano virou:

App SwiftUI  ──►  Cliente Swift (async/await)  ──►  Ponte Go  ──►  Syncthing v2.1.5
                                                    (gomobile)      (lib/syncthing)

Antes de escrever qualquer código de ponte, o Claude foi ler o código do Syncthing pra conferir se embedding era de fato possível. Era, mas com algumas peculiaridades que já viram uma boa história por si só.

A peculiaridade do module path. As tags de release do Syncthing v2 não dão pra usar com um go get simples. O sistema de módulos do Go espera um sufixo /v2 no caminho do módulo, e o do Syncthing não é nomeado assim. A solução foi fixar o commit exato do v2.1.5 como pseudo-versão. Coisa pequena, mas o tipo de detalhe que come uma tarde.

Os assets da web GUI. O lib/syncthing puxa o pacote da web GUI, cujos assets são gerados pelos próprios scripts de build do Syncthing. O Syncthing tem uma build tag noassets exatamente pra esse caso, e como o nó embutido não precisa de web GUI, tudo é compilado com ela.

Os folder summaries faltando. Esse foi sutil. Num teste inicial, pastas no nó embutido nunca atualizavam a contagem de arquivos. Depois de cavar o código, achamos a causa: o Syncthing só inicia o folder summary service, que emite os eventos FolderSummary e FolderCompletion, junto com a web GUI. Sem GUI, sem summaries. A ponte agora inicia esse serviço ela mesma.

Alcançando o modelo. O lib/syncthing deliberadamente expõe parte dos próprios internals pra quem embute. Mas algumas coisas que a GUI mostra não estão nesse subset: estatísticas de conexão por dispositivo, dispositivos pendentes lembrados entre reinicializações, estatísticas de pasta e versões de arquivo. A ponte alcança o modelo completo através de um pedaço pequeno e documentado de reflection. É verificado por testes e marcado pra reconferir a cada upgrade do Syncthing. É o compromisso mais pragmático do projeto, e eu adoraria que se tornasse desnecessário (ver abaixo).

Mantendo o lado Swift chato

A decisão de design com a qual estou mais contente: cada pedaço de dado atravessa a fronteira Go→Swift como JSON moldado exatamente como a REST API do Syncthing. A ponte não é um servidor REST; nada escuta na rede. Mas as funções dela retornam os mesmos formatos de /rest/db/status, /rest/system/connections, /rest/events e por aí vai.

Isso significou que o código Swift da versão de controle remoto abandonada veio junto quase inalterado:

  • os modelos de decoding (escritos pra serem tolerantes, porque a API do Syncthing evolui),
  • o event reducer puro que transforma o stream de eventos do Syncthing em estado de UI,
  • a sessão ao vivo que acompanha eventos e cai pra polling como fallback.

O começo errado virou a fundação.

Mais um detalhe: chamadas Go bloqueiam. Um long-poll de eventos pode ficar esperando até o timeout dele. Então o wrapper Swift roda essas chamadas numa fila dedicada em vez do pool cooperativo de threads do Swift, e cancelamento retorna imediatamente em vez de esperar o Go.

O que o app faz

  • Pareamento: seu iPhone mostra o device ID Syncthing dele como QR code; você escaneia ou cola o ID do seu Mac.
  • Integração com o app Files: pastas sincronizadas vivem em Files › No Meu iPhone › SuperSynch, usáveis por qualquer outro app. Também tem um browser embutido com Quick Look.
  • Sync seletivo: um editor de padrões de ignore deixa você manter só parte de uma pasta grande no telefone.
  • Versões e conflitos: versionamento de arquivos com restore, e uma lista de conflitos onde você escolhe qual cópia vence.
  • Dispositivos não confiáveis: compartilhar uma pasta criptografada com senha.
  • Consciente de dados: sync só em Wi-Fi, um switch de Modo de Baixo Consumo de Dados e limites de banda.
  • iPad: layout de três colunas com atalhos de teclado. iPhone e iPad compartilham a mesma codebase; só dois arquivos pequenos diferem.
  • Sem telemetria: reporte de uso e crash do Syncthing, auto-upgrade e web GUI estão todos desligados.

Sendo honesto sobre o iOS

Insisti pra que o README trouxesse uma seção Limitations clara, porque nada corrói confiança mais rápido do que um app que promete demais.

  • Sem sync contínuo em background. O iOS não deixa um app rodar o tempo todo. O SuperSynch sincroniza enquanto está aberto, termina transferências por uns 30 segundos depois que você sai, e usa o tempo de background que o iOS concede, frequentemente durante o carregamento. Na prática: abre o app, e ele se atualiza.
  • Sem descoberta de rede local ainda. O Syncthing encontra dispositivos numa LAN com broadcasts UDP, que no iOS precisam de uma entitlement de multicast que a Apple tem que aprovar. Até lá, descoberta global, relays ou um endereço inserido manualmente dão conta.
  • Arquivos inteiros, não sob demanda. Tudo que não é ignorado é baixado. Arquivos sob demanda estão no roadmap.

Bugs que me ensinaram alguma coisa

Três momentos da construção se destacam.

“Não mexe no meu Syncthing de verdade.” No meio do processo, enquanto testava no simulador, o Claude notou que a porta 22000 já estava ocupada, pelo Syncthing de verdade do meu Mac, que tinha uma conexão viva com outro dos meus dispositivos. O simulador compartilha a rede do Mac, então um teste descuidado podia ter conectado nele. Ele parou, me contou, e mudou o setup pra que os testes nunca toquem portas padrão. No simulador, o app agora usa a porta dele. Agradeci que tratasse minha máquina como algo pra ter cuidado, não só um ambiente de build.

O crash que não era. No meu iPhone, o Xcode parou com EXC_BAD_ACCESS. Em vez de chutar, o Claude lançou o app instalado no telefone sem o debugger e fez o streaming do console dele: Syncthing iniciou, entrou num relay, detectou NAT, nenhum crash. O runtime do Go lida sozinho com certos faults de memória, mas o debugger agarra eles primeiro. A solução foi um arquivo de init do LLDB pequeno no esquema do Xcode que passa esses sinais pro Go.

“Folder path missing.” Depois de rodar o app de novo pelo Xcode, minha pasta sincronizada entrou em estado de erro. O Claude puxou o arquivo de config do meu telefone e achou a causa: o iOS move o container de dados de um app pra um caminho novo quando o app é reinstalado, e o Syncthing tinha armazenado o caminho absoluto antigo. Os arquivos todos ainda estavam lá; só o ponteiro ficou obsoleto. Caminhos de pasta agora são armazenados como ~/Documents/…, que o Syncthing resolve pro container atual toda vez, e configs quebradas se reparam no startup.

Nenhum desses apareceria numa demo. Todos eles apareceriam pros usuários.

Trabalhando com o Claude

Quero ser transparente sobre como isso foi construído, porque acho que o como é tão interessante quanto o quê.

Trabalhei com o Claude no meu terminal, como Claude Code. Eu dei a direção e tomei as decisões de produto:

  • o que o app deveria ser,
  • o pivot,
  • a licença open source,
  • quais trade-offs eram aceitáveis.

O Claude escreveu a vasta maioria do código: Go, Swift, testes, build scripts, a landing page, e até o ícone do app, desenhado com CoreGraphics porque os SF Symbols da Apple não podem ser usados em ícones.

O que fez funcionar não foi velocidade, embora a velocidade tenha sido notável. Foi verificação:

  • Ele conferiu a fonte real. Formatos de endpoint vieram dos arquivos de documentação do Syncthing, e detalhes de embedding vieram do próprio código Go do Syncthing, não de memória.
  • Cada mudança foi testada. A suite agora tem 72 testes Swift mais testes Go.
  • O teste principal é um sync de verdade. O nó embutido, rodando dentro do simulador iOS, pareia com um processo Syncthing separado, aceita uma pasta compartilhada através do fluxo normal de pending request, e sincroniza arquivos nos dois sentidos.
  • Ele olhou pro que construiu. Tirou screenshots do app e do site, achou os defeitos sozinho (uma imagem esticada, um campo desalinhado, texto saindo da página em celulares) e corrigiu.
  • Ele perguntou antes de agir nas coisas que importavam: o pivot, a licença, tornar o repositório público.

Ele também me disse coisas que eu não tinha pedido pra ouvir: que minha primeira especificação derrotava o próprio propósito, que open source significa escolher uma licença (não só um repo público), e que uma parte da ponte depende de internals do Syncthing que podem mudar.

Open source, e devolvendo

O SuperSynch é lançado sob a Mozilla Public License 2.0, a mesma licença do Syncthing. Syncthing e todas as suas dependências Go estão incluídas sem modificação. Antes de escolher a licença, auditamos elas: MIT, BSD, Apache-2.0 e MPL-2.0, nada incompatível. Estão todas listadas nas third-party notices do repositório.

“Syncthing” é marca registrada da Syncthing Foundation. O SuperSynch é um projeto independente, não afiliado nem endossado pela Foundation. Escrevi pra Foundation contando sobre o projeto, perguntando se dá pra listar entre as contribuições da comunidade, e oferecendo ajuda no único lugar em que acho que o ecossistema poderia melhorar: uma API de embedding mais estável. Os dois workarounds acima, iniciar o summary service e alcançar o modelo, são exatamente o tipo de coisa que uma mudança pequena upstream poderia tornar desnecessária pra todo app Syncthing mobile ou embutido, não só o meu.

O que vem a seguir

  • Arquivos sob demanda, baixando arquivos individuais através da block API do Syncthing, e uma File Provider extension decente.
  • Pedir a entitlement de multicast pra que a descoberta local funcione no iOS.
  • Mais tempo em dispositivos reais e redes reais: timing de background, bateria e troca de Wi-Fi.
  • Eventualmente, TestFlight e App Store.

Se você usa Syncthing e um iPhone ou iPad, adoraria que experimentasse, quebrasse e me contasse o que achou. Issues e pull requests são bem-vindos no GitHub.

E se você ainda não usa Syncthing: comece aqui. Ele roda em quase tudo. Agora, finalmente, isso inclui o telefone no meu bolso.


O SuperSynch é open source sob a MPL-2.0. Syncthing é marca registrada da Syncthing Foundation; este projeto não é afiliado nem endossado por ela.