No description
  • JavaScript 84.5%
  • Java 8.7%
  • CSS 5.4%
  • HTML 0.7%
  • Shell 0.6%
  • Other 0.1%
Find a file
Gjessing1 b26bb0dc5a
All checks were successful
docker / build-and-deploy (push) Successful in 56s
chore(deploy): move host deploy dir to ~/docker/<Category>/<svc>
2026-10-28 21:32:46 +00:00
.forgejo/workflows chore(deploy): move host deploy dir to ~/docker/<Category>/<svc> 2026-10-28 21:32:46 +00:00
.github/workflows ci: fold test gate into image build and skip CI on doc-only pushes 2026-06-13 08:37:27 +00:00
android fix: assert Android passkeys for the web origin, not the app 2026-09-13 20:21:02 +00:00
assets/screenshots docs(readme): add app screenshots 2026-10-28 18:42:44 +00:00
client feat: token-authenticated agenda endpoint for the Glance widget 2026-10-25 09:05:08 +00:00
mobile feat: add Android app update channel 2026-08-14 17:48:04 +00:00
public/icons feat: build the web app with Vite and Workbox 2026-08-21 17:26:42 +00:00
scripts chore(deploy): move host deploy dir to ~/docker/<Category>/<svc> 2026-10-28 21:32:46 +00:00
server feat: token-authenticated agenda endpoint for the Glance widget 2026-10-25 09:05:08 +00:00
test feat: token-authenticated agenda endpoint for the Glance widget 2026-10-25 09:05:08 +00:00
.dockerignore feat: build the web app with Vite and Workbox 2026-08-21 17:26:42 +00:00
.env.example feat: add BYPASS_AUTH to skip password login behind SSO 2026-06-13 08:37:27 +00:00
.gitignore feat: build the web app with Vite and Workbox 2026-08-21 17:26:42 +00:00
.prettierignore feat: add Android app update channel 2026-08-14 17:48:04 +00:00
.prettierrc.json chore: add Prettier + ESLint matching maily, format repo, remove dead code 2026-07-02 18:06:24 +00:00
AGENTS.md feat: add dynamic Android launcher icon 2026-08-14 18:53:45 +00:00
ANDROID_APP_PLAN.md feat: deliver Android reminders as native alarms 2026-08-19 18:36:25 +00:00
capacitor.config.json fix: polish Android status bar and date icon 2026-08-14 19:09:35 +00:00
CLAUDE.md feat: add dynamic Android launcher icon 2026-08-14 18:53:45 +00:00
docker-compose.yml fix: require safe deployment storage paths 2026-09-16 13:52:19 +00:00
docker-entrypoint.sh feat: Phase 8 + 9 — dark mode, auth, animations, UI polish 2026-05-06 17:22:59 +00:00
Dockerfile test: cover event editor DOM wiring 2026-09-29 05:53:41 +00:00
eslint.config.mjs feat: build the web app with Vite and Workbox 2026-08-21 17:26:42 +00:00
index.html fix: preserve Android inset during Tailwind migration 2026-08-21 20:53:28 +00:00
NLP.md chore: add Prettier + ESLint matching maily, format repo, remove dead code 2026-07-02 18:06:24 +00:00
package-lock.json test: cover event editor DOM wiring 2026-09-29 05:53:41 +00:00
package.json test: cover event editor DOM wiring 2026-09-29 05:53:41 +00:00
README.md docs(readme): add app screenshots 2026-10-28 18:42:44 +00:00
tsconfig.base.json feat: type-check the whole codebase with tsc --checkJs 2026-07-02 18:16:45 +00:00
tsconfig.client.json feat: build the web app with Vite and Workbox 2026-08-21 17:26:42 +00:00
tsconfig.server.json feat: type-check the whole codebase with tsc --checkJs 2026-07-02 18:16:45 +00:00
vite.config.mjs feat: add Tailwind build foundation 2026-08-21 20:09:26 +00:00

Nodecal

Nodecal is a self-hosted, mobile-first CalDAV calendar and task client. It runs in a browser as a PWA and is also available as a private Android app that connects to your Nodecal server.

Agenda listing today's and tomorrow's events with category filters Day view with colour-coded work and family events on a time grid Month view in dark mode with event chips and multi-day bars Task list grouped by due date with priority and category tags
Agenda Day view Month (dark mode) Tasks

Screenshots: Nodecal 0.1.10 (commit a0a105e), taken 28 October 2026 with sample data.

Download the Android app

Open this address on the Android device, replacing the example host with your Nodecal host:

https://calendar.example.com/api/app/download

Android may ask you to allow your browser to install apps from this source. After installation, open Nodecal and enter the root HTTPS URL of the server, such as https://calendar.example.com. The choice is stored on the device and can later be changed under Settings → Android app.

Pocket ID and Bitwarden passkeys

Passkey requests run in WebView's WebAuthn browser mode (APK 0.1.10 and later): the WebView builds the request for the Pocket ID page it is showing, so the passkey is asserted for the Pocket ID site's own HTTPS origin exactly as it would be in a browser. Bitwarden passkeys require Android 14 or later; in Bitwarden, open Settings → Autofill → Passkey management and select Bitwarden as the preferred passkey provider. Also keep Android System WebView current.

The first passkey login from the app stops with Bitwarden reporting that the browser (Nodecal) is not recognized. Tap Trust, then choose the passkey: Bitwarden adds the package and its signing certificate to its locally trusted privileged apps, and later logins go straight through. Trust belongs to the signing certificate, so an APK signed with a different key (a debug build, say) has to be trusted separately.

No /.well-known/assetlinks.json is involved. WebAuthn's app mode, which Digital Asset Links would authorise, asserts the passkey for the app's android:apk-key-hash: origin instead of the website's, and Pocket ID only accepts its own HTTPS origin — Bitwarden refuses app mode with "Passkeys not supported for this app" when the asset links are missing, and Pocket ID rejects the login when they are present.

The download answers with 404 Not Found until the server administrator publishes the first APK. You can always use the web app instead: visit the Nodecal URL in Chrome and choose Add to Home screen.

Android updates

The Android container loads Nodecal from the selected server, so calendar UI and server changes arrive with normal server deployments. A new APK is only needed when the native Android container, permissions, icons, or native plugins change.

When a newer APK is published, the app shows a Download notice and also lists it under Settings → Android app. The download opens in the system browser so Android can verify and install it. Keep the signing keystore safe: every update must use the same key.

Publish an Android release

Requirements are Node.js 22.12+, a JDK, and an Android SDK. On this host the SDK environment is loaded automatically from /home/gjessing/android-sdk/env.sh; elsewhere, export ANDROID_HOME and JAVA_HOME.

Create ~/.config/nodecal/keystore.env with the release-signing values (do not commit this file):

export NODECAL_KEYSTORE_FILE=/absolute/path/to/nodecal-release.jks
export NODECAL_KEYSTORE_PASSWORD='...'
export NODECAL_KEY_ALIAS='nodecal'
export NODECAL_KEY_PASSWORD='...'

For every native release, increment versionCode and update versionName in android/app/build.gradle, then run the end-to-end release command:

npm ci
npm run android:release

This command reads the version from Gradle, makes a clean release-signed build, publishes it, confirms the live /api/app/version response, downloads the served APK, and verifies its checksum. It defaults to this host's live Compose config directory when present, or /mnt/data/nodecal/config/app when that path is backed by the expected tank/data/nodecal dataset. Set NODECAL_ANDROID_APP_DIR and NODECAL_ANDROID_VERIFY_URL for another deployment. A native release is not complete until this command succeeds; committing or building a debug APK does not publish an update.

The server exposes public metadata at /api/app/version and the current APK at /api/app/download.

For local development, build the debug APK with:

npm run android:build:debug

The debug APK is written to android/app/build/outputs/apk/debug/app-debug.apk. Do not publish debug-signed builds; Android cannot install them over a release-signed app.

Run the server

Copy the required values into .env (CALDAV_BASEURL, CALDAV_USERNAME, and CALDAV_PASSWORD), then use:

npm install
npm run build
npm start

For local web development, npm run dev starts the watched API server on port 3000 and Vite on port 5173.

Docker users can use the included docker-compose.yml; it exposes Nodecal on host port 3042 and persists configuration under /mnt/data/nodecal/config. Create both /mnt/data/nodecal/config and /mnt/data/nodecal/cache first; Compose deliberately refuses to create missing bind-mount directories on the root filesystem.

Quality checks

npm run format:check
npm run lint
npm run typecheck
npm test
npm run build