Files
infra-scripts/ferretdb/README.md
Damien d10efadaba fix(ferretdb): create the Mongo user via DocumentDB to fix SCRAM salt length
A plain CREATE ROLE ... PASSWORD stores PostgreSQL's native 16-byte SCRAM salt.
DocumentDB has no check_password_hook, so that salt is served verbatim to Mongo
clients, which reject it at SASL step2 with 'invalid salt length of 16'.

Provision the user through documentdb_api.create_user / update_user instead:
DocumentDB builds the SCRAM-SHA-256 verifier with documentdb.scramDefaultSaltLen
(28 bytes) via its own scram_build_secret. create_user only accepts a read-only
role or the clusterAdmin + readWriteAnyDatabase pair, so use the latter for R/W.

- CREATE EXTENSION documentdb now runs before user provisioning so the API
  functions exist.
- Spec built with jq (password JSON-escaped) and passed in a $DDB$ dollar-quoted
  SQL literal; idempotent via update_user when the role already exists.
- Set password_encryption = 'scram-sha-256' explicitly in postgresql.conf.
2026-05-29 11:18:28 +02:00

150 lines
9.5 KiB
Markdown

# FerretDB
Automated installation and update script for [FerretDB](https://www.ferretdb.io) — a truly
open-source, MongoDB-compatible database — running inside a **Debian LXC** on Proxmox.
Built to give [LibreChat](https://www.librechat.ai) a drop-in MongoDB replacement without
running MongoDB itself (whose recent releases break on current kernels).
### Why Debian and not Alpine?
FerretDB v2 is two pieces:
1. the **FerretDB proxy** (a static Go binary), and
2. **PostgreSQL + Microsoft's DocumentDB extension**, the mandatory storage engine.
The DocumentDB extension is a compiled C PostgreSQL extension and is published **only** as
`deb`/`rpm` packages (`deb11`, `deb12`, `ubuntu`, `rhel`) — there is **no Alpine/musl build**.
So, unlike the `openbao`/`gitea-runner` Alpine LXCs in this repo, this stack runs on Debian 12
(`deb12`, the newest target the extension ships for).
### Features
Single script, automatic mode selection:
| Context | Action |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| From Proxmox host, no existing FerretDB container | Detects newest Debian template, creates LXC, installs PostgreSQL + DocumentDB + FerretDB |
| From Proxmox host, FerretDB container already present | Reuses the existing LXC, refreshes packages, upgrades the FerretDB stack to latest |
| From inside an LXC, no `ferretdb` binary | Installs the full stack from scratch |
| From inside an LXC, `ferretdb` already present | Updates the packages only (no config / role / data changes) |
The container is identified by hostname **and** the `ferretdb` tag, so it is re-found across
reruns even if the CTID was auto-allocated the first time.
### Requirements
- Proxmox VE host with `pveam`, `pct`, `pvesh`, `jq` available
- Internet access from both the host (template download) and the LXC (package downloads)
- Script must be run as **root** on the Proxmox host (enforced; the Web UI shell qualifies)
### Usage
#### Full install (from Proxmox shell)
```bash
bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh)"
```
The script prints the generated password and the ready-to-paste `MONGO_URI` at the end.
**Save them** — the password is not persisted on the Proxmox host.
Re-running the exact same command later upgrades packages inside the LXC and brings the
FerretDB stack to the latest release, without touching the config, role, or PostgreSQL data.
#### Customisation
Every parameter is exposed as an environment variable:
| Variable | Default | Description |
| ---------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `CTID` | auto | Container ID (auto-allocated via `pvesh get /cluster/nextid`) |
| `FERRETDB_HOSTNAME` | `ferretdb` | LXC hostname |
| `TEMPLATE` | auto-detected | Debian template; auto-detected from `pveam available` |
| `STORAGE` | `local-lvm` | Proxmox storage for the LXC root disk |
| `TEMPLATE_STORAGE` | `local` | Storage where Debian templates live |
| `CORES` | `2` | vCPU cores |
| `RAM` | `2048` | RAM in MiB (Postgres + FerretDB) |
| `DISK` | `16` | Root disk size in GB |
| `BRIDGE` | `vmbr0` | Network bridge |
| `LXC_TAG` | `ferretdb` | Stable tag used to re-discover the container |
| `PG_VERSION` | `17` | PostgreSQL major version (from PGDG) |
| `DOCUMENTDB_TAG` | `latest` | DocumentDB release tag (couples documentdb + FerretDB versions); pin e.g. `v0.107.0-ferretdb-2.7.0` |
| `DOCUMENTDB_DISTRO` | `deb12` | Distro target in the documentdb deb filename. Escape hatch for a future `deb13` (set with `TEMPLATE`) |
| `FERRETDB_LISTEN_ADDR` | `0.0.0.0:27017` | TCP listener. Exposed on all interfaces — it is a database other hosts must reach. |
| `FERRETDB_USER` | `ferretdb` | App user — both the PostgreSQL role and the MongoDB user LibreChat authenticates as |
| `FERRETDB_PASSWORD` | auto-generated | Auto-generated (`openssl rand -hex 24`) when unset; printed in the final summary |
| `TS_AUTHKEY` | _(unset)_ | Pre-auth key (generate at <https://login.tailscale.com/admin/settings/keys>). If unset, finish `tailscale up` manually inside the LXC. |
```bash
CTID=220 FERRETDB_HOSTNAME=mongo RAM=4096 DISK=32 \
bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh)"
```
#### Wiring LibreChat
LibreChat talks to FerretDB exactly as it would to MongoDB. Point its `MONGO_URI` at the
container and **remove the bundled `mongodb` service** so it doesn't start:
```env
# LibreChat .env
MONGO_URI=mongodb://ferretdb:<password>@<lxc-ip-or-tailnet-fqdn>:27017/LibreChat
```
If you run LibreChat with the upstream `docker-compose.yml`, drop the dependency on the local
Mongo service in `docker-compose.override.yml`:
```yaml
services:
api:
environment:
- MONGO_URI=mongodb://ferretdb:<password>@<lxc-ip>:27017/LibreChat
# disable the bundled mongodb service (don't start it)
mongodb:
profiles: [donotstart]
```
LibreChat creates the `LibreChat` database on first connection and stores all conversations
and settings there.
#### Tailscale
The LXC joins the tailnet (`tailscale up --ssh`) so LibreChat on another node can reach the
database over the tailnet. Unlike the `openbao` script there is **no `tailscale serve`** — the
MongoDB wire protocol is raw TCP, not HTTP, so the listener is exposed directly on
`0.0.0.0:27017` (LAN + tailnet) by design.
If `TS_AUTHKEY` was supplied the node is brought up automatically; otherwise finish it
manually inside the LXC:
```bash
pct enter <CTID>
tailscale up --ssh --hostname ferretdb
tailscale status # prints the tailnet FQDN
```
> Because the listener is on `0.0.0.0`, restrict access with your tailnet ACLs and/or a host
> firewall — anyone who can route to TCP 27017 can attempt to authenticate.
#### Update (from inside the LXC)
```bash
curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh | bash
```
The script auto-detects the presence of `/usr/bin/ferretdb` and switches to update mode:
packages are upgraded (including the DocumentDB extension via
`ALTER EXTENSION documentdb UPDATE`) and the services are restarted. The config, the
PostgreSQL role, and the data are left untouched.
### Architecture
- **OS**: latest Debian LXC template (auto-detected), unprivileged, `nesting=1`, `/dev/net/tun` passthrough for Tailscale
- **Storage engine**: PostgreSQL `17` (PGDG) + the DocumentDB extension (`pg_documentdb`, `pg_cron`), loopback-only on `127.0.0.1:5432`
- **Proxy**: official `ferretdb` deb from `github.com/FerretDB/FerretDB`, systemd unit, listening on `0.0.0.0:27017`
- **Auth**: the app user is provisioned through `documentdb_api.create_user` (roles `clusterAdmin` + `readWriteAnyDatabase`), **not** a plain `CREATE ROLE`. DocumentDB builds the SCRAM-SHA-256 verifier with its own 28-byte salt (`documentdb.scramDefaultSaltLen`); a native PostgreSQL role would store a 16-byte salt that MongoDB clients reject (`invalid salt length of 16 in sasl step2`). FerretDB connects to PostgreSQL as the same user.
- **Network**: FerretDB exposed on `0.0.0.0:27017`; Tailscale runs in the LXC for tailnet reachability (no `serve`)
- **Config**: `/etc/postgresql/17/main/conf.d/documentdb.conf` (extension settings) and `/etc/systemd/system/ferretdb.service.d/override.conf` (`FERRETDB_POSTGRESQL_URL`, `FERRETDB_LISTEN_ADDR`)
- **Logs**: PostgreSQL via its stock `logrotate`; FerretDB via journald, capped at `SystemMaxUse=200M`
- **Version tracking**: `/opt/ferretdb_version.txt` records the installed DocumentDB tag for idempotent reruns