2 puntos por GN⁺ 2025-04-11 | 1 comentarios | Compartir por WhatsApp
  • A medida que se combinaron los viajes de trabajo frecuentes, los viajes y la gestión de relaciones en varias zonas horarias, surgió la necesidad de un sistema de calendario propio para reducir la dependencia de Big Tech y controlar directamente los datos de agenda
  • La nueva arquitectura pone en el centro un servidor CalDAV Baïkal, reúne Airtrail, correo IMAP e ICS de la escuela de idiomas, los incorpora a CalDAV y luego los vuelve a publicar como archivos .ics para compartir
  • El método de escribir eventos en YAML y convertirlos a ICS en CI/CD fue suficiente para un prototipo, pero los errores manuales y la carga de mantenimiento crecieron, por lo que terminó reemplazándose el enfoque de archivos estáticos
  • Un script de Python recopila y clasifica eventos cada 15 minutos, crea por separado archivos ICS familiares y laborales, y en el calendario de trabajo marca algunos eventos como PRIVATE
  • Google Calendar y Google Script se usan solo para sincronizar el calendario laboral, y tras integrar la API de Airtrail, al ingresar un vuelo una sola vez se refleja en 15 minutos en el calendario personal y en 1 hora en el calendario de trabajo

Por qué se volvió compleja la gestión del calendario

  • Debido a los desplazamientos frecuentes, a familiares, amistades y colegas les resultaba difícil saber la zona horaria actual o si ya había abordado un vuelo, y coordinar compromisos en varias zonas horarias exigía seguir haciendo cálculos de tiempo
  • Vuelos, trenes, bloqueos previos al embarque y tiempos de traslado al aeropuerto tenían que registrarse repetidamente en varios lugares, lo que aumentaba la carga de gestionar la agenda
  • Las incomodidades del ecosistema de calendarios existente abarcaban los estándares, las apps de frontend y la experiencia de usuario en general
    • Aunque un proveedor de correo generara entradas de calendario para vuelos, a veces omitía conexiones o se equivocaba con la zona horaria
    • El organizer de los elementos generados podía no ser el usuario, lo que dificultaba compartirlos o modificarlos
  • Las condiciones necesarias iban más allá de guardar eventos simples e incluían compartir y control
    • Los eventos debían mostrarse como blocker en el calendario laboral
    • La pareja debía poder suscribirse al calendario
    • La entrada de eventos debía hacerse como máximo una sola vez
    • Debía ser posible editarlos desde varios dispositivos
    • Los datos debían estar bajo control directo
    • No se podía usar la opción de compartir el calendario laboral con la pareja
  • Los requisitos adicionales incluían importar adjuntos de correo .ics, importar por HTTP el ICS del calendario de la escuela de idiomas, integrar el rastreador de vuelos autoalojado Airtrail, diferenciación por colores, flag privado para el calendario laboral, actualizaciones frecuentes y el uso de cualquier frontend

Dónde chocó con el límite el enfoque YAML·ICS

  • Las formas existentes de compartir solían ofrecer todas las funciones solo dentro de una plataforma común o un entorno con la misma cuenta
    • Ecosistema de Gmail u Outlook.com
    • Compartición de cuentas dentro del mismo entorno, como Exchange
  • Había dos vías alternativas comunes
    • Publicar datos iCal por HTTP como un calendario de solo lectura
    • Enviar archivos iCal .ics por correo electrónico
  • La versión inicial consistía en subir archivos .ics al sitio web usando una URL pública pero difícil de adivinar, y crear varias URLs según el propósito
  • Los datos de eventos se escribían en YAML y luego se volvían a serializar como archivos ICS mediante un pequeño script en el pipeline de CI/CD
  • Bastaba para un prototipo, pero a medida que creció la escala se hicieron frecuentes los errores al escribir YAML a mano, y una tarea que originalmente debía requerir poco esfuerzo empezó a volverse pesada

Arquitectura cambiada a CalDAV y Baïkal

  • La nueva estructura abandona los archivos estáticos y pasa a un sistema basado en CalDAV alojado
  • CalDAV es una extensión de la especificación de autoría distribuida WebDAV y proporciona las funciones necesarias para aplicaciones de calendario
  • Al tener un servidor CalDAV, se pueden ver y gestionar eventos desde varios dispositivos, como laptop o teléfono, usando la app frontend que se prefiera
  • Como no hay muchos servidores CalDAV fáciles de suscribir sin autenticación, hizo falta un script que consultara periódicamente el servidor, extrajera los eventos y los publicara como archivos iCal en el sitio web
  • El flujo completo es el siguiente
    • Consultar eventos desde las fuentes de datos
    • Publicar eventos de forma programática en CalDAV
    • Obtener todos los eventos desde CalDAV y escribirlos en archivos .ics
    • Servir los archivos .ics por HTTP

Instalación de Baïkal y conexión de clientes

  • La herramienta elegida fue Baïkal, un servidor ligero autoalojado de CalDAV/CardDAV para gestionar calendarios y contactos
  • Se ejecutó la imagen ckulka/baikal:0.9.5 con Docker Compose, se definieron puertos y volúmenes locales, y se inició con docker compose up -d
  • Baïkal también puede usar MySQL, pero funciona con SQLite, y SQLite simplifica más la administración
  • Para exponerlo en la web se usó un reverse proxy con nginx
    • proxy_pass conecta con el puerto local de Baïkal
    • /.well-known/caldav redirige con 301 a dav.php
    • Esta redirección es necesaria al agregarlo en la app de calendario de iPhone o Mac
  • TLS se configuró con Let’s Encrypt y certbot, y certbot actualiza automáticamente el archivo de configuración de nginx
  • Tras entrar al dominio desde el navegador, se creó una cuenta de administrador y luego usuarios y calendarios
  • Se conectó con las apps de calendario predeterminadas de macOS e iOS, y en la configuración de macOS/iOS se usaron manual settings en lugar de automático o advanced

Clasificación de eventos y alcance de publicación del calendario laboral

  • La especificación iCalendar incluye la propiedad opcional CATEGORIES en el componente EVENT, pero las herramientas usadas, como macOS Calendar, iOS Calendar y Google Calendar, no implementan ese campo
  • Como la clasificación con texto libre dificulta mantener consistencia, se modeló el tipo de evento con enums de Python
    • Ejemplos: TerminType, CultureType, SocialType, AwayType, TransportType
    • Incluyen valores como MEETUP, CONFERENCE, CLASS, APPOINTMENT, MEETING, EXAM
  • La razón para dividir los enums en varios grupos fue ordenar conceptualmente los tipos de evento
  • En este sistema de clasificación también entra la lógica que define qué tanto se publica al calendario laboral
    • Los valores enum de dos dígitos se tratan por defecto como private en el calendario laboral
    • Eventos como consultas médicas se serializan como privados en un archivo separado del calendario laboral
  • Esta clasificación se usa para mejorar la capacidad de encontrar eventos y su identificación visual, y para hacerlos más fáciles de distinguir de un vistazo en frontends con soporte de colores

Script de sincronización en Python y publicación de ICS

  • Para automatizarlo, se conectó un script de Python a un cron job
  • El script reúne varias entradas, las inserta en Baïkal y luego las exporta otra vez como ICS compartibles
    • Importar eventos desde correo por IMAP
    • Extraer eventos de vuelos desde la API de Airtrail
    • Importar el archivo ICS alojado por la escuela de idiomas
    • Hacer push de los eventos recopilados a Baïkal
    • Obtener todos los eventos de Baïkal y volver a serializarlos en uno o más archivos ICS compartibles
  • La integración con IMAP ofrece una función parecida a Google Calendar, ya que importa automáticamente invitaciones de calendario recibidas por correo y las agrega al calendario
  • El script de entrada del cron crea por separado Calendar familiar y laboral, obtiene los eventos de Baïkal y los bifurca
    • Agrega eventos al ICS familiar
    • Los agrega al ICS laboral solo cuando la dirección de correo laboral no aparece en el resultado serializado del evento
    • Según el resultado de is_work_public, establece la clasificación como PUBLIC o PRIVATE
  • El script de integración con Baïkal usa solicitudes PROPFIND y autenticación HTTP Digest para obtener eventos remotos
  • Si en la descripción del evento aparece el patrón CATEGORIES:, extrae la categoría del campo de descripción y la coloca en event.categories, eliminando esa cadena de la descripción
  • Los archivos ICS compartibles se sirven desde nginx con URLs difíciles de adivinar, generadas a partir de cadenas hash aleatorias con salt
  • Este cron job se ejecuta cada 15 minutos

Sincronización con Google Calendar del trabajo

  • Además de ver eventos en el calendario personal, era necesario que en el calendario laboral los traslados y vuelos aparecieran como tiempos bloqueados y que los colegas pudieran saber el estado
  • Para copiar al calendario laboral se utilizó Google Script Engine
  • El script base es una versión modificada de GAS-ICS-Sync
  • La versión modificada lee la propiedad CATEGORIES del calendario y aplica codificación por colores en el calendario laboral
  • Google Script se ejecuta cada 30 minutos
  • Aunque usar Google Calendar contradice el objetivo de reducir la dependencia de Big Tech, se distingue como una elección del entorno de trabajo y no como una dependencia para uso personal

Resultados operativos y costos

  • Este sistema se ha usado durante unos 6 meses, con pequeños ajustes y agregados
  • El cambio más reciente fue la integración de la API de Airtrail
    • Al reservar un vuelo, los datos se ingresan en el rastreador de vuelos
    • Se agregan al calendario personal en menos de 15 minutos
    • Se copian automáticamente al calendario laboral en menos de 1 hora
  • Frente a requerimientos de movilidad complejos, el tiempo dedicado a gestionar la agenda se redujo mucho, lo que se tradujo en una mejora en la calidad de vida
  • El costo total puede mantenerse al mínimo y también puede operarse fácilmente en un NAS doméstico
  • Actualmente, para ejecutar el sitio web y las integraciones, se mantienen los datos en Suiza y se paga aproximadamente 100 dólares mensuales en tiempo de servidor
  • Ese costo corresponde a una configuración algo excesiva, con la idea de optimizarla con el tiempo
  • Haber podido montarlo rápidamente en el host Docker de una instancia VM justificó asumir ese costo adicional, y la simplificación de la gestión del calendario ya está ahorrando un tiempo equivalente a esos 100 dólares mensuales

1 comentarios

 
GN⁺ 2025-04-11
Opiniones de Hacker News
  • Creo que habrá rechazo, pero para mí CalDAV no es muy bueno
    Si de verdad fuera fácil y excelente, habría muchas más soluciones CalDAV autohospedadas y la usabilidad sería mejor; Radicale tampoco me impresionó
    En realidad, como prácticamente solo necesitaba ofrecerme un archivo de calendario, intenté implementar mi propio servidor CalDAV, pero hasta exponer un solo archivo resultó tan poco intuitivo y complejo que desistí
    Entiendo la necesidad si hay que compartir un calendario con otra persona mediante una app compatible con CalDAV, pero para alojar un calendario de uso personal me parece casi una pérdida de tiempo
    En su lugar, al principio subía un archivo iCal que se actualizaba dinámicamente a un bucket de S3 y lo sincronizaba con ICSx5 en Android. Con HTTPS bastaba, sin CalDAV
    Sin embargo, todavía faltan buenas apps de calendario libres y de código abierto para Android, y como ICSx5 no me funciona bien en GrapheneOS, ahora uso Proton Calendar casi de la misma forma, porque admite enlaces directos a archivos iCal
    El enfoque de usar el formato iCalendar está bien, pero el alcance del soporte de funciones de iCalendar varía mucho entre programas de calendario, así que al final termino metiendo la mayor parte de los metadatos en el campo de descripción en vez de usar propiedades específicas
    Mi caso de uso es un software que trae datos de varios sitios, como Eventbrite, Meetup, empresas de trivias en pubs y calendarios públicos de eventos, filtra solo los eventos sociales que me interesan y los combina en un único calendario que puedo ver en la app de calendario que uso normalmente

    • Trabajé en el cliente CalDAV incluido en todos los productos de Apple, y coincido en que el protocolo no es bueno
      Además, sufrimos muchísimo para manejar los servidores CalDAV de Exchange y Google que no cumplían la especificación, y los usuarios nos culpaban a nosotros por todos esos problemas
      Ahora que trabajo en Google, veo que las apps de gCal usan un protocolo completamente distinto
    • El problema central de CalDAV y de WebDAV, en el que se basa, es parecido a la enfermedad crónica que se ve en la mayoría de los estándares aprobados por el W3C
      En vez de ser un formato liviano, intentó hacer demasiadas cosas de una sola vez, quedó sobrediseñado y terminó siendo un protocolo como una bola de pelos de ideas distintas enredadas
      Por eso muchos servicios no intentan cumplir por completo toda la especificación, porque se sale del alcance de lo que realmente quieren
      Me da la impresión de que las especificaciones de formatos de datos del W3C siempre terminan así, aunque en las especificaciones de protocolos de más bajo nivel parece ocurrir relativamente menos
    • El libro Dreaming in Code trata en detalle cómo surgieron CalDAV y WebDAV a través del equipo que estaba creando el programa de calendario Chandler
      Lo leí hace unos 15 años, pero recuerdo que también abordaba en parte cómo protocolos como estos se pierden dentro de comités durante el proceso de diseño
      El solo hecho de que este proyecto haya empezado alrededor de 2001 muestra lo antiguo que es el dolor de cabeza de los calendarios
      Lo recomiendo si quieres entender por qué no es muy bueno, y también es una buena historia sobre el choque entre startups y código abierto, la creación de productos y el desarrollo de software en general
      http://www.dreamingincode.com/ ; https://en.m.wikipedia.org/wiki/Dreaming_in_Code
    • Uso CalDAV y CardDAV de Nextcloud junto con DAVx5 y funciona casi perfecto
    • Mi proveedor de CalDAV es ZOHO
      Para la sincronización en Android uso DAVX5, una app open source sin root que ejecuta un servicio de sincronización en segundo plano, pero la configuración tuvo muchos problemas y quizá se deba a diferencias en la implementación de CalDAV. Sincronizar desde DAVX5 hacia el calendario de ZOHO también tarda bastante
      Evolution en Ubuntu tiene un soporte de CalDAV bastante bueno, pero cada vez que cambio entre la vista de correo y la vista de calendario vuelve a actualizar todo el calendario. La actualización del calendario de ZOHO tarda mucho y los límites de velocidad de ZOHO también son estrictos, así que en la práctica termino recibiendo una especie de bloqueo suave
      La versión reciente de Thunderbird tiene una incompatibilidad con CalDAV porque espera otro código HTTP de éxito. ZOHO devuelve 200, pero Thunderbird espera otro código. Como referencia, el código CalDAV de Thunderbird está escrito en JS
      Este problema solo ocurre con el segundo calendario, y en cuanto agregas el segundo empieza a fallar. Al menos con ZOHO es bastante doloroso
  • Otro servidor CalDAV recomendable es Radicale
    Está escrito en Python y tiene un diseño bastante modular, así que se le pueden agregar plugins para autenticación, autorización y demás
    Los datos se guardan como archivos de texto plano, y los rastreo y respaldo con git
    Con el tiempo acumulé algunas modificaciones interesantes. La autenticación se maneja con pam del sistema anfitrión, y para compartir calendarios ejecuto un script periódico que crea enlaces simbólicos del calendario para todos los usuarios autorizados. Eso sí, tiene un problema de explosión combinatoria
    También agregué listas de control de acceso con un plugin de permisos que usa propiedades CalDAV personalizadas y un plugin web modificado
    A los calendarios públicos les doy permiso de lectura al usuario public mediante ACL, y con un hack en nginx permito acceso amplio para operaciones de lectura
    Estos calendarios públicos se pueden ver en un calendario web: https://gitlab.nomagic.uk/popi/js_calendar_from_ics
    Creo que debería escribir un post en el blog sobre esta configuración

    • Llevo varios años usando Radicale. Salvo algún problema ocasional con BusyCal, funciona de forma muy fluida
  • Este artículo era justo lo que necesitaba
    En mi servidor personal uso el calendario SoGO de Mailcow, y para el calendario del trabajo uso Fastmail, pero no se llevan bien entre sí
    No he encontrado una forma de integrar correctamente el calendario de Mailcow con otras herramientas, así que paso tiempo sincronizándolo de manera semimanual
    Hace tiempo quería separar los calendarios, pero la única alternativa que conocía era Nextcloud, y me parece algo pesado
    Baïkal se ve bien y tengo ganas de probarlo pronto. Con esto también podría explorar cambiar Mailcow por otro servicio de correo autohospedado

  • Últimamente estoy saliendo de la nube de forma bastante agresiva. Tenía cientos de GB de datos, pero aunque hice la migración de a poco, tardé menos de 2 semanas, y funciona mucho mejor de lo que esperaba
    El único servicio que dejé en manos de terceros es el correo electrónico, porque no quiero lidiar con registros MX e IMAP/SMTP
    Para todo lo demás, me ayudó mucho una Asus PN40 con CPU Celeron de doble núcleo. Su ventaja es que es fácil de acceder y tiene ranuras para M.2 NVME y unidades SATA, lo que permite armar una configuración de backup segura en un solo lugar
    Coincido en que CalDAV es algo sospechoso, pero Nextcloud cubrió la mayoría de mis necesidades. Incluso con Redis, algunas operaciones pueden ser lentas, pero maneja bien calendario, reemplazo de Dropbox/Google Drive, documentos, fotos, backups y sincronización automática
    Si a eso se le suman varias soluciones open source, también queda resuelto todo lo de streaming de música y video, monitoreo, notificaciones, VPN, lector, herramientas de administración y todo tipo de utilidades
    Aunque esta mini PC no es muy potente, mi ISP es excelente, así que puede con todo dejando bastante margen, y hasta podría sumar sin problema a 5 o 6 personas más como yo

    • Si quieres mejorar la resiliencia de tus datos, conviene buscar la estrategia de backup 3-2-1. RAID también vale la pena revisarlo
  • Se siente como si lo viejo volviera a ser nuevo
    A principios de los 2000 alojaba mi calendario con Mozilla Sunbird en un servidor Win2k personal mediante el módulo IIS WebDAV
    Creo que CardDAV todavía no existía, aunque podría estar equivocado
    Al final importé todos esos datos a Google Calendar, y es bastante divertido porque, si me voy muy atrás, todavía quedan cosas como tareas de la universidad

    • Hacia 2004, Sunbird era casi lo que quería, pero parecía inestable
      Perdí algunas citas porque no se subieron al servidor, y lo usaba con CalDAV junto con un servidor DAViCal
      Sunbird fue discontinuado, y últimamente no he buscado si existe un reemplazo adecuado que corra en Windows
  • Baikal se ve interesante
    Antes usaba DAViCal para compartir calendarios entre varios dispositivos Apple iOS. Cuando funcionaba, funcionaba bien, pero parecía volverse inestable con los cambios de versión de iOS, así que terminé abandonándolo
    Si algún día vuelvo a revisar esto, pienso mirar DAViCal y Radicale, y ahora también Baikal
    https://www.davical.org/
    https://radicale.org/v3.html

    • Usé Radicale durante mucho tiempo y funciona bien
      Hace poco me pasé a Nextcloud porque en el hosting compartido se encargan del despliegue y tiene una UI web integrada, así que la carga de configuración era mucho menor
  • Uso Baikal desde hace varios años y es bastante sólido. Eso sí, lo uso solo y no tengo todos los requisitos del texto original
    En el escritorio me conecto con el calendario integrado de Thunderbird, y en el teléfono uso DAVx5 de F-Droid y Fossify Calendar
    Fossify Calendar nació como un fork de la app Calendar de Simple Mobile Tools, pero pese al nombre es la app de calendario con más funciones que he visto hasta ahora
    Me gusta porque su simpleza consiste en mostrar todas las opciones directamente, sin obligarte a meterte por varios menús y diálogos

  • En el trabajo uso Outlook y en casa Google Calendar, pero sincronizar por HTTP es totalmente inestable
    Parece que arruina constantemente las zonas horarias, y a veces publica en UTC y a veces no, así que los horarios de las reuniones siempre quedan mal
    No entiendo por qué estos dos grandes proveedores no pueden arreglar una función tan básica
    Si usar mi propio sistema resolviera esto, lo haría de inmediato, pero me preocupa que pase lo mismo: que una reunión creada en Outlook se importe con 2 horas de diferencia

    • El manejo de zonas horarias en los productos de Google en general lleva décadas desastrosamente roto, aplicación por aplicación. Igual, seguro que ahora los desarrolladores de IA lo arreglan pronto
    • Hay muy pocos incentivos económicos para corregir este problema. De hecho, ayuda al efecto de encierro
      Además, muchas empresas directamente prohíben sincronizar calendarios personales y laborales por problemas de filtración de datos
  • Me gustaría hacer algo así también con las fotos. Hace tiempo que quiero alejarme de Dropbox y Google, y mis prioridades son dos
    Primero, quiero crear algo tipo recuerdos a partir de mis fotos y enviarlo al teléfono. Sería una forma de agruparlas por aniversarios, actividades parecidas, yo y X (X puede ser mi cónyuge, familia, amigos, etc.), cambios a lo largo del tiempo, y criterios similares
    Segundo, necesito poder guardar en mi servidor las fotos tomadas desde los dispositivos
    Ya tengo 3 máquinas TrueNAS con 2 TB de espacio en ubicaciones distintas, y quiero ir construyendo esta función poco a poco

  • Otra alternativa es usar DecSync sin servidor
    Thunderbird o Evolution se sincronizan con apps de Android, y calendarios, contactos, tareas y RSS se replican a otros pares. También está disponible en F-Droid
    https://github.com/39aldo39/DecSync

    • Suena interesante, pero no veo soporte para iOS y, lamentablemente, parece que tampoco tiene mantenimiento