A self-hosted wiki with Spaces → Documents → Pages hierarchy, inspired by Confluence but lightweight. Content is stored as Markdown files; metadata lives in SQLite (or Postgres). The backend is a JSON API served by Axum; the frontend is a Leptos WASM SPA.
cp .env.example .env # edit SESSION_SECRET
cargo run # builds + runs the API server on :3000Then open http://localhost:3000. The first run creates an admin user
automatically — see Administration.
All configuration is via environment variables (or .env):
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
sqlite:ruki.db |
SQLite path or Postgres connection string |
CONTENT_DIR |
./content |
Directory for Markdown page files |
SESSION_SECRET |
— | Key for signing session cookies |
PORT |
3000 |
HTTP listen port |
ADMIN_USERNAME |
admin |
Initial admin username (first run only) |
ADMIN_PASSWORD |
admin |
Initial admin password (first run only) |
RUST_LOG |
— | Log level (ruki=info, tower_sessions=debug) |
DATABASE_URL=sqlite:ruki.db?mode=rwccargo run --features postgres --no-default-featuresDATABASE_URL=postgres://user:pass@localhost:5432/rukiMigrations run automatically on server start.
The frontend is a Leptos WASM SPA. Pre-built files in frontend/dist/ are
served automatically by the backend at /.
cd frontend
trunk serve # http://localhost:8080, proxies /api → :3000The API server must be running separately on port 3000. CORS is configured
to allow localhost:8080.
cd frontend && trunk buildOutput goes to frontend/dist/. Restart the backend to pick up the new build.
sudo useradd -r -s /bin/false ruki
sudo mkdir -p /opt/ruki
sudo cp -r . /opt/ruki
cd /opt/ruki && cargo build --release
sudo cp contrib/ruki.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now rukiSee contrib/ruki.service for the unit file.
Spaces → Documents → Pages
- Spaces — top-level sections (e.g. "Engineering", "HR")
- Documents — groupings within a space (e.g. "Playbooks")
- Pages — individual wiki pages written in Markdown
Page content is stored as .md files at:
content/{space_slug}/{doc_slug}/{page_slug}.md
The frontend has a light/dark theme toggle in the sidebar, persisted to
localStorage (defaults to system prefers-color-scheme).
Ruki uses a hierarchical permission model. Each permission level inherits the capabilities of the levels below it:
| Level | Can view | Can create/edit content | Can manage ACLs |
|---|---|---|---|
read |
✅ | ❌ | ❌ |
write |
✅ | ✅ | ❌ |
admin |
✅ | ✅ | ✅ |
Resolution order (highest wins):
- Space-specific user ACL
- Space-specific group ACL (any of the user's groups)
- Global user ACL
- Global group ACL
- If none match → no access
Admin group: members of the admin group always have admin permission on
every space, regardless of other ACLs.
Space creator: the user who created a space always retains ACL management access for that space.
When no ACLs exist at all, only admin group members can see or do anything. Anonymous (unauthenticated) users have no access.
Per-space: visit the space's Settings page (visible when you have admin permission on that space). Add or remove entries by user ID or group ID.
Global defaults: Admin → Global Access Control. These apply to all spaces unless overridden by a space-specific ACL.
Available to global admins at Admin → Backup & Restore.
- Backup — downloads a
.tar.gzcontaining a clean copy of the database (VACUUM INTO) and all content files - Restore — upload a previous
.tar.gz, replaces the database and content files atomically
All endpoints are at /api/ and accept/produce JSON. Authentication is via
cookie-based sessions (set by login, sent automatically by the browser).
| Method | Path | Description |
|---|---|---|
GET |
/api/auth/me |
Current user, or null |
POST |
/api/auth/login |
Login — {"username","password"} |
POST |
/api/auth/logout |
Clear session |
| Method | Path | Description |
|---|---|---|
GET |
/api/spaces |
List accessible spaces |
POST |
/api/spaces |
Create space (admin group only) |
GET |
/api/spaces/{slug} |
Get space with user's effective permission |
PUT |
/api/spaces/{slug} |
Update space (admin permission) |
DELETE |
/api/spaces/{slug} |
Delete space (admin permission) |
| Method | Path | Description |
|---|---|---|
GET |
/api/spaces/{slug}/documents |
List documents in space |
POST |
/api/spaces/{slug}/documents |
Create document (write permission) |
GET |
/api/spaces/{space}/{doc} |
Get document |
PUT |
/api/spaces/{space}/{doc} |
Update document (write permission) |
DELETE |
/api/spaces/{space}/{doc} |
Delete document (write permission) |
| Method | Path | Description |
|---|---|---|
GET |
/api/spaces/{s}/{d}/pages |
List pages in document |
POST |
/api/spaces/{s}/{d}/pages |
Create page (write permission) |
GET |
/api/spaces/{s}/{d}/{p} |
Get page (includes rendered HTML) |
PUT |
/api/spaces/{s}/{d}/{p} |
Update page (write permission) |
DELETE |
/api/spaces/{s}/{d}/{p} |
Delete page (write permission) |
| Method | Path | Description |
|---|---|---|
GET |
/api/spaces/{slug}/acls |
List space ACLs |
POST |
/api/spaces/{slug}/acls |
Create space ACL entry |
DELETE |
/api/spaces/{slug}/acls/{id} |
Delete space ACL entry |
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/acls |
List global ACLs |
POST |
/api/admin/acls |
Create global ACL entry |
DELETE |
/api/admin/acls/{id} |
Delete global ACL entry |
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/users |
List users |
POST |
/api/admin/users |
Create user |
GET |
/api/admin/users/{id} |
Get user with groups |
PUT |
/api/admin/users/{id} |
Update user |
DELETE |
/api/admin/users/{id} |
Delete user |
GET |
/api/admin/groups |
List groups |
POST |
/api/admin/groups |
Create group |
GET |
/api/admin/groups/{id} |
Get group |
PUT |
/api/admin/groups/{id} |
Update group |
DELETE |
/api/admin/groups/{id} |
Delete group |
GET |
/api/admin/backup |
Download backup archive |
POST |
/api/admin/restore |
Upload and restore backup |
When the database is empty the server creates an initial admin user using the
ADMIN_USERNAME / ADMIN_PASSWORD env vars (defaults: admin / admin).
Change the password on first login.
Logged-in admin users see an Admin link in the sidebar. From there you can:
- Users — create, edit, delete users and assign group memberships
- Groups — create and delete groups
- Global Access Control — set default permissions for users/groups
- Backup & Restore — download and restore full backups
The admin group is seeded automatically. Users in this group have full
administrative access.
Only admins can create users. There is no public registration. To add a user:
- Log in as an admin
- Go to Admin → Manage Users → + New User
- Set username, display name, password, and optionally assign groups
All page content is stored as plain Markdown files on disk. You can:
- Edit files directly in
content/(changes reflect on next page load) - Version control them with git
- Back them up independently from the database
# Backend
cargo build
cargo run
# Frontend (separate terminal)
cd frontend
trunk serve
# Run both at once
RUST_LOG=ruki=info cargo run & cd frontend && trunk serve