A simple, self-hosted note-taking application similar to Obsidian, built with Kotlin, Ktor, and HTMX.
- Markdown Notes: All notes stored as
.mdfiles with YAML frontmatter support - Split Editor: CodeMirror editor with live preview
- Wiki Links:
[[Note Name]]syntax for linking notes - Full-Text Search: SQLite-powered search across all notes
- Folder Organization: Support for nested folders and subdirectories
- GitHub OAuth: Secure authentication via oauth2-proxy
- Per-User Storage: Each user has their own private note space
- Backend: Kotlin + Ktor 3.3.0
- Frontend: FreeMarker templates + HTMX + CodeMirror
- Search: SQLite with LIKE-based full-text search
- Markdown: CommonMark with YAML frontmatter parsing
- Auth: oauth2-proxy with GitHub provider
- Container: Podman Compose
- Java 25+ (or compatible JDK)
- Podman and Podman Compose
- GitHub OAuth App (for authentication)
- Go to GitHub Developer Settings
- Click "New OAuth App"
- Fill in:
- Application Name: Notes App
- Homepage URL:
http://localhost:8000 - Authorization callback URL:
http://localhost:8000/oauth2/callback
- Click "Register application"
- Copy the Client ID and generate a Client Secret
git clone <repository-url>
cd notes-appcp .env.example .envEdit .env with your GitHub OAuth credentials:
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
OAUTH2_PROXY_COOKIE_SECRET=$(openssl rand -base64 32)./gradlew buildpodman-compose up --buildThe application will be available at:
- Notes App: http://localhost:8000
- Health Check: http://localhost:8000/health
For development, you can run the app directly:
# Set environment variable
export NOTES_PATH="./notes"
export GITHUB_CLIENT_ID="your-client-id"
export GITHUB_CLIENT_SECRET="your-client-secret"
# Run the application
./gradlew installDist
./build/install/notes-app/bin/notes-appNote: Without oauth2-proxy, you'll need to add the X-Auth-Request-User header manually for testing:
curl -H "X-Auth-Request-User: testuser" http://localhost:8080/notes-app/
├── src/main/kotlin/com/notes/
│ ├── Application.kt # Main Ktor application
│ ├── model/
│ │ └── Note.kt # Data models
│ ├── services/
│ │ ├── NoteService.kt # File operations
│ │ └── SearchService.kt # SQLite search
│ └── util/
│ └── MarkdownParser.kt # Markdown & frontmatter
├── src/main/resources/
│ ├── application.conf # Ktor configuration
│ └── templates/ # FreeMarker templates
├── compose.yaml # Podman Compose
├── Dockerfile # Container build
└── notes/ # Note storage (created at runtime)
- Click "+ New Note" in the sidebar
- Enter a title and optional tags
- The note is created with YAML frontmatter
Notes support YAML frontmatter:
---
title: My Note Title
tags:
- kotlin
- jvm
created: 2024-01-15T10:00:00Z
---
# My Note Title
Content here...Link to other notes using double brackets:
See [[Another Note]] for details.Use the search bar in the sidebar to search across all your notes.
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Main application (requires auth) |
/notes |
GET | List all notes |
/notes/{path} |
GET | View/edit specific note |
/notes/new |
POST | Create new note |
/notes/{path} |
PUT | Update note |
/notes/{path} |
DELETE | Delete note |
/search?q= |
GET | Search notes |
/tree |
GET | Folder tree structure |
/health |
GET | Health check (no auth) |
| Variable | Description | Default |
|---|---|---|
NOTES_PATH |
Path to notes storage | ./notes |
GITHUB_CLIENT_ID |
GitHub OAuth Client ID | - |
GITHUB_CLIENT_SECRET |
GitHub OAuth Secret | - |
OAUTH2_PROXY_COOKIE_SECRET |
Session encryption key | - |
Edit src/main/resources/application.conf:
ktor {
application {
modules = [com.notes.ApplicationKt.module]
}
deployment {
port = 8080
}
}
notes {
path = ${NOTES_PATH ?: "./notes"}
}./gradlew test- Ensure the
notes/{username}/directory exists and contains.mdfiles - Check file permissions
- Search requires at least 2 characters
- The SQLite database is created automatically on first run
- Verify callback URL matches exactly:
http://localhost:8000/oauth2/callback - Ensure GitHub OAuth app is registered correctly
MIT