Pronounced "hash" — a self-hosted, offline-first markdown knowledge base.
#ash lets you create, edit, and sync markdown documents across devices. It runs as a Docker container in your homelab and syncs with lightweight desktop clients that work fully offline.
Notes support Obsidian-style frontmatter tags, wiki-links, Mermaid diagrams, and a distraction-free focus mode.
The # in the name is the markdown heading character — the foundational symbol of any structured document.
| Component | Technology | Purpose |
|---|---|---|
| Server | Rust + Axum | REST API, web UI, sync engine |
| Frontend | Svelte | Shared UI for both web and desktop |
| Desktop | Tauri v2 | Lightweight native client with offline sync |
| Storage | Flat files | Markdown lives as plain .md files — no database |
| Config | TOML | Human-readable configuration |
services:
hash:
image: ghcr.io/ryantiger658/hash:latest
ports:
- "3535:3535"
volumes:
- hash-vault:/vault
- ./config.toml:/app/config.toml:ro
restart: unless-stopped
volumes:
hash-vault:Create a config.toml next to your compose file:
[auth]
api_key = "your-secret-key" # change this
[ui]
secondary_color = "#aaff00" # accent color (any CSS hex)
default_theme = "dark" # "light", "dark", or "system"Then start it:
docker compose up -dOpen http://your-server:3535 in your browser and enter your API key to log in.
#ash exposes an authenticated, stateless Streamable HTTP MCP server at https://your-server/mcp. Configure your MCP client with that URL and the same API key as a Bearer token. It provides search_notes and read_note; search results include a URL that opens the original note in #ash.
When deployed behind a reverse proxy, set the public URL so those source links are stable:
[server]
public_url = "https://notes.example.com"OKF v0.1 support is recognition-only: any ordinary Markdown note with leading YAML frontmatter and a non-empty type field is recognized as an OKF concept. index.md and log.md are treated as OKF reserved files. Existing notes are never changed or rejected, and no validate_okf MCP tool is exposed.
The web interface supports any standards-compliant OpenID Connect provider. Register this callback URL with the provider:
https://notes.example.com/api/auth/oidc/callback
When running without a config.toml, configure the server with environment variables:
environment:
HASH_API_KEY: "your-automation-api-key"
HASH_PUBLIC_URL: "https://notes.example.com"
HASH_OIDC_ISSUER: "https://identity.example.com/oidc"
HASH_OIDC_CLIENT_ID: "hash"
HASH_OIDC_CLIENT_SECRET: "replace-with-provider-secret"
HASH_OIDC_REDIRECT_URL: "https://notes.example.com/api/auth/oidc/callback"
HASH_OIDC_SCOPES: "openid profile email"The issuer must expose OpenID Connect discovery metadata. The browser uses Authorization Code with PKCE and receives an HttpOnly session cookie after the ID token is verified. Browser sessions last 24 hours and are cleared when the server restarts. HTTPS deployments use the host-only __Host-hash-session-v2 cookie; #ash automatically removes the legacy hash-session cookie during migration. The API key remains available for desktop sync, MCP clients, and administrator fallback access.
If you mount a config.toml, use the equivalent [auth] fields shown in config.example.toml; the project loads the config file instead of environment configuration.
- Go to Stacks → Add stack
- Paste the Docker Compose above
- Click Deploy the stack
Download the latest release for your platform from the Releases page:
| Platform | File |
|---|---|
| macOS (Apple Silicon + Intel) | hash_0.0.x_universal.dmg |
| Windows | hash_0.0.x_x64-setup.exe or .msi |
| Linux | hash_0.0.x_amd64.AppImage or .deb |
Because the app is not notarized with Apple, macOS Gatekeeper will block it on first launch:
"hash.app" cannot be opened because Apple cannot verify it is free of malware.
macOS Sequoia (15+):
- Click Done to dismiss the dialog
- Open System Settings → Privacy & Security
- Scroll down to the Security section — you will see "hash.app was blocked"
- Click Open Anyway → enter your password → click Open Anyway again
macOS Ventura / Sonoma (13–14):
- Right-click (or Control-click)
hash.app→ Open - Click Open in the confirmation dialog
Alternative (any macOS version) — Terminal:
xattr -cr /Applications/hash.appYou only need to do this once. Subsequent launches open normally.
- Open the app — you will see the
#ashlogin screen - Enter your Server URL (e.g.
http://192.168.1.100:3535) - Enter your API Key from
config.toml - Click Connect — your notes will appear
The server icon (⬜) in the sidebar footer shows sync status. Click it to configure the background sync:
| Color | Meaning |
|---|---|
| Grey | Not configured — click to set up |
| Green | Connected and synced |
| Amber | Connected, changes pending |
| Red | Server unreachable |
3535 — because ASCII 35 is #, and 3535 = ##, the H2 markdown heading.
#ash supports light and dark mode with a configurable accent color. Set your preferred accent in config.toml:
[ui]
secondary_color = "#6366f1" # any valid CSS hex color
default_theme = "dark" # "light", "dark", or "system"#ash/
server/ # Rust server (Axum)
ui/ # Svelte frontend (shared by server + desktop)
desktop/ # Tauri v2 desktop client
docker/ # Dockerfile + docker-compose.yml
- Rust (stable) + Cargo — install via rustup.rs
- Node.js 20+
- Tauri v2 CLI:
cargo install tauri-cli --version "^2"
cd server
cargo runcd ui
npm install
npm run devcd desktop
npm install
npm run tauri devmake ciAGPL-3.0 — if you run a modified version as a network service, you must publish your changes.