Add user guide, self-hosting, API, and contributing docs
docs/user-guide.md — magic-link sign-in, note basics, wikilink syntax, keyboard shortcuts, offline behaviour, and privacy notes (sessionStorage for tokens, tenant-scoped localStorage). docs/self-hosting.md — system requirements, Docker Compose quick-start, the full LIBRENOTES_* env-var matrix (which are required, which conditional), reverse proxy snippets for Caddy and nginx, volume layout, the in-binary healthcheck, and update/rollback procedure. docs/api.md — every public endpoint: auth (login/verify), notes CRUD, /api/whoami, /healthz. Status codes per endpoint, the optimistic-locking ?base=<unix> contract for PUT/DELETE, note-ID regex, and the rate-limit policy. CONTRIBUTING.md — dev setup (Nix flake .#dev, plain Go, Docker), package layout overview, coding standards (one-way dep flow, tenant FS gateway requirement), branch naming, commit format, and the PR process. Also points security reports at security@librete.ch rather than public issues. Closes #29. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# Self-hosting
|
||||
|
||||
This guide explains how to run your own librenotes instance with
|
||||
Docker Compose. For day-2 ops (deploys, backups) see
|
||||
[operations.md](./operations.md).
|
||||
|
||||
## System requirements
|
||||
|
||||
- Linux server with Docker Engine 24+ and the Compose plugin.
|
||||
- At least 256 MB of RAM and 1 GB of disk for the application
|
||||
plus whatever space you expect notes to consume (typically
|
||||
measured in MB even for heavy users).
|
||||
- A public DNS name pointing to the host. Magic-link sign-in will
|
||||
not work over plain IP because some email clients refuse to
|
||||
follow links to bare-IP URLs.
|
||||
- An SMTP relay (or a service like Postmark/SES) that the host
|
||||
can reach on port 587 or 465.
|
||||
- A reverse proxy (nginx, Caddy, or Traefik) terminating TLS in
|
||||
front of librenotes.
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
git clone https://git.librete.ch/public/librenotes
|
||||
cd librenotes
|
||||
cp .env.example .env # edit values, see below
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
Visit `https://your-domain/` and sign in.
|
||||
|
||||
## Environment configuration
|
||||
|
||||
The runtime reads its configuration from environment variables.
|
||||
Place them in an `.env` file next to the Compose stack.
|
||||
|
||||
| Variable | Required | Description |
|
||||
| -------- | -------- | ----------- |
|
||||
| `LIBRENOTES_BASE_URL` | yes | Public origin used in magic links, e.g. `https://notes.example.com`. Must match what users will type. |
|
||||
| `LIBRENOTES_JWT_SECRET` | yes | At least 32 bytes of random data. Sessions are invalidated when this changes. |
|
||||
| `LIBRENOTES_SMTP_HOST` | yes (prod) | SMTP server hostname. Without it, magic links are logged to stdout (useful for dev only). |
|
||||
| `LIBRENOTES_SMTP_PORT` | no | Default `587`. |
|
||||
| `LIBRENOTES_SMTP_USER` | conditional | SMTP credential. |
|
||||
| `LIBRENOTES_SMTP_PASS` | conditional | SMTP credential. |
|
||||
| `LIBRENOTES_SMTP_FROM` | yes | Envelope sender, e.g. `no-reply@notes.example.com`. |
|
||||
| `LIBRENOTES_DATA_DIR` | no | Default `/data` inside the container. |
|
||||
| `LIBRENOTES_DB` | no | Default `/var/lib/librenotes/librenotes.db`. |
|
||||
| `LIBRENOTES_IMAGE` | yes (prod) | Image tag to pull, e.g. `registry.librete.ch/librenotes:v0.1.0`. |
|
||||
|
||||
Generate a JWT secret:
|
||||
|
||||
```sh
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
## Reverse proxy
|
||||
|
||||
### Caddy
|
||||
|
||||
```caddy
|
||||
notes.example.com {
|
||||
encode zstd gzip
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name notes.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/notes.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/notes.example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The application speaks plain HTTP and trusts the proxy for the
|
||||
public URL via `LIBRENOTES_BASE_URL`. Always terminate TLS at the
|
||||
proxy.
|
||||
|
||||
## Volumes
|
||||
|
||||
The Compose stack declares two named volumes:
|
||||
|
||||
- `notes` mounted at `/data` — per-tenant note files.
|
||||
- `state` mounted at `/var/lib/librenotes` — the SQLite database.
|
||||
|
||||
Both must be on persistent storage. Backing them up is covered
|
||||
in [operations.md](./operations.md#backups).
|
||||
|
||||
## Health check
|
||||
|
||||
`GET /healthz` returns `{"status":"ok"}` when the binary is
|
||||
running. The Compose stacks invoke `librenotes healthcheck` from
|
||||
inside the container (since the runtime image has no shell tools
|
||||
to use `curl`/`wget`).
|
||||
|
||||
## Updating
|
||||
|
||||
Pull the new image and restart:
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
To pin a specific version, set `LIBRENOTES_IMAGE` to that tag in
|
||||
`.env`. Rolling back is the same command after editing `.env`.
|
||||
Reference in New Issue
Block a user