<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wynne Pirini</title>
    <description>The latest articles on DEV Community by Wynne Pirini (@shockalotti).</description>
    <link>https://dev.to/shockalotti</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4130402%2Ff4d182b0-0e62-4ac2-96c5-23a878d5cb83.jpg</url>
      <title>DEV Community: Wynne Pirini</title>
      <link>https://dev.to/shockalotti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shockalotti"/>
    <language>en</language>
    <item>
      <title>Calnode v0.10.1: the release our deployers wrote</title>
      <dc:creator>Wynne Pirini</dc:creator>
      <pubDate>Tue, 29 Sep 2026 23:09:22 +0000</pubDate>
      <link>https://dev.to/shockalotti/calnode-v0101-the-release-our-deployers-wrote-5fij</link>
      <guid>https://dev.to/shockalotti/calnode-v0101-the-release-our-deployers-wrote-5fij</guid>
      <description>&lt;p&gt;On Sunday evening, a developer in Rotterdam I had never spoken to opened ten pull requests in sixteen minutes and signed the contributor agreement on all of them. Then he went quiet, presumably back to his weekend.&lt;/p&gt;

&lt;p&gt;Those ten PRs became the core of v0.10.0. Two days after that release shipped, v0.10.1 landed: a security sweep plus the one-click module our hosting crowd kept asking for. I wrote less of either than our users did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What v0.10.0 was
&lt;/h2&gt;

&lt;p&gt;Marijn Bent (WordProof) shipped a coordinated batch across the whole booking surface: destination-provider preference, availability reporting, shared-calendar booking, a mailer relay, calendar rechecking before every booking, ownership transfer without breaking the booking URL, a per-user accent color, an optional phone call for online events, and grouping available times by time of day. Plus the one that tells you where he's from: Europe/Amsterdam added to the timezone picker.&lt;/p&gt;

&lt;p&gt;I checked his account while merging. Eleven years old, 78 public repos. This was not a drive-by. It was someone running the software, hitting every rough edge in one sitting, and fixing all of them before dinner.&lt;/p&gt;

&lt;p&gt;He is the second deployer of this kind. Minos Chatzidakis spent months reporting bugs with full reproductions and root-cause diagnoses, then started shipping the fixes in his own fork before I merged them upstream. When I asked permission to quote his work, he said: "You can do as you see fit with my issues." That sentence is the highest compliment this project has received.&lt;/p&gt;

&lt;h2&gt;
  
  
  What v0.10.1 added
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A NethServer 8 module.&lt;/strong&gt; Calnode now installs as a one-click NS8 app: host-based Traefik route with cluster TLS, SQLite on a persistent volume, an encryption key that survives reconfigures, and release tags that pin module and app to the same version. Honest caveat, printed in the release notes themselves: it is preview-grade. The install and configure paths are covered by robot tests, but no live node has run one end to end yet.&lt;/p&gt;

&lt;p&gt;If you follow self-hosted schedulers, you know why this matters: the Rust newcomer in this space built its distribution story on exactly this surface. Now the Go binary has one too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The security sweep
&lt;/h2&gt;

&lt;p&gt;Five fixes, most of them the kind nobody notices until the post-mortem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public booking lookup is now rate-limited.&lt;/strong&gt; &lt;code&gt;GET /v1/bookings/{id}&lt;/code&gt; needs no auth by design (it carries no PII), but it was the one public route outside any rate limiter - an enumeration free-for-all. It shares the manage-token bucket now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CalDAV connect failures no longer distinguish error classes.&lt;/strong&gt; Refused vs timeout vs TLS vs auth failures were surfaced verbatim to the member form - a usable LAN-scan oracle. One generic message to the user, detail logged server-side. Timing side-channels are accepted as residual, and the release notes say so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CalDAV no longer sends Basic credentials on cross-origin redirects.&lt;/strong&gt; A redirect to another origin now drops the Authorization header instead of forwarding your app password to a server you never configured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A short &lt;code&gt;GOOGLE_CLIENT_ID&lt;/code&gt; no longer panics at boot.&lt;/strong&gt; The startup log sliced the first 20 characters unconditionally; an unset or short value crashed the process instead of logging the usual "not configured" warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video rooms explain host takeover.&lt;/strong&gt; Sharing the host link let anyone take over as host, and the demoted side just lost its controls with no explanation. Host-link holders are now warned before joining not to share it, and a demotion names who took over, with a reclaim hint for owners.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The credential-forwarding one is the sort of thing most projects fix silently in a patch release. It is in the changelog because the changelog is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;100 stars. 17 forks. One static Go binary, embedded SQLite, no Redis, no Postgres, Apache-2.0. Four digits on stars is a rounding error in this field, but every one of them is inside the window that matters to me: people running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than its size
&lt;/h2&gt;

&lt;p&gt;Cal.eu closes on November 1. From today that is 32 days, and every EU team that picked Cal.eu for data residency needs an answer, not a debate. I wrote down the practical one three weeks ago: export now, stand up one binary, rebuild your event types, repoint webhooks, cut over one calendar at a time. That playbook is here: &lt;a href="https://dev.to/shockalotti/caleu-is-closing-on-nov-1-here-is-your-migration-playbook-26la"&gt;Cal.eu is closing on Nov 1. Here is your migration playbook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you follow it, v0.10.1 is the version to install, and the NS8 module is there if your hosting crowd thinks in NethServer.&lt;/p&gt;

&lt;p&gt;Calnode lives at &lt;a href="https://calnode.com/" rel="noopener noreferrer"&gt;calnode.com&lt;/a&gt; (&lt;a href="https://github.com/Calnode/calnode" rel="noopener noreferrer"&gt;github.com/Calnode/calnode&lt;/a&gt;). If you deploy it and something breaks, file the issue the way Minos does. And as this post went together, a third contributor named jeroenrinzema opened two invitation-feature PRs. The pattern is holding. If you are Marijn, keep your weekends.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>go</category>
      <category>self</category>
    </item>
    <item>
      <title>Cal.eu is closing on Nov 1. Here is your migration playbook</title>
      <dc:creator>Wynne Pirini</dc:creator>
      <pubDate>Thu, 17 Sep 2026 19:29:58 +0000</pubDate>
      <link>https://dev.to/shockalotti/caleu-is-closing-on-nov-1-here-is-your-migration-playbook-26la</link>
      <guid>https://dev.to/shockalotti/caleu-is-closing-on-nov-1-here-is-your-migration-playbook-26la</guid>
      <description>&lt;p&gt;Cal.eu shuts down on November 1. Your EU-hosted booking data has an expiry date.&lt;/p&gt;

&lt;p&gt;Cal.com said it plainly in their own migration notice: "Cal.eu will remain available until November 1, 2026." New signups are already closed. If your team picked Cal.eu because client meetings should stay on EU soil, that reason disappears in about 45 days. You either move to Cal.com's US-hosted platform, or you move somewhere else.&lt;/p&gt;

&lt;p&gt;This is a short, practical playbook for getting your scheduling off Cal.eu and onto infrastructure you control, before the deadline. Disclosure: I build Calnode, one of the options below. The other two are real options too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually happening
&lt;/h2&gt;

&lt;p&gt;Cal.com is consolidating Cal.eu into Cal.com International. Enterprise contracts get extensions, everyone else gets a data export and a deadline. The notice is published in four languages, so this is settled, not rumoured.&lt;/p&gt;

&lt;p&gt;If a client contract says EU hosting, "we moved to the US version" breaks that promise. You need another answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your three options
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Accept the migration to Cal.com International.&lt;/strong&gt; Easiest path, keeps your existing setup. You lose EU residency. Fine if nobody ever asked where the data lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Move to another EU-hosted scheduler.&lt;/strong&gt; There are ten or so credible ones. This works, but you are swapping one vendor's database for another's. Next shutdown, same problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Self-host.&lt;/strong&gt; One server, your database file, your rules. No vendor can take this one away from you. The rest of this post is option 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  The playbook
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Export everything from Cal.eu now.&lt;/strong&gt; Cal.com says you will get the chance to export before access ends. Do not wait for the last week. Pull event types, bookings, and team member lists before the rush.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Stand up one binary.&lt;/strong&gt; Calnode is a single static Go binary with an embedded SQLite database. No Redis, no Postgres, no separate API server. &lt;code&gt;docker run&lt;/code&gt; one container with data in &lt;code&gt;./data&lt;/code&gt; and you have booking pages, an admin UI, and the database in one place. Set an encryption key and point Litestream at your backup target for point-in-time recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Recreate your event types and booking links.&lt;/strong&gt; This is the manual hour. Rebuild each meeting type, set the durations and buffers you actually use, and wire confirmation emails and reminders. Calnode fires confirmations, webhooks, and reminders off the same booking event, so everything stays in sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Reconnect your automations.&lt;/strong&gt; If n8n, Make, or an internal service consumed Cal.com webhooks, repoint them at HMAC-signed Calnode webhooks, configured via API. If agents book on your behalf, Calnode includes a native MCP server in the same binary: stdio for local agents, streamable HTTP for remote ones, with role-scoped tools so an agent gets the permissions you grant it and nothing more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Cut over one calendar at a time.&lt;/strong&gt; Point one event type at the new links, run it for a week, then move the rest. Keep Cal.eu read-only until November so old links keep working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this instead of another SaaS
&lt;/h2&gt;

&lt;p&gt;Every hosted scheduler is a promise that someone else keeps your data where you want it. Cal.eu was that promise. It ends Nov 1. Self-host, and your data is where your server is. No terms and conditions.&lt;/p&gt;

&lt;p&gt;One binary. One SQLite file. Your meetings stay where you put them.&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://cal.com/blog/cal.eu-to-cal.com-migration" rel="noopener noreferrer"&gt;Cal.com migration notice&lt;/a&gt;, &lt;a href="https://app.cal.eu/signup" rel="noopener noreferrer"&gt;Cal.eu signup page&lt;/a&gt;, &lt;a href="https://eualternative.eu/alternative-to/cal-com" rel="noopener noreferrer"&gt;EU alternatives to Cal.com&lt;/a&gt;. Calnode: &lt;a href="https://calnode.com/" rel="noopener noreferrer"&gt;calnode.com&lt;/a&gt; (&lt;a href="https://github.com/Calnode/calnode" rel="noopener noreferrer"&gt;github.com/Calnode/calnode&lt;/a&gt;, Apache-2.0).&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>saas</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
