A fullscreen touchscreen calendar app for Raspberry Pi. Syncs with Google Calendar, shows a live clock and weather, and fires configurable reminders.
![Default view: week view with current day highlighted, sidebar with clock/weather/next event]
- Week view (default) with current day highlighted and a live time-of-day indicator
- Month and day views, swipe left/right to navigate
- Google Calendar sync — create, edit, and delete events that sync back to Google
- Sidebar widgets: live clock, current weather (OpenWeatherMap), next event today with countdown
- Per-event notifications: configurable minutes-before alerts, optional chime sound
- Fullscreen kiosk mode on Pi, normal window in dev
| Dependency | Version |
|---|---|
| Node.js | 18 or later |
| Raspberry Pi OS | 64-bit Desktop recommended |
| Google account | for Calendar API |
| OpenWeatherMap account | free tier |
Run this on your Pi (connected to the internet):
bash <(curl -fsSL https://raw.githubusercontent.com/danlafeir/calendar/main/install.sh)The script will:
- Install Node.js 20 LTS if needed
- Install Electron's system library dependencies via
apt - Clone this repo and
npm install - Build the app for ARM64 (~10 min on a Pi 4)
- Prompt you for credentials (Google + OpenWeatherMap)
- Set up a systemd user service so the app starts on boot
- Optionally start the app immediately
Before first launch you need a Google OAuth Desktop App client.
- Go to console.cloud.google.com and create a project
- Enable the Google Calendar API: APIs & Services → Enable APIs → search Google Calendar API
- Create credentials: APIs & Services → Credentials → Create Credentials → OAuth client ID
- Application type: Desktop app
- Configure the consent screen: APIs & Services → OAuth consent screen
- User type: External
- Add your Gmail as a test user
- Add scope:
https://www.googleapis.com/auth/calendar
- Copy the Client ID and Client Secret into your
.env
- Sign up at openweathermap.org — the free tier is enough
- After signing up, go to API keys in your account dashboard and copy your default key (or generate a new one)
- Note: new API keys can take up to 2 hours to activate after sign-up
- Set your location coordinates in
.env(decimal degrees — positive = North/East, negative = South/West)
Finding your coordinates:
- Search your city on Google Maps, right-click your location, and the first line of the context menu shows
lat, lon - Or use latlong.net
The weather widget displays in the top navigation bar showing the current temperature and condition (e.g. ☀️ 72° / clear sky). Data is cached for 10 minutes. If the API key is missing or invalid, the widget is hidden rather than showing an error.
Copy .env.example to .env and fill in all values:
cp .env.example .env# Google OAuth — Desktop App client
GOOGLE_CLIENT_ID=xxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxx
# OpenWeatherMap free-tier key
OPENWEATHER_API_KEY=xxxxx
# Your location for weather (decimal degrees)
# Example: San Francisco
WEATHER_LAT=37.7749
WEATHER_LON=-122.4194npm install
npm run devElectron opens in a normal (non-kiosk) window. The first thing you'll see is the Connect Google Calendar screen — click it and a browser window opens the Google OAuth consent flow. After you approve, the window closes and your calendar loads automatically. Tokens are saved locally and refreshed silently going forward.
Drop any MP3 file at resources/sounds/chime.mp3. If the file is missing, notifications still show the modal but play no sound.
npm run build:linux:arm64Output is at dist/linux-arm64-unpacked/. Copy it to the Pi:
rsync -avz dist/linux-arm64-unpacked/ pi@raspberrypi.local:/home/pi/calendar/Then on the Pi, launch with the --no-sandbox flag (required on Raspberry Pi):
/home/pi/calendar/pi-calendar --no-sandboxThe install script sets this up automatically. To do it manually:
# Copy and edit the service file
cp resources/autostart/pi-calendar.service ~/.config/systemd/user/
nano ~/.config/systemd/user/pi-calendar.service # update paths + credentials
systemctl --user daemon-reload
systemctl --user enable pi-calendar
systemctl --user start pi-calendarCheck logs:
journalctl --user -u pi-calendar -fcd ~/pi-calendar
git pull
npm run build:pi
systemctl --user restart pi-calendarsystemctl --user disable --now pi-calendar
rm -rf ~/pi-calendar
rm ~/.config/systemd/user/pi-calendar.service
systemctl --user daemon-reloadsrc/
├── types/ipc.ts — shared types + IPC channel constants
├── main/ — Electron main process (Node.js)
│ ├── auth.ts — Google OAuth2 loopback flow + token refresh
│ ├── calendar.ts — Google Calendar CRUD via googleapis
│ ├── weather.ts — OpenWeatherMap fetch, 10-min cache
│ ├── notifications.ts — 30 s scheduler → IPC push to renderer
│ ├── store.ts — electron-store + OS-level token encryption
│ └── ipc.ts — ipcMain handlers + background sync
├── preload/index.ts — contextBridge API surface
└── renderer/src/
├── App.tsx — auth gate, sidebar, view switcher
├── views/ — WeekView (default), MonthView, DayView
├── components/
│ ├── CalendarGrid/ — WeekGrid, MonthGrid, DayGrid, EventBlock, TimeIndicator
│ ├── widgets/ — ClockWidget, WeatherWidget, NextEventWidget
│ └── modals/ — EventModal, NotificationModal, ConfirmDeleteModal
├── store/ — Zustand: calendar, weather, UI state
└── hooks/ — calendar sync, notifications, swipe gestures
| Framework | React 18 + Electron |
| Build tool | electron-vite |
| State | Zustand |
| Date math | date-fns |
| Google API | googleapis (main process only) |
| Storage | electron-store + safeStorage |
| Sound | Howler.js |
| Weather | OpenWeatherMap REST API |