Merge pull request #7 from Dvorinka/docs/readme-screenshots
docs: README refresh with logo and app screenshots
No files matched your search
@@ -1,6 +1,56 @@
|
|||||||
# Primora
|
<p align="center">
|
||||||
|
<img src="./primora.svg" alt="Primora" width="120">
|
||||||
|
</p>
|
||||||
|
|
||||||
A self-hosted backend platform for building products — auth, Postgres-backed APIs, object storage, JSON document collections, API keys, and a full audit log behind one dashboard. Inspired by the UX of Appwrite and Supabase, with no hosted tier: your data stays on your hardware.
|
<h1 align="center">Primora</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Self-hosted backend platform for building products.<br>
|
||||||
|
Auth, Postgres-backed APIs, object storage, and audit logs on your own hardware.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#quick-start">Quick Start</a> ·
|
||||||
|
<a href="QUICK_START.md">Documentation</a> ·
|
||||||
|
<a href="https://github.com/Dvorinka/Primora/releases">Releases</a> ·
|
||||||
|
<a href="CONTRIBUTING.md">Contributing</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/Dvorinka/Primora/actions/workflows/ci.yml"><img src="https://github.com/Dvorinka/Primora/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
||||||
|
<a href="https://github.com/Dvorinka/Primora/releases"><img src="https://img.shields.io/github/v/release/Dvorinka/Primora" alt="Release"></a>
|
||||||
|
<a href="LICENSE"><img src="https://img.shields.io/github/license/Dvorinka/Primora" alt="License"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## What is Primora?
|
||||||
|
|
||||||
|
Primora is a self-hosted backend platform: organizations, projects, auth,
|
||||||
|
S3-style object storage, JSON document collections, scoped API keys, and a
|
||||||
|
full audit log behind one dashboard. Inspired by the UX of Appwrite and
|
||||||
|
Supabase, with no hosted tier - your data stays on your hardware.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
| Overview | Storage |
|
||||||
|
|:-:|:-:|
|
||||||
|
|  |  |
|
||||||
|
| **Telemetry** | **Collections** |
|
||||||
|
|  |  |
|
||||||
|
| **Automation** | **Audit log** |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Organizations & projects** - top-level workspaces containing projects, members, and scoped roles (`owner`/`admin`/`member` org roles, `admin`/`developer`/`viewer` project roles).
|
||||||
|
- **Auth** - email/password plus optional GitHub, Google, Discord, and Microsoft OAuth via Better Auth. JWTs minted by the auth service are verified by the Go API against JWKS.
|
||||||
|
- **Storage** - S3-style buckets and objects backed by your local filesystem, with public/private visibility and downloadable URLs.
|
||||||
|
- **Collections** - schema-flexible JSON documents stored in Postgres JSONB.
|
||||||
|
- **API keys** - `prm_<prefix>_<secret>` credentials; secrets are shown once.
|
||||||
|
- **Audit log** - every mutating request recorded with actor, resource, request ID, and timestamp; CSV/JSON export from the dashboard.
|
||||||
|
- **Generated client** - the TypeScript client is generated from `apps/backend/openapi/openapi.yaml`, so the API contract is the source of truth.
|
||||||
|
- **Demo mode** - a fully client-side workspace (`?demo=true` or `VITE_DEMO_MODE=true`) for trying the UI without a backend.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Browser ──▶ Nginx ──▶ Frontend (SolidJS + Vite)
|
Browser ──▶ Nginx ──▶ Frontend (SolidJS + Vite)
|
||||||
@@ -10,18 +60,16 @@ Browser ──▶ Nginx ──▶ Frontend (SolidJS + Vite)
|
|||||||
──▶ Local filesystem object storage
|
──▶ Local filesystem object storage
|
||||||
```
|
```
|
||||||
|
|
||||||
## What you get
|
| Service | Port | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| nginx | 80 (`NGINX_PORT`) | Reverse proxy - only public entrypoint |
|
||||||
|
| backend | 8080 (internal) | Go + Gin API |
|
||||||
|
| auth | 3001 (internal) | Better Auth service |
|
||||||
|
| postgres | 5432 (`POSTGRES_PORT`) | Primary datastore |
|
||||||
|
| dragonfly | 6379 (`DRAGONFLY_PORT`) | Redis-compatible cache |
|
||||||
|
| mailpit (dev) | 8025 (`MAILPIT_HTTP_PORT`) | Email capture with `docker-compose.dev.yml` |
|
||||||
|
|
||||||
- **Organizations & projects** — top-level workspaces containing projects, members, and scoped roles (`owner`/`admin`/`member` org roles, `admin`/`developer`/`viewer` project roles).
|
## Quick Start
|
||||||
- **Auth** — email/password plus optional GitHub, Google, Discord, and Microsoft OAuth via Better Auth. JWTs minted by the auth service are verified by the Go API against JWKS.
|
|
||||||
- **Storage** — S3-style buckets and objects backed by your local filesystem, with public/private visibility and downloadable URLs.
|
|
||||||
- **Collections** — schema-flexible JSON documents stored in Postgres JSONB.
|
|
||||||
- **API keys** — `prm_<prefix>_<secret>` credentials; secrets are shown once.
|
|
||||||
- **Audit log** — every mutating request recorded with actor, resource, request ID, and timestamp; CSV/JSON export from the dashboard.
|
|
||||||
- **Generated client** — the TypeScript client is generated from `apps/backend/openapi/openapi.yaml`, so the API contract is the source of truth.
|
|
||||||
- **Demo mode** — a fully client-side workspace (`?demo=true` or `VITE_DEMO_MODE=true`) for trying the UI without a backend.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
Prerequisites: Docker with the Compose plugin.
|
Prerequisites: Docker with the Compose plugin.
|
||||||
|
|
||||||
@@ -32,7 +80,7 @@ curl -fsSL https://raw.githubusercontent.com/Dvorinka/Primora/master/install.sh
|
|||||||
```
|
```
|
||||||
|
|
||||||
Installs into `./primora`, generates secrets, and starts the stack on port 80.
|
Installs into `./primora`, generates secrets, and starts the stack on port 80.
|
||||||
Non-interactive — configure with env vars:
|
Non-interactive - configure with env vars:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DOMAIN=example.com NGINX_PORT=8080 PRIMORA_DIR=/opt/primora bash install.sh
|
DOMAIN=example.com NGINX_PORT=8080 PRIMORA_DIR=/opt/primora bash install.sh
|
||||||
@@ -62,11 +110,10 @@ docker pull ghcr.io/dvorinka/primora-{backend,auth,frontend}:latest
|
|||||||
docker compose up -d --no-build
|
docker compose up -d --no-build
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open `http://localhost`. For local email capture, add the dev overlay
|
Then open `http://localhost` (or append `?demo=true` to explore a seeded
|
||||||
|
workspace without signing up). For local email capture, add the dev overlay
|
||||||
(`-f docker-compose.dev.yml`) and Mailpit is at `http://localhost/mailpit/`.
|
(`-f docker-compose.dev.yml`) and Mailpit is at `http://localhost/mailpit/`.
|
||||||
|
|
||||||
If ports `5432` or `6379` are already in use on your machine, set `POSTGRES_PORT` and `DRAGONFLY_PORT` in `.env` — only the host bindings change.
|
|
||||||
|
|
||||||
### Useful commands
|
### Useful commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -77,7 +124,7 @@ docker compose down # stop (data persists in volumes)
|
|||||||
docker compose down -v # stop and delete all data
|
docker compose down -v # stop and delete all data
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verification
|
### Verify the install
|
||||||
|
|
||||||
| Check | Endpoint |
|
| Check | Endpoint |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -87,6 +134,24 @@ docker compose down -v # stop and delete all data
|
|||||||
| Auth health | `http://localhost/auth-health` |
|
| Auth health | `http://localhost/auth-health` |
|
||||||
| OpenAPI spec | `http://localhost/api/v1/openapi.yaml` |
|
| OpenAPI spec | `http://localhost/api/v1/openapi.yaml` |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All configuration lives in `.env` (see `.env.example` for the full list):
|
||||||
|
|
||||||
|
| Variable | Default | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `NGINX_PORT` | `80` | Public HTTP port |
|
||||||
|
| `POSTGRES_PORT` | `5432` | Host binding for Postgres |
|
||||||
|
| `DRAGONFLY_PORT` | `6379` | Host binding for Dragonfly |
|
||||||
|
| `JWT_SECRET` | - | Signs API tokens (required) |
|
||||||
|
| `BETTER_AUTH_SECRET` | - | Signs auth sessions (required) |
|
||||||
|
| `COOKIE_DOMAIN` | `localhost` | Cookie scope for auth sessions |
|
||||||
|
| `PRIMORA_ENCRYPTION_KEY` | - | AES-256 key encrypting secrets at rest |
|
||||||
|
|
||||||
|
Only host bindings change when you move ports - the internal network stays
|
||||||
|
the same. OAuth providers, SMTP, and storage limits are documented in
|
||||||
|
`.env.example` and [QUICK_START.md](QUICK_START.md).
|
||||||
|
|
||||||
## Local development
|
## Local development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -116,11 +181,11 @@ Supports session sign-in (`primora login`) and API-key mode (`primora login --ap
|
|||||||
|
|
||||||
## Desktop & phone
|
## Desktop & phone
|
||||||
|
|
||||||
- **Desktop** — `apps/desktop` is a Tauri 2 shell (Linux/Windows/macOS). First
|
- **Desktop** - `apps/desktop` is a Tauri 2 shell (Linux/Windows/macOS). First
|
||||||
run asks for your deployment URL, then renders the same dashboard in a
|
run asks for your deployment URL, then renders the same dashboard in a
|
||||||
webview with a system-tray presence. Nothing is bundled; your server does the
|
webview with a system-tray presence. Nothing is bundled; your server does the
|
||||||
work. See [apps/desktop/README.md](apps/desktop/README.md).
|
work. See [apps/desktop/README.md](apps/desktop/README.md).
|
||||||
- **Phone / PWA** — the dashboard ships a web manifest and service worker.
|
- **Phone / PWA** - the dashboard ships a web manifest and service worker.
|
||||||
Open your deployment in a mobile browser and "Add to Home Screen"; the shell
|
Open your deployment in a mobile browser and "Add to Home Screen"; the shell
|
||||||
is precached, API traffic always goes to the network. Chromium desktop
|
is precached, API traffic always goes to the network. Chromium desktop
|
||||||
browsers get an in-app install banner.
|
browsers get an in-app install banner.
|
||||||
@@ -141,9 +206,9 @@ apps/
|
|||||||
backend/ Go + Gin API, sqlc, migrations, object storage
|
backend/ Go + Gin API, sqlc, migrations, object storage
|
||||||
auth/ Better Auth on Hono (sessions, JWT, OAuth, mail)
|
auth/ Better Auth on Hono (sessions, JWT, OAuth, mail)
|
||||||
frontend/ SolidJS + Tailwind dashboard
|
frontend/ SolidJS + Tailwind dashboard
|
||||||
cli/ `primora` CLI — login, context, projects, buckets, objects, keys, audit
|
cli/ `primora` CLI - login, context, projects, buckets, objects, keys, audit
|
||||||
mcp/ `@primora/mcp` — MCP stdio server exposing `primora_*` tools
|
mcp/ `@primora/mcp` - MCP stdio server exposing `primora_*` tools
|
||||||
desktop/ Tauri 2 shell — connects to a deployment URL, tray presence
|
desktop/ Tauri 2 shell - connects to a deployment URL, tray presence
|
||||||
packages/
|
packages/
|
||||||
api-client/ OpenAPI-generated TypeScript client (do not hand-edit)
|
api-client/ OpenAPI-generated TypeScript client (do not hand-edit)
|
||||||
shared-types/
|
shared-types/
|
||||||
@@ -153,10 +218,17 @@ scripts/ setup.sh, verify-production-ready.sh
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [QUICK_START.md](QUICK_START.md) — setup, configuration, troubleshooting
|
- [QUICK_START.md](QUICK_START.md) - setup, configuration, troubleshooting
|
||||||
- [project_backend.md](project_backend.md) / [project_frontend.md](project_frontend.md) — original design specs
|
- [project_backend.md](project_backend.md) / [project_frontend.md](project_frontend.md) - original design specs
|
||||||
- [CONTRIBUTING.md](CONTRIBUTING.md) — development workflow
|
- `apps/backend/openapi/openapi.yaml` - API contract
|
||||||
- `apps/backend/openapi/openapi.yaml` — API contract
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 83 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<rect width="64" height="64" rx="14" fill="#f4693a"/>
|
||||||
|
<g fill="#ffffff">
|
||||||
|
<rect x="15" y="14" width="34" height="10" rx="5"/>
|
||||||
|
<rect x="15" y="27" width="23" height="10" rx="5" opacity="0.7"/>
|
||||||
|
<rect x="15" y="40" width="10" height="10" rx="5"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 334 B |