Skip to content

Jojodicus/calunite

Repository files navigation

Go Docker MIT

Go Report Card Docker Image

🗓️ CalUnite

Calendar sharing has long been unnecessarily cumbersome, especially when the objective is to consolidate multiple calendar sources into a single, easily shareable view for partners, families, or groups.

While several tools exist to merge calendars, many available solutions fall into two extremes: they either lack essential flexibility or are burdened by unnecessary complexity and overengineering.

This is where CalUnite comes in:

  • Combine RFC 5545 calendars (.ics) using URLs or local paths
  • Supports multiple calendars with a single config file
  • Custom formatting of event titles for different calendars
  • Calendar aliases (private or publicly served)
  • Recursive merging (with cycle detection)
  • Combined calendars are immediately served with a webserver
  • Hot-reload configuration, no need to stop the container
  • Fast deployment using Docker
  • Written in memory-safe Go

✏️ Configuration

A sample configuration file can be found at config.yml. It's recommended to read the documentation in there. Additional settings can be tweaked using the container's environment variables, see the following Deployment section for more details.

If you want to use a hot-reloadable configuration, make sure to use a bind mount (directory path in volume specifier, not a regular file), as is also shown in the examples. This ensures that the file is synced between host and container.

During a hot-reload, the old content directory will be cleared entirely, keep that in mind when mounting your own volumes (/wwwdata by default). If you want to include files other than ones specified in the 'config.yml', it's advised to do that via a reverse proxy or as a sub-path from your existing webserver.

🏎️ Deployment

CalUnite uses Docker for deployment. Preferably, this is done via Compose:

compose.yml

services:
  calunite:
    image: jojodicus/calunite
    container_name: calunite
    restart: unless-stopped
    volumes:
      - ./calunite:/config/
    ports:
      - 8080:8080
    environment: # OPTIONAL - the values below are the defaults
      CFG_PATH: /config/config.yml # path of config file within the container
      CRON: "@every 15m"           # how often the merger should run, format: https://pkg.go.dev/github.com/robfig/cron#hdr-CRON_Expression_Format
      PROD_ID: CalUnite            # RFC 5545 PRODID, who created the calendar
      CONTENT_DIR: /wwwdata        # directory from which the files are served, no change needed in most cases
      FILE_NAVIGATION: false       # generate an index.html for directories to allow for navigation
      DOT_PRIVATE: true            # if files prefixed with a dot (.) should not be served, only applicable when FILE_NAVIGATION is false
      ADDR: 0.0.0.0                # address to bind to
      PORT: 8080                   # port to expose
      LOG_LEVEL: info              # log level, one of [debug, info, warn, error] - case insensitive

🖧 Reverse Proxy

Files are served via HTTP. If you want HTTPS, this should be done via a reverse proxy like Caddy. A minimal example would look something like this:

compose.yml

services:
  calunite:
    image: jojodicus/calunite
    container_name: calunite
    restart: unless-stopped
    volumes:
      - ./calunite:/config/
    # no exposed ports needed

  caddy:
    image: caddy
    container_name: caddy
    restart: unless-stopped
    volumes:
      - ./caddy/:/etc/caddy/
    ports:
      - 80:80
      - 443:443

caddy/Caddyfile:

example.com {
    reverse_proxy calunite:8080
}

If you're looking to host this without having a public IP (typical for most home internet connections), you could use something like a Cloudflare Tunnel. A curated guide on how to set this up can be found here.

A Caddy configuration with files other than calendars managed by CalUnite may look like this. Adjust to your own needs:

caddy/Caddyfile:

example.com {
    handle_path /*.ics {
        reverse_proxy calunite:8080
    }

    # or use your own webserver
    handle {
        root * /path/to/mounted/website
        file_server
    }
}

🔗 How to get iCal links

Google Calendar

For Google, this is only possible on the desktop webiste. If you are on mobile, you can change the view at the bottom of the page.

Go to your Google Calendar Settings and click on your desired calendar. Here, you can copy the "address in iCal format".

For non-public calendars, you'll need the secret address.

Apple Calendar

Go to your Apple Calendar. Then, click on the person icon next to a calendar (will appear when hovering over the name).

On mobile, press the calendar icon at the bottem of the page, then press the "i" icon next to a calendar.

Make the calendar public and copy the link.

📡 Subscribing to calendars

Google Calendar

For Google, this is only possible on the desktop website. If you are on mobile, you can change the view at the bottom of the page. Go to Google Calendar - Add from URL and paste the URL.

Apple Calendar

On your Apple device, click the calendar icon. At the bottom under "Add Calendar", add a subscription calendar and paste the URL.

⌨️ Development

For local development, create a testdata/config.yml, then run ./run.sh to start CalUnite via the commandline (may need elevated permissions depending on your Docker setup).