- Ruby 46.2%
- HTML 28.7%
- CSS 14.8%
- JavaScript 8.6%
- Dockerfile 1.6%
- Other 0.1%
bin/rails test:system passed the literal 'test/system' path to the runner, which requires it as a file when the directory is missing, raising LoadError. Add the test/system directory (with .keep) and the standard test/application_system_test_case.rb so the task globs test/system/**/*_test.rb, runs 0 tests, and exits 0. Ready for system tests to be added later. |
||
|---|---|---|
| .github | ||
| app | ||
| bin | ||
| config | ||
| db | ||
| lib/tasks | ||
| log | ||
| public | ||
| script | ||
| storage | ||
| test | ||
| tmp | ||
| vendor | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .rubocop.yml | ||
| .ruby-version | ||
| config.ru | ||
| Dockerfile | ||
| Gemfile | ||
| Gemfile.lock | ||
| Rakefile | ||
| README.md | ||
Ollama Chat
A small, minimalistic multi-user web chat for an Ollama server on your network. Built with Ruby on Rails 8, Turbo/Stimulus, and SQLite.
- Multi-user via Authelia — the app sits behind your existing Authelia (or
any compatible reverse proxy). It trusts the identity headers Authelia
injects (
Remote-User,Remote-Email, …) and auto-provisions users. There is no password code in the app. - Chat history — every chat and message is persisted. You own your chats.
- Shared chat rooms — share a chat with other users on the same platform by username. Everyone in the room (1+ users + the AI) sees messages stream live.
- Ollama web search — toggle per-chat. Sends Ollama's native
searchtool; the model can browse and cite sources (requires the Ollama server to have a search provider configured — see below). - Thinking/reasoning — optional per-chat toggle to show the model's reasoning trace alongside the answer.
Architecture
Browser ──(Authelia headers)──▶ Rails (Puma) ──▶ SQLite (primary)
│
├── OllamaClient ──HTTP/NDJSON──▶ Ollama /api/chat
│
└── GenerateJob (Solid Queue) ──Turbo Streams──▶ room members
| Concern | Dev | Production |
|---|---|---|
| Jobs | :async |
Solid Queue (bin/jobs) |
| Cable/pubsub | :async |
Solid Cable (SQLite) |
| Cache | :memory_store |
Solid Cache (SQLite) |
| DB | SQLite | SQLite (primary/queue/cache/cable) |
In production the Solid Queue worker is a separate process, so broadcasts from the worker reach web clients through Solid Cable (SQLite-backed pubsub).
Requirements
- Ruby 3.3+, Bundler
- An Ollama server reachable from the app (e.g.
http://10.0.20.58:11434) - Authelia (or compatible) in front of the app, injecting identity headers
First run (development)
bundle install
bin/rails db:prepare
export OLLAMA_URL=http://10.0.20.58:11434
export OLLAMA_MODEL=qwen3.8:27b-mlx
export DEV_REMOTE_USER=alice # dev-only fallback user (skips Authelia)
bin/rails server
Open http://localhost:3000. With DEV_REMOTE_USER set you're logged in as
alice without Authelia — handy for local hacking. Remove it in production.
Configuration (environment variables)
| Variable | Default | Purpose |
|---|---|---|
OLLAMA_URL |
http://localhost:11434 |
Base URL of the Ollama server |
OLLAMA_MODEL |
llama3.2 |
Default model for new chats (overridable per chat) |
OLLAMA_CONNECT_TIMEOUT |
5 |
Seconds to connect to Ollama |
OLLAMA_READ_TIMEOUT |
600 |
Seconds before a streaming response times out |
AUTHELIA_USER_HEADER |
Remote-User |
Header carrying the username |
AUTHELIA_EMAIL_HEADER |
Remote-Email |
Header carrying the email |
AUTHELIA_NAME_HEADER |
Remote-Name |
Header carrying the display name |
AUTHELIA_GROUPS_HEADER |
Remote-Groups |
Header carrying groups |
TRUSTED_PROXIES |
empty | Comma-separated CIDRs allowed to set identity headers. When empty, all sources are trusted (fine only if the app is only reachable via the proxy). Set this to your proxy's IP(s) to prevent header spoofing if the app is ever exposed directly. |
DEV_REMOTE_USER |
empty | Dev-only: username to impersonate when no header is present |
Authelia setup
Authelia authenticates the user and forwards identity headers to the app. A
typical reverse-proxy config (Caddy example, Authelia via forward_auth):
chat.example.com {
forward_auth authelia:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Email Remote-Name Remote-Groups
}
reverse_proxy ollama_chat:3000
}
The app must only be reachable through the proxy. To harden it, set
TRUSTED_PROXIES to the proxy's address so identity headers are ignored when a
request does not come from the proxy:
export TRUSTED_PROXIES=10.0.0.5,10.0.0.0/24
Ollama web search
The "Enable Ollama web search" toggle sends Ollama's built-in search tool
(tools: [{"type":"search"}]). Web search actually happening depends on the
Ollama server having a search provider configured — e.g. Ollama Cloud
search, or a self-hosted provider such as SearXNG/Tavily set via the server's
OLLAMA_SEARCH_PROVIDER/OLLAMA_SEARCH_API_KEY environment variables
(see the Ollama web search docs).
If a particular model rejects the search tool, the client automatically retries without it so the chat keeps working.
Production
export RAILS_ENV=production
export OLLAMA_URL=http://10.0.20.58:11434
export OLLAMA_MODEL=qwen3.8:27b-mlx
export TRUSTED_PROXIES=<your proxy ip(s)>
bin/rails db:prepare # creates primary/queue/cache/cable SQLite DBs
bin/rails server # web (Puma + Thruster on :80)
bin/jobs # Solid Queue worker (in another process/container)
The included Dockerfile builds a production image (runs Thruster on port 80).
Run the web container and a second container from the same image with command
bin/jobs for the worker. Mount storage/ as a persistent volume.
Using the app
- New chat — click + New chat (sidebar) or use the form on the home page. Pick a model; optionally add a system prompt, enable web search/thinking.
- Settings — in a chat, the ⚙ button edits title/model/web-search/thinking/ system prompt (owner only).
- Share — the 👥 button (owner only) adds members by username. Shared chats are live for all members: everyone sees user messages and the streamed AI reply. Non-owners see a "shared by …" badge and can leave the chat.
- Delete — owner can delete a chat (removes it for everyone).
Project layout
app/
controllers/ concerns/authentication.rb, chats/messages/memberships
jobs/ generate_job.rb # streams from Ollama → Turbo broadcasts
lib/ authelia.rb, ollama_client.rb, chat_broadcaster.rb
models/ user, chat, chat_membership, message
channels/ application_cable/connection.rb # ActionCable identity from headers
views/ chats/, messages/
config/ cable.yml, database.yml, environments/production.rb