MTD Calendar Hub: Support Information

Summary

Public calendar for MTD productions and departments, built from Outlook feeds, with an editor control panel.
Editor instructions, access, architecture and support information for the MTD Calendar Hub (aidev.ithaca.edu/mtdcalendar).

Body

Knowledge Base internal Article Banner

 

Introduction

The MTD Calendar Hub shows all School of Music, Theatre and Dance production and department calendars on one public page. This article starts with how editors add and manage calendars, then covers how IT grants editor access and how the service is built and supported.

Audience

MTD staff who edit calendars: Editor Tasks. Ithaca College IT staff who support the MTD Calendar Hub: all sections after it.

Platform

Docker Compose on agenai02.campus.ithaca.lan, Microsoft Entra ID single sign-on, Outlook published calendars (.ics)

Table of Contents

Editor Tasks

Sign In

  1. Go to https://aidev.ithaca.edu/mtdcalendar/admin
  2. Sign in with your Ithaca College Microsoft account

If you see "not set up as an editor", contact the File and Access group for editor access, then sign out and back in.

Add a Calendar

Get the feed link from Outlook:

  1. Open Outlook on the web and choose Settings > Calendar > Shared calendars
  2. Click the calendar under Publish a calendar and choose Can view all details
  3. Click Publish
  4. Copy the ICS link (the HTML link is optional)

Add it in the control panel:

  1. Click Add calendar
  2. Enter the name, section, code (for example TS), term (for example FA26), responsible person, color and ICS link; codes must be unique and colors must be readable on white
  3. Choose a Status
    • Draft: not shown publicly; may be incomplete
    • Active: shown publicly; needs a code, color and ICS link
  4. Click Save

Changes reach the public page within 5 minutes. Refresh all feeds now makes it happen within a minute. A section with no Active calendars is not shown publicly.

To change a calendar's ICS link, open it and paste the new one. Events from the old link leave the public page at once; the new link's events appear after its check succeeds.

For many calendars at once, use Export CSV, edit in Excel, then Import CSV. A row with an existing id updates that calendar; a new id creates one. If any row has a problem, nothing is imported and every problem is listed.

Archive or Delete a Calendar

  • Archive: open the calendar and set Status to Archived; it leaves the public page and stays in the list
  • Delete: removes it permanently

Every change is recorded under History with who, when, and before and after values.

Feed Status

Badge Meaning Action
Checking Feed is being checked after a save Wait about 10 seconds
OK Last download worked None
Error Download failed; the message says why Re-publish in Outlook and paste the new ICS link
Check Every event shows "Busy" Re-publish with Can view all details
Not fetched Not downloaded yet Wait, or click Refresh all feeds now

Events marked Private in Outlook show publicly as "Reserved" with no title or location.

Overview

Public URL: https://aidev.ithaca.edu/mtdcalendar

  • Public page (/): no login, readable by anyone who can reach the site
  • Control panel (/admin): Microsoft sign-in, editors only
  • Health check (/healthz)

Request flow:

Browser > nginx (HTTPS) > web container :8000 > SQLite database
refresher container > Outlook .ics feeds > SQLite database

Components:

  • nginx: separate container shared with other apps on the host, terminates HTTPS, reaches web over the external Docker network proxy; strips the /mtdcalendar prefix and sends X-Forwarded-Prefix, X-Forwarded-Proto and X-Forwarded-Host
  • web: Flask app under gunicorn; serves the public page, its JSON, and the control panel; never downloads feeds
  • refresher: same image; downloads every Active calendar's feed every 5 minutes and acts on refresh requests within 10 seconds
  • Database: one SQLite file, /data/mtdcal.db, on the Docker volume data

The database holds the calendar list, sections and change history. Events are re-fetched from Outlook, so losing them costs nothing.

Outbound access required:

  • login.microsoftonline.com from web (sign-in)
  • outlook.office365.com and the other hosts in FEED_ALLOWED_HOSTS from refresher (feeds)

Host and Access

  • Server: agenai02.campus.ithaca.lan, owned by the AI Development Team
  • Project folder: /opt/mtd_calendar (holds docker-compose.yml and .env)
  • File ownership: Linux local group mtd-cal
  • nginx: its own Docker container on the same host; reverse proxy for several apps, not only this one
  • TLS: terminates at the nginx reverse proxy; the certificate is managed automatically by Azure Gateway
  • Server access: submit a ticket to the Linux Sysadmin team to have your -sa@ithaca.edu account added to the server

Never run docker compose down -v or docker volume rm. Both delete the database.

Docker must start on boot (systemctl enable --now docker). Both containers restart automatically once Docker is up.

Secrets and Renewals

Item Where it lives Expires Owner
Entra client secret (AZURE_CLIENT_SECRET) .env on the host; Entra app registration 9/24/2028 AI Development Team
Session signing key (SECRET_KEY) .env on the host Does not expire AI Development Team

backup.sh does not back up .env. If it is lost, create a new client secret in Entra and a new SECRET_KEY; the new key signs every editor out. Permissions on the host: chmod 600 .env.

Changing SECRET_KEY signs every editor out. Use it to cut access immediately.

Renew the Client Secret

Do this before the old secret expires. Editors cannot sign in once it does.

  1. Open the AI Dev IC App Hub app registration in the Entra admin center
  2. Click Certificates & secrets > New client secret
  3. Copy the Value (not the Secret ID); it is shown once
  4. Replace AZURE_CLIENT_SECRET in .env on the host
  5. Run docker compose up -d
  6. Test sign-in at /admin
  7. Delete the old secret in Entra
  8. Update the expiry date in the table above

Microsoft Entra Configuration

  • App registration: AI Dev IC App Hub, single tenant (this directory only)
  • Application (client) ID and Directory (tenant) ID: see .env (AZURE_CLIENT_ID, AZURE_TENANT_ID)
  • Redirect URI (Web): https://aidev.ithaca.edu/mtdcalendar/auth/callback
  • API permissions: default User.Read only
  • App role: display name Calendar Editor, value Calendar.Editor, allowed member type Users/Groups
  • Enterprise application: AI Dev IC App Hub; Assignment required is Yes, so only assigned users can sign in
  • Editor assignment (current): individual users are assigned to the Calendar Editor role on the enterprise application
  • Editor assignment (planned): an on-premises AD group, synced to Entra, assigned to the Calendar Editor role; group name [TODO: group name once created]

The app checks the Calendar.Editor role on every request to /admin. Signed-in users without it see "not set up as an editor".

Add or Remove an Editor

Current method, per user:

  1. Open the Entra admin center and choose Enterprise applications > AI Dev IC App Hub > Users and groups
  2. Click Add user/group and select the person, or select the person and click Remove
  3. Choose the Calendar Editor role when adding
  4. Have the person sign out and back in at /admin

Once the AD group exists ([TODO: group name]), add or remove the person in that group instead. Changes reach Entra at the next directory sync.

Access changes apply at the next sign-in. Sessions end 8 hours after sign-in, so a removed editor can keep access for up to 8 hours. To cut access immediately, also change SECRET_KEY in .env and run docker compose up -d.

Routine Support

Run these from /opt/mtd_calendar on the host.

Update the App

  1. Run git pull
  2. Run docker compose up -d --build

Database migrations apply automatically when web starts.

Check Status and Logs

  • docker compose ps (web should be healthy)
  • docker compose logs --since 1h web (requests, sign-ins, errors)
  • docker compose logs --since 1h refresher (one line per calendar per refresh)

Control panel changes are also visible at /admin/audit.

Back Up and Restore

  • Back up: ./backup.sh writes a dated copy to ./backups and deletes copies older than 14 days; safe while running
  • Schedule: none; backup.sh is not in cron, run it by hand before risky changes
  • Off-host copy: none; backups stay in /opt/mtd_calendar/backups. The VM is snapshotted on a schedule. Calendar data is re-fetched from Microsoft 365, so frequent backups are not critical

Restore (use the file you want):

  1. Run docker compose stop
  2. Run docker compose run --rm -v "$PWD/backups":/restore web sh -c "cp /restore/<backup file> /data/mtdcal.db && rm -f /data/mtdcal.db-wal /data/mtdcal.db-shm"
  3. Run docker compose start

Settings

All settings are environment variables in .env. After a change, run docker compose up -d. The full list is in mtdcal/config.py and docs/ADMIN_GUIDE.md in the repository: mtd_calendar repository (Azure DevOps).

Troubleshooting

Symptom Cause and fix
Sign-in fails with AADSTS50011 Redirect URI in the app registration does not match https://<host>/auth/callback; check nginx passes X-Forwarded-Proto and X-Forwarded-Host
"Microsoft sign-in failed" Usually an expired or wrong client secret; check the web log
"Not set up as an editor" User lacks the Calendar.Editor role; check group membership and sync, then sign out and in
Public page shows "Updated" hours ago (red dot), or Refresh does nothing Refresher is not running; docker compose ps and docker compose logs refresher
Public page shows "couldn't be loaded" web is down or nginx cannot reach it; docker compose ps, check the proxy network
Calendar shows Error See Feed Status
Page will not load inside SharePoint Set FRAME_ANCESTORS to the SharePoint site origin in .env
docker compose up fails with network proxy not found The proxy network name in docker-compose.yml does not match nginx's network; docker network ls

Support Contacts

  • Editors and access requests: File and Access group
  • Server and application: AI Development Team
  • Content owner (MTD): Zachary Mcdonald

Details

Details

Article ID: 2268
Created
Wed 10/7/26 9:20 AM
Modified
Thu 10/8/26 9:38 AM
Who is the audience for this Knowledge Base Article (KBA)?
Who is the target audience of this article?
Internal