- TypeScript 75.8%
- SCSS 10.6%
- Rust 5.7%
- JavaScript 4.5%
- Kotlin 1.1%
- Other 2.1%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| .idea | ||
| .vscode | ||
| containers | ||
| crates@08170041d7 | ||
| docs | ||
| flatpak | ||
| frontend-checks@259e5b8c41 | ||
| graphics/logo | ||
| LICENSES | ||
| messages | ||
| pb | ||
| plugins | ||
| project.inlang | ||
| public | ||
| scripts | ||
| src | ||
| src-tauri | ||
| test | ||
| tools | ||
| .env.template | ||
| .git-blame-ignore-revs | ||
| .gitignore | ||
| .gitmodules | ||
| .mise.toml | ||
| CHANGELOG.md | ||
| de.dencla.nobo.yml | ||
| flake.lock | ||
| flake.nix | ||
| frontend-checks.config.mjs | ||
| generated-cargo-sources.json | ||
| generated-pnpm-sources.json | ||
| index.html | ||
| LICENSE | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| renovate.json | ||
| REUSE.toml | ||
| rust-toolchain.toml | ||
| rustfmt.toml | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
nobo
Local-first Org-mode notes, tasks, projects, calendars, recipes and encrypted vault sync, built with Rust, Tauri and SolidJS.
Screenshots
Screenshots from the nobo Linux test sandbox with synthetic demo data and the local test server. Desktop, tablet and mobile layouts are shown below. Click a thumbnail to view the full-resolution image.
| Agenda · Desktop | Inbox · Desktop | Projects · Desktop |
|---|---|---|
| Lists · Mobile | Cookbook · Tablet | Notes · Desktop |
|---|---|---|
Watch the MP4 tour · All screenshots, dark themes and recording details
Clone
The shared Rust core lives in a submodule under crates/ and the frontend
checks in frontend-checks/:
git clone --recurse-submodules https://github.com/denclade/nobo.git
git submodule update --init # if already cloned without --recurse-submodules
Development environment (MicroVM)
Development runs inside an isolated NixOS MicroVM built from
nix-dev-sandbox
(flake.nix). Only the project directory is shared with the host.
1. Host setup (once)
sudo usermod -aG kvm $USER
mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
cp .env.template .env # set DEV_SSH_PUBKEY and git identity
~/.ssh/config:
Host nobo-vm
HostName 127.0.0.1
Port 2200
User dev
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
IdentityFile ~/.ssh/id_ed25519
2. Start and connect
nix run --impure
code --remote ssh-remote+nobo-vm /workspaces/nobo
Inside the VM, run mise run setup once to install the Rust toolchain, Android
targets, submodules and pnpm dependencies.
3. Tasks (mise)
Run mise tasks to list all tasks:
| Command | Description |
|---|---|
mise run setup |
One-time VM setup (Rust, Android targets, submodules, pnpm install). |
mise run dev |
Starts Vite + Tauri dev server with the local test server and notification daemon (sandbox only, guarded by scripts/require-sandbox.sh). |
mise run check |
Runs Rust tests in both workspaces and all frontend/architecture checks (pnpm check:all). |
mise run check:lite |
Verifies every reduced feature combination. |
mise run build:desktop |
Builds native Linux packages. |
mise run install:system |
Installs the built binary, icons and .desktop file (PREFIX=/usr default, or PREFIX=$HOME/.local). |
mise run uninstall:system |
Removes files installed by install:system. |
mise run build:flatpak |
Generates offline sources and builds nobo.flatpak. |
mise run flatpak:lint |
Validates flatpak/de.dencla.nobo.desktop and flatpak/de.dencla.nobo.metainfo.xml. |
mise run dev:android |
Starts the KVM Android emulator and runs the app with hot reload. |
mise run android:release |
Builds the signed Android release APK. |
mise run release:prepare <version> |
Server release, step 1: sets pb/VERSION, commits, tags cloud-v<version> (no push; see Operations). |
mise run release:deploy <tag> |
Server release, step 2: checks the tagged, clean state and builds deploy/ and dist/nobocloud-<tag>.tar.zst. |
mise run build:deploy <name> |
Trial build of the server bundle without version checks (PR check); never a delivery. |
mise run test:containers |
Builds every server role from deploy/ and starts it as restricted as on d0b (needs Podman). |
For macOS / iOS (nix develop, mise run ios:init, mise run dev:ios,
mise run build:ios), see docs/mobile.md.
For the D-Bus Secret Service / gnome-keyring setup in the VM, source
scripts/dev-keyring.sh (done automatically by mise run dev; see
docs/secrets.md).
Architecture
- Rust core & feature plugins (
crates/):crates/nobo-core/holds Org parsing, indexing, search, history, settings and storage traits. Optional capabilities (nobo-babel,nobo-bookmarks,nobo-calendar,nobo-caldav,nobo-cookbook,nobo-review,nobo-sync) depend on the core, never the reverse (enforced bypnpm check:layers-rust). See docs/architecture.md and docs/core-api.org. - Tauri host (
src-tauri/&plugins/): Platform adapters (nobo-tauri) and native plugins (tauri-plugin-gestures,tauri-plugin-securestore,tauri-plugin-share). - Frontend (
src/): SolidJS + CodeMirror 6 AST editor (orgKeymap.ts,orgLinePlugin.ts,orgInlinePlugin.ts,orgListPlugin.ts). Feature UI and command bindings live insrc/features/<name>/and register insrc/features/index.ts. Coding and UI standards are documented in docs/qualitaet-und-coding-standards.md. - Releases: See docs/releasing.md and CHANGELOG.md.
Operations
The sync backend is a PocketBase instance, nobocloud, deployed on the server
d0b (contract 1): rootless Podman pod under its own user, central Caddy in
front, snapshot and automatic rollback on every deploy.
| Service / user / pod | nobocloud, number 10 (UID 10010, 1000:1000 in the container) |
| Domain | nobocloud.dencla.de → 127.0.0.1:11090 → pod port 8090 |
| Data | /srv/nobocloud/pb_data → /pb/pb_data |
| Environment | /etc/apps/nobocloud/pb.env (template: containers/env/pb.env.example) |
What lives where:
containers/– Containerfile,build.env(PocketBase version and checksum), Quadlets,site.caddy,smoke.txt.app deploychecks these files line by line; keep them free of header comments and within the rules of the contract.pb/pb_migrations/– createssync_indexanduser_filesand requires verified accounts (what docs/sync.md describes by hand). Runs on every start of a new release; existing collections are left alone.1793030401_rate_limits.jsturns on PocketBase's rate limiter (login, registration, mail-sending endpoints), trustsX-Forwarded-Forfrom Caddy and sets the app URL used in mail links.1761500000_d0b_mail_links.jspoints the links in PocketBase's mails at/konto.htmlinstead of the dashboard, which d0b always answers with 403.pb/pb_public/konto.html+konto.js– confirms an account from the link in the verification mail (the token sits after#, so it never reaches a log).pb/pb_hooks/konto.pb.js– refuses password reset and email change: nobo derives the account password from vault password and email, so either would lock the account out for good without bringing the encrypted data back.
Release: the server has its own version, only in pb/VERSION (SemVer, pre-releases
as 1.2.0-beta.2); its tag is cloud-v<version>. vX.Y.Z belongs to the app
(docs/releasing.md).
mise run release:prepare 1.0.0 # commit "(c) Release nobocloud 1.0.0" + annotated tag cloud-v1.0.0
mise run release:deploy cloud-v1.0.0 # checks, tests, deploy/ and dist/nobocloud-cloud-v1.0.0.tar.zst (+ .sha256)
mise run test:containers # from deploy/: rootless, read-only, no capabilities, health, UID 1000
git push origin main && git push origin cloud-v1.0.0 # only now; .forgejo/workflows/release.yml attaches the bundle
release:prepare (scripts/cloud-release-version.mjs) refuses an invalid
version, one not greater than the current, a dirty tree and an existing tag.
release:deploy (scripts/cloud-release-deploy.mjs) sets no version: it
requires the tag to match pb/VERSION, a clean tree and HEAD on the tag, so
the commit in MANIFEST is exactly what was tagged. Rebuilding without a new
version means the same tag again. The bundle (scripts/cloud-deploy-bundle.mjs)
is an allowlist (containers/, pb/pb_migrations, pb/pb_hooks,
pb/pb_public) and rejects links and special files. Nothing is pushed or
uploaded by these commands. Then, as root on d0b:
app deploy nobocloud cloud-v1.0.0. First-time setup:
app add nobocloud 10
editor /etc/apps/nobocloud/app.conf # DOMAIN=nobocloud.dencla.de REPO=https://zumcode.de/<owner>/nobo
install -m 0640 -o root -g nobocloud /dev/null /etc/apps/nobocloud/pb.env
app deploy nobocloud <tag>
Releases must be downloadable without login; otherwise fetch the bundle first
and run app deploy nobocloud <folder>.
After the first deploy, open the dashboard through a tunnel
(ssh -p 1505 -L 8090:127.0.0.1:11090 d0b, then http://localhost:8090/_/),
create the superuser and set Settings → Mail settings: SMTP
mail.d0b.de:465 (TLS), account sys-nobocloud. Check that Application
URL reads https://nobocloud.dencla.de (set by the migration), send a test
mail, then register once in nobo from start to finish. Without mail, nobody can
verify an account and therefore nobody can sync.
Moving from an old server: copy pb_data to /srv/nobocloud/pb_data, then
chown -R nobocloud: /srv/nobocloud. Secrets go into the .env files on the
server, never into the repository.
Updates: d0b rebuilds the images nightly with base-image patches. Renovate
(renovate.json) proposes PocketBase updates, recomputes PB_SHA256 via
scripts/pb-sha256.sh (the Renovate instance must allow that command), and
merges patches only once .forgejo/workflows/server.yml is green.
License
Copyright 2026 Dennis Burgermeister. Licensed under
GPL-3.0-or-later. Source files carry SPDX headers; REUSE.toml
covers generated files, fixtures and assets (reuse lint).
