Skip to content

Repository files navigation

Dark Oak

Dark Oak

Dark Oak is a local-first, single-binary Markdown notebook intended to replace Obsidian for users who want a small, auditable, dependency-light alternative. It runs a local web server on 127.0.0.1, serves a minimal SvelteKit UI, edits Markdown files in CodeMirror 6 with a live preview, and auto-commits every save to a local git repository.

The goal is to keep the attack surface small, the dependency set small, and the codebase small.

Goals

  • Single static binary to run; no daemon, no install step beyond copying the binary.
  • Markdown files are the only source of truth and live on the filesystem.
  • Every save is a git commit. History is plain git log.
  • The interface is minimal: a file tree on the left, an editor and a live preview on the right.
  • Renames and moves are tracked correctly in git (git mv).

Non-Goals

  • Cloud sync, mobile apps, real-time collaboration, accounts.
  • A plugin system or theming engine.
  • Non-Markdown file editing.
  • Network exposure. The server binds 127.0.0.1 only.

Features

  • File tree in a left side-pane (toggleable). Files and folders can be created, renamed, moved, and deleted. Drag-and-drop reorders within the tree, and a per-item menu (⋯) offers rename / delete / new file / new folder. ⊞ All / ⊟ All expand or collapse every folder.
  • Markdown editor in the main pane, using CodeMirror 6 with a Markdown mode and syntax highlighting in fenced code blocks.
  • Live preview pane to the right of the editor, rendered client-side with marked and sanitized with DOMPurify (100 ms debounce).
  • Ctrl+S saves the current file. Every save is also a git commit, with message update: <relative path>. The footer shows the most recent commit hash. Renaming via the title bar uses git mv, so git log --follow works across renames.
  • Wikilinks[[note name]] links are highlighted in the editor and rendered as clickable links in the preview. Links resolve relative to the current file first, then the vault root. Unresolved links are styled distinctly.
  • Full-text search (Ctrl+Shift+F) backed by git grep, returning matching lines across the whole vault.
  • File switcher (Ctrl+P) for fuzzy-jumping to any note by name.
  • Dark / light theme (default dark, matching the project name), persisted in localStorage. Side-pane visibility is also persisted.
  • Footer showing the most recent commit hash.

Architecture

  • Backend: Go, stdlib net/http only. No web framework. os/exec is used to call the system git binary for commits and moves. SvelteKit static assets are embedded via //go:embed and served by the same binary.
  • Frontend: Svelte 5 (runes) + SvelteKit, built to a static bundle. CodeMirror 6 is the editor. marked + DOMPurify render the preview.
  • Storage: a vault directory (a plain folder) is passed via --vault. On first run, Dark Oak runs git init in it if needed and creates an initial commit. All subsequent saves commit on top.

Project layout

darkoak/
├── README.md
├── LICENSE
├── go.mod
├── main.go                    # flag parsing, embed, server bootstrap
├── Makefile
├── darkoak.service            # optional systemd user unit
├── internal/
│   └── server/                # http handlers, embed, routes, git, path
│       ├── handlers.go        # API routes + security headers + SPA fallback
│       ├── git.go             # EnsureRepo, commitFile, movePath, removePath, gitLog, gitGrep
│       └── path.go            # resolvePath — vault-relative, no traversal, no symlink escape
├── frontend/
│   ├── src/
│   │   ├── routes/            # SvelteKit pages (+layout, +page)
│   │   └── lib/               # FileTree, Editor, Preview, Switcher, Search, store, api
│   └── tests/                 # vitest
├── scripts/
│   └── smoke.sh               # end-to-end smoke test
└── .github/
    └── workflows/
        └── build.yml          # CI: test + cross-compile + release on tags

API

Method Path Body Effect
GET /api/tree JSON tree of the vault
GET /api/file?path= Raw text content of a .md file
PUT /api/file?path= text/plain Write + auto-commit
POST /api/file?path= Create empty .md file
POST /api/folder?path= Create folder
POST /api/move {from, to} git mv (file or folder)
DELETE /api/file?path= git rm (recursive for folders)
GET /api/log?limit= Recent commits
GET /api/search?q= Full-text search via git grep
GET /api/status Whether the vault has a git repo
POST /api/init git init + initial commit (idempotent)

Security

  • The server binds 127.0.0.1 only. There is no auth; the threat model is a single user on a single machine.
  • All file paths are validated relative to the vault root. .. segments are rejected, and any symlink is resolved and re-validated.
  • The server only accepts .md files for write/create. Other files in the vault are listed in the tree but not editable.
  • Markdown is rendered client-side from a vetted library (marked) and sanitized with DOMPurify before insertion.
  • A strict CSP is emitted by SvelteKit as a <meta> tag in index.html, with auto-computed hashes for inline scripts. No remote loads. X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer are set by the Go server.

Build & run

Prerequisites: Go 1.22+, Node 20+, git on $PATH.

make build                      # builds frontend, embeds it, builds ./darkoak
./darkoak --vault ~/notes
# open http://127.0.0.1:7331/

Flags:

  • --vault <path> vault directory (default ./vault).
  • --addr <host:port> listen address (default 127.0.0.1:7331).

Development (two terminals):

# terminal 1
make dev-go          # go run .

# terminal 2
make dev-fe          # vite dev on :5173, proxies /api to the Go server

Smoke test (builds, runs the binary, exercises the API, prints git log):

make build && bash scripts/smoke.sh

Running as a systemd user service

An optional darkoak.service unit is included for running Dark Oak as a systemd user service. Install the binary and register the unit:

install -m 0755 darkoak ~/.local/bin/darkoak
install -m 0644 darkoak.service ~/.config/systemd/user/darkoak.service
systemctl --user daemon-reload
systemctl --user enable --now darkoak

The unit runs darkoak --vault ~/notes --addr 127.0.0.1:7331. Edit the ExecStart line to point at a different vault if needed.

Testing

  • Go: go test ./... — table-driven tests for path validation, handlers, and git operations against a temp git repo.
  • Frontend: npm test -- --run --prefix frontend — Vitest unit tests for FileTree, the editor wrapper, and the store. (Drop --run for watch mode.)
  • All: make test runs both suites.

License

MIT — see LICENSE.

About

Local-first, single-binary Markdown notebook

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages