2 pontos por GN⁺ 2025-04-11 | 1 comentários | Compartilhar no WhatsApp
  • Com viagens frequentes e a necessidade de gerenciar relações em vários fusos horários ao mesmo tempo, surgiu a necessidade de um sistema de calendário próprio para reduzir a dependência de Big Tech e controlar diretamente os dados de agenda
  • A nova arquitetura coloca o servidor CalDAV Baïkal no centro, reunindo Airtrail, e-mail IMAP e o ICS da escola de idiomas em CalDAV, e depois publicando novamente como arquivos .ics para compartilhamento
  • O método de escrever compromissos em YAML e convertê-los em ICS via CI/CD era suficiente para um protótipo, mas os erros manuais e o custo de manutenção cresceram, levando à substituição da abordagem com arquivos estáticos
  • Um script em Python coleta e classifica eventos a cada 15 minutos, cria ICS separados para família e trabalho, e marca alguns eventos como PRIVATE no calendário de trabalho
  • Google Calendar e Google Script são usados apenas para sincronizar o calendário de trabalho; após a integração com a API do Airtrail, basta inserir um voo uma vez para que ele apareça no calendário pessoal em até 15 minutos e no calendário de trabalho em até 1 hora

Por que o gerenciamento de calendário ficou mais complexo

  • Por causa dos deslocamentos frequentes, ficou difícil para família, amigos e colegas saberem o fuso horário atual ou se a pessoa estava embarcando em um voo, e alinhar compromissos em vários fusos exigia constantemente cálculo de horário
  • Voos, trens, bloqueios antes do embarque e tempo de deslocamento até o aeroporto precisavam ser inseridos em vários lugares, aumentando a carga de gestão da agenda
  • Os incômodos do ecossistema de calendários existente apareciam nos padrões, nos apps de frontend e na experiência de uso como um todo
    • Mesmo quando o provedor de e-mail criava eventos de calendário para voos, às vezes omitava conexões ou errava o fuso horário
    • O organizer dos itens gerados nem sempre era o próprio usuário, o que podia dificultar compartilhamento ou edição
  • As condições necessárias iam além de simplesmente armazenar compromissos e incluíam compartilhamento e controle
    • Os eventos precisavam aparecer como blocker no calendário de trabalho
    • A esposa precisava poder assinar o calendário
    • Cada evento deveria ser inserido no máximo uma vez
    • Deveria ser possível editar em vários dispositivos
    • Os dados precisavam ficar sob controle direto
    • Compartilhar o calendário de trabalho com a esposa não era uma opção
  • Entre os requisitos adicionais estavam importar anexos .ics recebidos por e-mail, importar via HTTP o ICS do calendário da escola de idiomas, integrar com o rastreador de voos self-hosted Airtrail, diferenciação por cores, flag de privacidade para o calendário de trabalho, atualizações frequentes e liberdade para usar qualquer frontend

Onde a abordagem com YAML e ICS chegou ao limite

  • As formas tradicionais de compartilhamento geralmente só ofereciam toda a funcionalidade dentro de uma plataforma comum ou de contas no mesmo ecossistema
    • Ecossistema Gmail ou Outlook.com
    • Compartilhamento entre contas do mesmo ambiente, como Exchange
  • Havia duas alternativas mais comuns
    • Publicar dados iCal via HTTP como calendário somente leitura
    • Enviar arquivos iCal .ics por e-mail
  • A versão inicial publicava arquivos .ics em URLs públicas do site, difíceis de adivinhar, criando várias URLs para diferentes finalidades
  • Os dados dos eventos eram escritos em YAML e reserializados em arquivos ICS por um pequeno script dentro do pipeline de CI/CD
  • Isso bastava para um protótipo, mas, conforme o uso cresceu, os erros na escrita manual em YAML se tornaram frequentes e algo que deveria exigir pouco esforço passou a ser trabalhoso

A arquitetura baseada em CalDAV e Baïkal

  • A nova estrutura abandona os arquivos estáticos e migra para um sistema hospedado baseado em CalDAV
  • CalDAV é uma extensão da especificação de autoria distribuída WebDAV e fornece os recursos necessários para aplicações de calendário
  • Com um servidor CalDAV, é possível visualizar e gerenciar eventos em vários dispositivos, como notebook e celular, usando o app frontend de sua escolha
  • Como não há muitos servidores CalDAV facilmente assináveis sem autenticação, foi necessário um script para consultar o servidor periodicamente, extrair os eventos e publicá-los como arquivos iCal no site
  • O fluxo completo é o seguinte
    • Consultar as fontes de dados em busca de eventos
    • Publicar os eventos em CalDAV de forma programática
    • Buscar todos os eventos do CalDAV e gerar arquivos .ics
    • Servir os arquivos .ics via HTTP

Instalação do Baïkal e conexão dos clientes

  • A ferramenta escolhida foi Baïkal, um servidor CalDAV/CardDAV leve e self-hosted para gerenciar calendários e contatos
  • A imagem ckulka/baikal:0.9.5 foi executada com Docker Compose, definindo portas e volumes locais, e iniciada com docker compose up -d
  • O Baïkal também pode usar MySQL, mas funciona com SQLite, que é mais simples de administrar
  • Para publicação na web, foi usado um reverse proxy nginx
    • proxy_pass aponta para a porta local do Baïkal
    • /.well-known/caldav é redirecionado com 301 para dav.php
    • Esse redirecionamento é necessário ao adicionar o serviço no app Calendário do iPhone ou do Mac
  • O TLS foi configurado com Let’s Encrypt e certbot, e o certbot atualiza automaticamente os arquivos de configuração do nginx
  • Depois de acessar o domínio no navegador e criar a conta de administrador, foram criados o usuário e os calendários
  • A conexão foi feita com os apps Calendário padrão do macOS e do iOS, usando manual settings nas configurações de macOS/iOS, e não o modo automático ou advanced

Classificação dos eventos e escopo de divulgação do calendário de trabalho

  • A especificação iCalendar inclui a propriedade opcional CATEGORIES no componente EVENT, mas as ferramentas usadas, como macOS Calendar, iOS Calendar e Google Calendar, não implementam esse campo
  • Como a classificação por texto livre dificulta manter consistência, os tipos de evento foram modelados com enums em Python
    • Ex.: TerminType, CultureType, SocialType, AwayType, TransportType
    • Incluem valores como MEETUP, CONFERENCE, CLASS, APPOINTMENT, MEETING, EXAM
  • Os enums foram divididos em vários grupos para organizar conceitualmente os tipos de evento
  • O sistema de classificação também incorpora a lógica que define o que pode ser divulgado no calendário de trabalho
    • Valores de enum com dois dígitos são tratados por padrão como private no calendário de trabalho
    • Eventos como consultas médicas são serializados como privados em um arquivo separado do calendário de trabalho
  • Essa classificação melhora a capacidade de busca e a identificação visual dos eventos, além de facilitar a visualização rápida em frontends que suportam diferenciação por cores

Script de sincronização em Python e publicação de ICS

  • Para automação, o script em Python foi ligado a um cron job
  • O script reúne várias entradas, envia tudo ao Baïkal e depois exporta novamente em ICS compartilháveis
    • Importa eventos de e-mails via IMAP
    • Extrai eventos de voos pela API do Airtrail
    • Importa o arquivo ICS hospedado pela escola de idiomas
    • Faz push dos eventos coletados para o Baïkal
    • Busca todos os eventos do Baïkal e os reserializa em um ou mais arquivos ICS compartilháveis
  • A integração com IMAP importa automaticamente convites de calendário recebidos por e-mail e os adiciona ao calendário, oferecendo algo semelhante ao Google Calendar
  • O script de entrada do cron cria separadamente um Calendar para a família e outro para o trabalho, busca os eventos no Baïkal e os distribui por regras
    • Adiciona os eventos ao ICS da família
    • Só adiciona ao ICS de trabalho quando o endereço de e-mail profissional não aparece no resultado serializado do evento
    • Define a classificação como PUBLIC ou PRIVATE conforme o resultado de is_work_public
  • O script de integração com o Baïkal usa requisições PROPFIND e autenticação HTTP Digest para buscar eventos remotos
  • Se a descrição do evento contém o padrão CATEGORIES:, o script extrai a categoria do campo de descrição para event.categories e remove essa string da descrição
  • Os arquivos ICS de compartilhamento são servidos pelo nginx em URLs difíceis de adivinhar, geradas a partir de hashes aleatórios com salt
  • Esse cron job é executado a cada 15 minutos

Sincronização com o Google Calendar do trabalho

  • Além de visualizar os eventos no calendário pessoal, era necessário que deslocamentos e voos aparecessem como bloqueios no calendário de trabalho para que colegas pudessem entender a disponibilidade
  • A cópia para o calendário de trabalho usa o Google Script Engine
  • O script base é uma versão modificada do GAS-ICS-Sync
  • A versão modificada lê a propriedade CATEGORIES do calendário e aplica codificação por cores no calendário de trabalho
  • O Google Script é executado em intervalos de 30 minutos
  • O uso do Google Calendar entra em tensão com o objetivo de reduzir dependência de Big Tech, mas é tratado como uma exigência do ambiente de trabalho, e não como dependência para uso pessoal

Resultados operacionais e custo

  • O sistema está em uso há cerca de 6 meses, com pequenos ajustes e adições ao longo do tempo
  • A mudança mais recente foi a integração com a API do Airtrail
    • Ao reservar um voo, os dados são inseridos no rastreador de voos
    • Em até 15 minutos, o evento aparece no calendário pessoal
    • Em até 1 hora, ele é copiado automaticamente para o calendário de trabalho
  • Em um cenário de mobilidade complexa, o tempo gasto com gestão da agenda caiu muito, resultando em melhora na qualidade de vida
  • O custo total pode ser mantido no mínimo, e o sistema pode rodar facilmente até em um NAS doméstico
  • Atualmente, os dados ficam na Suíça e há uma assinatura de cerca de US$ 100 por mês em tempo de servidor para rodar o site e as integrações
  • Esse custo representa uma configuração um pouco excessiva e deve ser otimizado com o tempo
  • A facilidade de montar tudo rapidamente no host Docker da instância de VM justificou o custo adicional, e a simplificação da gestão da agenda já economiza um tempo equivalente a esses US$ 100 mensais

1 comentários

 
GN⁺ 2025-04-11
Opiniões no Hacker News
  • Acho que vai haver resistência, mas considero que CalDAV não é muito bom
    Se fosse realmente fácil e excelente, haveria muito mais soluções CalDAV self-hosted e a usabilidade seria melhor; o Radicale também não me impressionou
    Como, na prática, eu só precisava fornecer um único arquivo de calendário, tentei implementar meu próprio servidor CalDAV, mas até expor um único arquivo era tão pouco intuitivo e complexo que desisti
    Entendo a necessidade se você precisa compartilhar calendários com outras pessoas usando apps com suporte a CalDAV, mas, para hospedar um calendário de uso individual, parece quase perda de tempo
    Em vez disso, no início eu colocava um arquivo iCal atualizado dinamicamente em um bucket S3 e sincronizava com o ICSx5 no Android. Só HTTPS bastava, sem CalDAV
    Ainda assim, continuam faltando bons apps de calendário livres e open source para Android e, como o ICSx5 não funciona bem no GrapheneOS, hoje uso o Proton Calendar, que oferece suporte a link direto para arquivo iCal, de uma forma quase igual
    A abordagem de usar o formato iCalendar é boa, mas o grau de suporte aos recursos do iCalendar varia muito entre softwares de calendário, então, no fim, acabo colocando a maior parte dos metadados no campo de descrição em vez de usar propriedades específicas
    No meu caso de uso, é um software que busca dados de vários sites, como Eventbrite, Meetup, empresas de pub quiz e calendários públicos de eventos, filtra apenas os eventos sociais que me interessam e combina tudo em um único calendário que posso ver no app de calendário que uso normalmente

    • Trabalhei no cliente CalDAV incluído em todos os produtos da Apple e concordo que o protocolo não é bom
      Além disso, sofremos bastante para lidar com os servidores CalDAV fora de especificação do Exchange e do Google, e os usuários colocavam toda a culpa em nós
      Agora que trabalho no Google, vejo que os apps do gCal usam um protocolo completamente diferente
    • O problema central do CalDAV e de sua base, o WebDAV, é parecido com a doença crônica vista na maioria dos padrões aprovados pelo W3
      Em vez de ser um formato leve, ele tentou fazer coisas demais de uma vez, foi superprojetado e acabou virando um protocolo como um novelo de ideias diferentes emaranhadas
      Por isso, muitos serviços saem do escopo que realmente desejam e não tentam cumprir completamente a especificação inteira
      As especificações de formatos de dados do W3 parecem sempre acabar assim; nas especificações de protocolos de baixo nível isso parece acontecer relativamente menos
    • O livro Dreaming in Code aborda em detalhes como CalDAV e WebDAV surgiram, acompanhando a equipe que criava o programa de calendário Chandler
      Faz uns 15 anos que li, mas me lembro de que ele também tratava, em parte, de como protocolos desse tipo se perdem dentro de comitês durante o processo de design
      Só o fato de esse projeto ter começado por volta de 2001 já mostra como o problema de calendários é uma dor de cabeça antiga
      Recomendo se você quiser entender por que ele não é bom; também é uma boa história sobre o choque entre startups e open source, criação de produtos e desenvolvimento de software em geral
      http://www.dreamingincode.com/ ; https://en.m.wikipedia.org/wiki/Dreaming_in_Code
    • Uso o CalDAV e CardDAV do Nextcloud com o DAVx5 e funciona quase perfeitamente
    • Meu provedor CalDAV é o ZOHO
      Para sincronização no Android, uso o DAVX5, um app open source sem root que roda um serviço de sincronização em segundo plano, mas a configuração teve muitos problemas, talvez por diferenças na implementação do CalDAV. Sincronizar do DAVX5 para o calendário do ZOHO também demora bastante
      O Evolution no Ubuntu tem um suporte a CalDAV bastante bom, mas atualiza o calendário inteiro sempre que alterno entre a visualização de e-mail e a de calendário. Como a atualização do calendário do ZOHO demora e os limites de taxa do ZOHO também são rígidos, na prática acabo levando uma espécie de bloqueio leve
      O Thunderbird mais recente tem uma incompatibilidade com CalDAV por esperar outro código HTTP de sucesso. O ZOHO retorna 200, mas o Thunderbird espera outro código. A propósito, o código CalDAV do Thunderbird é escrito em JS
      Esse problema só acontece no segundo calendário e começa a falhar no momento em que o segundo é adicionado. Pelo menos no ZOHO, é bem sofrido
  • Outro servidor CalDAV recomendável é o Radicale
    Ele é escrito em Python e tem uma arquitetura bastante modular, permitindo adicionar plugins de autenticação, autorização etc.
    Os dados são armazenados como arquivos de texto simples, que acompanho e faço backup com git
    Com o tempo, acumulei algumas modificações interessantes. A autenticação é feita pelo pam do sistema host, e rodo um script periódico que cria links simbólicos dos calendários para todos os usuários autorizados, para compartilhamento de calendários. Mas há um problema de explosão combinatória
    Também adicionei listas de controle de acesso com um plugin de autorização que usa propriedades CalDAV customizadas e um plugin web modificado
    Para calendários públicos, dou permissão de leitura ao usuário public via ACL e permito acesso amplo a operações de leitura com um hack no nginx
    Esse calendário público pode ser visto em um calendário web: https://gitlab.nomagic.uk/popi/js_calendar_from_ics
    Acho que preciso escrever um post de blog sobre essa configuração

    • Uso o Radicale há alguns anos. Tirando um problema ocasional no BusyCal, ele roda de forma muito tranquila
  • Era exatamente o artigo de que eu precisava
    No servidor pessoal, uso o calendário SoGO do Mailcow, e para o calendário de trabalho uso o Fastmail, mas eles não combinam bem entre si
    Não encontrei uma forma de integrar direito o calendário do Mailcow com outras ferramentas, então gasto tempo fazendo uma sincronização semimanual
    Há algum tempo queria separar os calendários, mas a única alternativa que eu conhecia era o Nextcloud, que considero um pouco pesado
    O Baïkal parece bom e estou ansioso para experimentar. Com isso, também posso explorar trocar o Mailcow por outro serviço de e-mail self-hosted

  • Recentemente tenho saído da nuvem de forma bem agressiva. Eu tinha centenas de GB de dados, mas, mesmo fazendo a migração aos poucos, levou menos de 2 semanas, e funciona muito melhor do que eu esperava
    O único serviço que deixei nas mãos de terceiros foi o e-mail, porque não quero lidar com registros MX e IMAP/SMTP
    No resto, um Asus PN40 com CPU Celeron dual-core ajudou bastante. A vantagem é que ele é acessível e tem slots para M.2 NVME e unidade SATA, permitindo montar uma configuração de backup segura em um só lugar
    Concordo que CalDAV parece meio suspeito, mas o Nextcloud atendeu à maior parte das minhas necessidades. Mesmo com Redis, algumas operações podem ser lentas, mas ele dá conta de calendário, substituição de Dropbox/Google Drive, documentos, fotos, backups e sincronização automática
    Somando várias soluções open source, dá para resolver também streaming de música e vídeo, monitoramento, notificações, VPN, leitor, ferramentas de administração e todo tipo de utilitário
    Este mini PC é relativamente fraco, mas, como meu ISP é excelente, ele aguenta tudo com bastante folga; daria até para atender mais umas 5 ou 6 pessoas como eu sem problemas

    • Se quiser aumentar a resiliência dos dados, vale procurar pela estratégia de backup 3-2-1. RAID também vale ser considerado
  • É como se o que era antigo estivesse ficando novo de novo
    No começo dos anos 2000, eu hospedava meu calendário com o Mozilla Sunbird por meio do módulo IIS WebDAV em um servidor Win2k pessoal
    Acho que CardDAV ainda não existia, mas posso estar enganado
    No fim acabei importando todos esses dados para o Google Calendar e, voltando bastante no tempo, ainda há coisas como trabalhos da faculdade, o que é bem divertido

    • Por volta de 2004, o Sunbird era quase o que eu queria, mas parecia instável
      Já perdi alguns compromissos porque eles não subiram para o servidor, e eu o usava com CalDAV junto com um servidor DAViCal
      O Sunbird foi descontinuado, e não procurei recentemente se existe algum substituto adequado que rode no Windows
  • Baikal parece interessante
    Anos atrás usei DAViCal para compartilhar calendários entre alguns dispositivos Apple iOS. Quando funcionava, funcionava bem, mas, conforme as versões do iOS mudavam, parecia ficar instável, então acabei desistindo
    Se algum dia eu voltar a investigar isso, pretendo olhar DAViCal e Radicale, e agora Baikal também
    https://www.davical.org/
    https://radicale.org/v3.html

    • Usei Radicale por muito tempo e funciona bem
      Recentemente mudei para o Nextcloud, porque a hospedagem compartilhada cuidava da implantação e havia uma UI web integrada, o que reduziu muito o trabalho de configuração
  • Uso Baikal há alguns anos e ele é bastante sólido. Porém uso sozinho e não tenho todos os requisitos do texto original
    No desktop conecto pelo calendário embutido do Thunderbird e, no celular, uso DAVx5 e Fossify Calendar do F-Droid
    O Fossify Calendar nasceu de um fork do app Calendar do Simple Mobile Tools, mas, apesar do nome, é o app de calendário mais rico em recursos que já vi
    Gosto dele porque sua simplicidade é mostrar todas as opções diretamente, sem me obrigar a vasculhar vários menus e caixas de diálogo

  • No trabalho uso Outlook e, em casa, Google Calendar, mas sincronizar por HTTP é completamente instável
    Parece que ele bagunça os fusos horários o tempo todo; às vezes publica em UTC, às vezes não, então os horários das reuniões sempre ficam errados
    Não entendo por que esses dois grandes provedores não conseguem corrigir uma função tão básica
    Se usar meu próprio sistema resolvesse, eu faria na hora, mas tenho receio de acontecer a mesma coisa: reuniões criadas no Outlook serem importadas com 2 horas de diferença

    • O tratamento de fusos horários nos produtos do Google em geral esteve desesperadoramente quebrado, app por app, por décadas. Mas agora os desenvolvedores de IA logo devem cuidar disso
    • Há pouquíssimo incentivo financeiro para corrigir esse problema. Pelo contrário, ele ajuda no efeito de aprisionamento
      Além disso, muitas empresas de fato proíbem a sincronização entre calendário pessoal e corporativo por causa de problemas de vazamento de dados
  • Quero fazer algo assim também com fotos. Quero sair do Dropbox e do Google há algum tempo, e tenho duas prioridades
    Primeiro, quero criar algo como um recurso de memórias com minhas fotos e enviá-lo para o celular. Seria agrupado por critérios como aniversários, atividades parecidas, eu e X (X sendo cônjuge, família, amigos etc.) e mudanças ao longo do tempo
    Segundo, preciso conseguir armazenar no meu servidor as fotos tiradas nos dispositivos
    Já preparei 3 máquinas TrueNAS com 2 TB de espaço em locais diferentes, e quero construir essa funcionalidade aos poucos

  • Outra alternativa é usar DecSync sem servidor
    Thunderbird ou Evolution sincronizam com o app Android, e calendários, contatos, tarefas e até RSS são replicados para outros peers. Também está disponível no F-Droid
    https://github.com/39aldo39/DecSync

    • Interessante, mas não vejo suporte a iOS e, infelizmente, parece que também não é mais mantido