From e120d179e31c811556c240ec967a66a6af1872b6 Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 29 May 2026 09:26:15 +0200 Subject: [PATCH 1/5] Update README with FerretDB installation script and requirements Add FerretDB script to available scripts table Update requirements to include FerretDB --- README.md | 17 +- ferretdb/README.md | 149 +++++++++++ ferretdb/install.sh | 629 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 787 insertions(+), 8 deletions(-) create mode 100644 ferretdb/README.md create mode 100755 ferretdb/install.sh diff --git a/README.md b/README.md index c0a345e..150e1a8 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,16 @@ These scripts automate the deployment of personal infrastructure components. The ### Available Scripts -| Script | Description | Usage | -| ------------------------------------------ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| [`proxy/install.sh`](proxy/) | Reverse proxy with Tailscale + Nginx Proxy Manager | `curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/proxy/install.sh` \| `bash` | -| [`netlab/install.sh`](netlab/) | Network lab with ContainerLab | `curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/netlab/install.sh` \| `bash` | -| [`gitea-runner/install.sh`](gitea-runner/) | Gitea Act Runner on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)"` | -| [`openbao/install.sh`](openbao/) | OpenBao secrets manager on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh)"` | +| Script | Description | Usage | +| ------------------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| [`proxy/install.sh`](proxy/) | Reverse proxy with Tailscale + Nginx Proxy Manager | `curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/proxy/install.sh` \| `bash` | +| [`netlab/install.sh`](netlab/) | Network lab with ContainerLab | `curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/netlab/install.sh` \| `bash` | +| [`gitea-runner/install.sh`](gitea-runner/) | Gitea Act Runner on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/gitea-runner/install.sh)"` | +| [`openbao/install.sh`](openbao/) | OpenBao secrets manager on Alpine LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/openbao/install.sh)"` | +| [`ferretdb/install.sh`](ferretdb/) | FerretDB (MongoDB-compatible) on Debian LXC (Proxmox) | `bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh)"` | ### Requirements -- Fresh Debian 12/13 installation (proxy, netlab) or Proxmox VE host (gitea-runner, openbao) -- User with sudo privileges (do not run as root) — except gitea-runner and openbao which run as root on Proxmox +- Fresh Debian 12/13 installation (proxy, netlab) or Proxmox VE host (gitea-runner, openbao, ferretdb) +- User with sudo privileges (do not run as root) — except gitea-runner, openbao and ferretdb which run as root on Proxmox - Internet access diff --git a/ferretdb/README.md b/ferretdb/README.md new file mode 100644 index 0000000..3e82e31 --- /dev/null +++ b/ferretdb/README.md @@ -0,0 +1,149 @@ +# 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 ). 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:@: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:@: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 +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**: MongoDB client credentials map to PostgreSQL roles; the app role is a superuser so the single homelab user can manage collections and Mongo users (`documentdb.enableUserCrud`) +- **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 diff --git a/ferretdb/install.sh b/ferretdb/install.sh new file mode 100755 index 0000000..6882c46 --- /dev/null +++ b/ferretdb/install.sh @@ -0,0 +1,629 @@ +#!/bin/bash +# install.sh - FerretDB: LXC creation, installation & update +# Usage: +# From Proxmox host : bash -c "$(curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh)" +# From inside LXC : bash /root/install.sh (updates packages) +# +# Single entrypoint, three automatic modes: +# 1. Proxmox host, no existing container -> create Debian LXC + install FerretDB +# 2. Proxmox host, container already present -> update packages + upgrade FerretDB +# 3. Inside an LXC -> install if missing, otherwise update +# +# FerretDB v2 is a MongoDB wire-protocol proxy backed by PostgreSQL + the +# DocumentDB extension. The extension is a compiled C PostgreSQL extension and +# is only published as deb/rpm packages (no Alpine/musl build), so this stack +# runs on Debian — unlike the openbao/gitea-runner Alpine LXCs in this repo. +# +# The package install/upgrade logic lives in a single reusable function +# (install_or_upgrade_packages) shared by both the create and update paths. + +set -euo pipefail + +# --- Config (override via environment) --- +CTID="${CTID:-}" +HOSTNAME_LXC="${FERRETDB_HOSTNAME:-ferretdb}" +TEMPLATE="${TEMPLATE:-}" # auto-detected when empty +STORAGE="${STORAGE:-local-lvm}" +TEMPLATE_STORAGE="${TEMPLATE_STORAGE:-local}" +CORES="${CORES:-2}" +RAM="${RAM:-2048}" # Postgres needs more headroom than openbao +DISK="${DISK:-16}" +BRIDGE="${BRIDGE:-vmbr0}" +LXC_TAG="${LXC_TAG:-ferretdb}" # stable identifier for the container +PG_VERSION="${PG_VERSION:-17}" # PostgreSQL major version (PGDG) +# DocumentDB release tag couples both pieces: it encodes the documentdb package +# version AND the matching FerretDB version. "latest" resolves both at once. +DOCUMENTDB_TAG="${DOCUMENTDB_TAG:-latest}" +# Distro target embedded in the documentdb deb filename. Only deb11/deb12 exist +# today (no deb13). Escape hatch: when upstream ships deb13, set this + TEMPLATE +# to move to trixie without editing the script. +DOCUMENTDB_DISTRO="${DOCUMENTDB_DISTRO:-deb12}" +DOCUMENTDB_RELEASES_URL="${DOCUMENTDB_RELEASES_URL:-https://api.github.com/repos/FerretDB/documentdb/releases}" +# As a database we deliberately expose the listener on all interfaces so other +# hosts (LibreChat) can reach it over the LAN / tailnet. PostgreSQL stays local. +FERRETDB_LISTEN_ADDR="${FERRETDB_LISTEN_ADDR:-0.0.0.0:27017}" +# Application credentials. The same user/password is both the PostgreSQL role +# FerretDB connects with AND the MongoDB user LibreChat authenticates as. +FERRETDB_USER="${FERRETDB_USER:-ferretdb}" +FERRETDB_PASSWORD="${FERRETDB_PASSWORD:-}" # auto-generated when empty +# Optional: pre-authorise the LXC's Tailscale non-interactively. +# Generate at https://login.tailscale.com/admin/settings/keys +TS_AUTHKEY="${TS_AUTHKEY:-}" +# SCRIPT_URL is what the host-side flow pipes into the LXC. Override it when +# testing from a non-main branch. +SCRIPT_URL="${SCRIPT_URL:-https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/ferretdb/install.sh}" +VERSION_FILE="${VERSION_FILE:-/opt/ferretdb_version.txt}" + +# --- Colors --- +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Logs go to stderr so callers can safely use $(fn) without capturing log noise. +log_info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; } +log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } + +# ============================================================ +# Generic helpers +# ============================================================ +require_root() { + if [[ "$(id -u)" -ne 0 ]]; then + log_error "This script must be run as root (current uid: $(id -u))." + log_error "On Proxmox, launch it from the host shell or via the Web UI shell, both of which run as root." + exit 1 + fi +} + +# Debian packages use dpkg-style arch names (amd64, arm64), which is also what +# both the FerretDB and DocumentDB release assets are named with. +get_arch() { + case "$(uname -m)" in + x86_64) echo "amd64" ;; + aarch64) echo "arm64" ;; + *) log_error "Unsupported architecture: $(uname -m)"; exit 1 ;; + esac +} + +# Resolve the DocumentDB release tag into the two coupled versions it encodes. +# Sets globals: DOC_TAG (full tag), DOC_PKG_VER (documentdb pkg version), +# FERRET_VER (matching FerretDB version, no leading v). +# Tag shape: v0.107.0-ferretdb-2.7.0 +resolve_versions() { + local endpoint tag + if [[ "$DOCUMENTDB_TAG" == "latest" ]]; then + endpoint="${DOCUMENTDB_RELEASES_URL}/latest" + else + endpoint="${DOCUMENTDB_RELEASES_URL}/tags/${DOCUMENTDB_TAG}" + fi + tag=$(curl -fsSL "$endpoint" | jq -r '.tag_name') + if [[ -z "$tag" || "$tag" == "null" ]]; then + log_error "Failed to resolve DocumentDB release '${DOCUMENTDB_TAG}' from GitHub API." + exit 1 + fi + DOC_TAG="$tag" + DOC_PKG_VER="${tag#v}" # 0.107.0-ferretdb-2.7.0 + DOC_PKG_VER="${DOC_PKG_VER%%-ferretdb-*}" # 0.107.0 + FERRET_VER="${tag##*-ferretdb-}" # 2.7.0 + if [[ -z "$DOC_PKG_VER" || -z "$FERRET_VER" || "$FERRET_VER" == "$tag" ]]; then + log_error "Could not parse DocumentDB tag '${tag}' (expected vX-ferretdb-Y)." + exit 1 + fi +} + +# ============================================================ +# Reusable: install or upgrade PostgreSQL + DocumentDB + FerretDB. +# Used by both fresh-install and update flows. Idempotent. +# ============================================================ +install_or_upgrade_packages() { + local arch current doc_url ferret_url tmpdir doc_deb_name doc_full_ver + resolve_versions + arch=$(get_arch) + + current="" + if [[ -f "$VERSION_FILE" ]]; then + current=$(cat "$VERSION_FILE") + fi + + if [[ "$current" == "$DOC_TAG" && -x /usr/bin/ferretdb ]]; then + log_info "FerretDB stack already at ${DOC_TAG}, nothing to do." + return 0 + fi + + # Ensure the PGDG repo is present so the requested PostgreSQL major exists. + if [[ ! -f /etc/apt/sources.list.d/pgdg.list ]]; then + log_info "Adding the PostgreSQL APT (PGDG) repository..." + install -d -m 0755 /usr/share/keyrings + curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ + | gpg --dearmor -o /usr/share/keyrings/postgresql.gpg + echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] https://apt.postgresql.org/pub/repos/apt $(. /etc/os-release && echo "$VERSION_CODENAME")-pgdg main" \ + > /etc/apt/sources.list.d/pgdg.list + apt-get update >/dev/null + fi + + log_info "Installing PostgreSQL ${PG_VERSION} + pg_cron..." + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + "postgresql-${PG_VERSION}" "postgresql-${PG_VERSION}-cron" >/dev/null + + # documentdb deb naming: deb12-postgresql-17-documentdb_0.107.0.ferretdb.2.7.0_amd64.deb + doc_full_ver="${DOC_PKG_VER}.ferretdb.${FERRET_VER}" + doc_deb_name="${DOCUMENTDB_DISTRO}-postgresql-${PG_VERSION}-documentdb_${doc_full_ver}_${arch}.deb" + doc_url="https://github.com/FerretDB/documentdb/releases/download/${DOC_TAG}/${doc_deb_name}" + ferret_url="https://github.com/FerretDB/FerretDB/releases/download/v${FERRET_VER}/ferretdb-${arch}-linux.deb" + + tmpdir=$(mktemp -d) + log_info "Downloading DocumentDB extension (${doc_deb_name})..." + curl -fsSL "$doc_url" -o "${tmpdir}/documentdb.deb" + log_info "Downloading FerretDB ${FERRET_VER} (${arch})..." + curl -fsSL "$ferret_url" -o "${tmpdir}/ferretdb.deb" + + log_info "Installing DocumentDB extension + FerretDB (apt resolves dependencies)..." + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + "${tmpdir}/documentdb.deb" "${tmpdir}/ferretdb.deb" >/dev/null + + # If the extension is already created (update path), bring it to the new version. + if su -s /bin/sh postgres -c "psql -tAc \"SELECT 1 FROM pg_extension WHERE extname='documentdb'\" -d postgres" 2>/dev/null | grep -q 1; then + log_info "Updating documentdb extension to ${DOC_PKG_VER}..." + su -s /bin/sh postgres -c "psql -d postgres -c 'ALTER EXTENSION documentdb UPDATE;'" >/dev/null 2>&1 || \ + log_warn "ALTER EXTENSION documentdb UPDATE failed — check after restart." + fi + + echo "$DOC_TAG" > "$VERSION_FILE" + rm -rf "$tmpdir" + + log_info "Installed FerretDB: $(/usr/bin/ferretdb --version 2>&1 | head -n1 || true)" + + # Restart services if they already exist (update path); the install path + # enables them explicitly after configuration. + if systemctl list-unit-files ferretdb.service >/dev/null 2>&1; then + systemctl restart postgresql 2>/dev/null || true + systemctl restart ferretdb 2>/dev/null || true + fi +} + +# ============================================================ +# Reusable: bring Tailscale up so the LXC joins the tailnet. +# Unlike openbao we do NOT use 'tailscale serve' — FerretDB speaks the raw +# MongoDB wire protocol (TCP), not HTTP, so serve does not apply. The DB is +# reachable directly on 0.0.0.0:27017 over the LAN / tailnet. +# ============================================================ +configure_tailscale() { + if ! command -v tailscale >/dev/null 2>&1; then + log_warn "tailscale CLI not found, skipping tailnet setup." + return 0 + fi + + local backend_state + backend_state=$(tailscale status --json 2>/dev/null | jq -r '.BackendState // "unknown"') + if [[ "$backend_state" == "Running" ]]; then + log_info "Tailscale already up." + return 0 + fi + + if [[ -n "$TS_AUTHKEY" ]]; then + log_info "Bringing Tailscale up with provided auth key..." + tailscale up --authkey "$TS_AUTHKEY" --ssh --hostname "$HOSTNAME_LXC" \ + || log_warn "tailscale up failed — run it manually inside the LXC." + else + log_warn "Tailscale not authenticated and TS_AUTHKEY was not supplied." + log_warn "Finish setup inside the LXC with: tailscale up --ssh --hostname ${HOSTNAME_LXC}" + fi +} + +# ============================================================ +# Proxmox-host helpers +# ============================================================ + +# Detect newest Debian LXC template available from the Proxmox repos. +detect_latest_debian_template() { + local tmpl + tmpl=$(pveam available --section system 2>/dev/null \ + | awk '/^system[[:space:]]+debian-/ {print $2}' \ + | sort -V \ + | tail -n1) + + if [[ -z "$tmpl" ]]; then + log_warn "Could not query pveam; falling back to a known-good Debian template." + tmpl="debian-12-standard_12.7-1_amd64.tar.zst" + fi + log_info "Selected Debian template: $tmpl" + echo "$tmpl" +} + +# Find an existing LXC by tag or hostname. Echoes CTID, returns 1 if none. +find_existing_lxc() { + local id host tags + while read -r id _; do + [[ -z "$id" || "$id" == "VMID" ]] && continue + host=$(pct config "$id" 2>/dev/null | awk -F': ' '/^hostname:/ {print $2}' || true) + tags=$(pct config "$id" 2>/dev/null | awk -F': ' '/^tags:/ {print $2}' || true) + if [[ "$host" == "$HOSTNAME_LXC" ]] || [[ ",${tags//;/,}," == *",${LXC_TAG},"* ]]; then + echo "$id" + return 0 + fi + done < <(pct list | awk 'NR>1 {print $1}') + return 1 +} + +ensure_template_present() { + local tmpl="$1" + if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$tmpl"; then + log_info "Downloading template ${tmpl} to storage ${TEMPLATE_STORAGE}..." + pveam update >/dev/null + pveam download "$TEMPLATE_STORAGE" "$tmpl" + else + log_info "Template ${tmpl} already present on ${TEMPLATE_STORAGE}." + fi +} + +# Pick next available CTID if user did not provide one. +allocate_ctid() { + pvesh get /cluster/nextid 2>/dev/null \ + || pvesh get /cluster/resources --type vm --output-format json 2>/dev/null \ + | jq '[.[].vmid] | max + 1' \ + || echo 100 +} + +# Inject the script into the container and execute it in the requested mode. +# Forwards the relevant runtime configuration through the environment so the +# inner invocation produces the same config the user requested on the host. +exec_in_lxc() { + local ctid="$1" + local mode="$2" # --install or --update + + # Ensure base tooling exists inside the container before piping the script. + pct exec "$ctid" -- sh -c "export DEBIAN_FRONTEND=noninteractive; apt-get update >/dev/null 2>&1; apt-get install -y bash curl jq ca-certificates >/dev/null 2>&1" + curl -fsSL "$SCRIPT_URL" \ + | pct exec "$ctid" -- env \ + SCRIPT_URL="$SCRIPT_URL" \ + PG_VERSION="$PG_VERSION" \ + DOCUMENTDB_TAG="$DOCUMENTDB_TAG" \ + DOCUMENTDB_DISTRO="$DOCUMENTDB_DISTRO" \ + FERRETDB_HOSTNAME="$HOSTNAME_LXC" \ + FERRETDB_LISTEN_ADDR="$FERRETDB_LISTEN_ADDR" \ + FERRETDB_USER="$FERRETDB_USER" \ + FERRETDB_PASSWORD="$FERRETDB_PASSWORD" \ + TS_AUTHKEY="$TS_AUTHKEY" \ + bash -s -- "$mode" +} + +# ============================================================ +# MODE: Proxmox host — create LXC + install +# ============================================================ +create_lxc() { + log_info "=== FerretDB — LXC creation ===" + + # Generate the password on the host so we can both pass it in and print it. + if [[ -z "$FERRETDB_PASSWORD" ]]; then + FERRETDB_PASSWORD=$(openssl rand -hex 24) + log_info "Generated FerretDB password (saved in the summary below)." + fi + + if [[ -z "$TEMPLATE" ]]; then + TEMPLATE=$(detect_latest_debian_template) + else + log_info "Using user-provided template: $TEMPLATE" + fi + ensure_template_present "$TEMPLATE" + + if [[ -z "$CTID" ]]; then + CTID=$(allocate_ctid) + log_info "Auto-selected CTID: $CTID" + fi + + log_info "Creating LXC ${CTID} (${HOSTNAME_LXC})..." + pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" \ + --hostname "$HOSTNAME_LXC" \ + --cores "$CORES" \ + --memory "$RAM" \ + --rootfs "${STORAGE}:${DISK}" \ + --net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" \ + --unprivileged 1 \ + --features "nesting=1" \ + --tags "infra-script,${LXC_TAG}" \ + --onboot 1 \ + --start 0 + + # Tailscale needs /dev/net/tun inside the unprivileged container. + log_info "Adding /dev/net/tun passthrough for Tailscale..." + cat >> "/etc/pve/lxc/${CTID}.conf" </dev/null; do + tries=$((tries + 1)) + if (( tries > 30 )); then + log_error "LXC ${CTID} did not acquire an IP after 30s." + exit 1 + fi + sleep 1 + done + + log_info "Running installer inside LXC ${CTID}..." + exec_in_lxc "$CTID" "--install" + + local ip + ip=$(pct exec "$CTID" -- ip -4 addr show eth0 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1 || true) + + echo "" + log_info "=========================================" + log_info "LXC ${CTID} created successfully!" + log_info "=========================================" + echo "" + echo " Hostname : ${HOSTNAME_LXC}" + echo " IP : ${ip:-pending}" + echo " FerretDB : ${FERRETDB_LISTEN_ADDR}" + echo "" + echo "LibreChat connection string (set as MONGO_URI in LibreChat's .env):" + echo " mongodb://${FERRETDB_USER}:${FERRETDB_PASSWORD}@${ip:-}:27017/LibreChat" + echo "" + echo "Store this password somewhere safe — it is not persisted on the host:" + echo " user : ${FERRETDB_USER}" + echo " password : ${FERRETDB_PASSWORD}" + echo "" +} + +# ============================================================ +# MODE: Proxmox host — update existing LXC +# ============================================================ +update_lxc() { + local ctid="$1" + log_info "=== FerretDB — updating existing LXC ${ctid} ===" + + if ! pct status "$ctid" | grep -q running; then + log_info "Starting LXC ${ctid}..." + pct start "$ctid" + sleep 3 + fi + + log_info "Refreshing Debian packages inside LXC ${ctid}..." + pct exec "$ctid" -- sh -c "export DEBIAN_FRONTEND=noninteractive; apt-get update >/dev/null && apt-get upgrade -y >/dev/null" + + log_info "Upgrading FerretDB stack inside LXC ${ctid}..." + exec_in_lxc "$ctid" "--update" + + log_info "Update of LXC ${ctid} complete." +} + +# ============================================================ +# MODE: inside LXC — fresh install of FerretDB +# ============================================================ +install_inside_lxc() { + log_info "=== FerretDB — installation ===" + + if [[ -z "$FERRETDB_PASSWORD" ]]; then + FERRETDB_PASSWORD=$(openssl rand -hex 24 2>/dev/null || head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n') + log_info "Generated FerretDB password (shown in the summary below)." + fi + + log_info "Updating package index..." + export DEBIAN_FRONTEND=noninteractive + apt-get update >/dev/null + apt-get upgrade -y >/dev/null + + log_info "Installing base dependencies..." + apt-get install -y curl jq ca-certificates gnupg lsb-release sudo logrotate openssl >/dev/null + + log_info "Installing Tailscale..." + if ! command -v tailscale >/dev/null 2>&1; then + curl -fsSL https://tailscale.com/install.sh | sh >/dev/null 2>&1 \ + || log_warn "Tailscale install script failed — install it manually later." + fi + systemctl enable --now tailscaled >/dev/null 2>&1 \ + || log_warn "tailscaled failed to start (is /dev/net/tun mapped into the LXC?)" + + install_or_upgrade_packages + + log_info "Configuring PostgreSQL for DocumentDB..." + local pg_confd="/etc/postgresql/${PG_VERSION}/main/conf.d" + mkdir -p "$pg_confd" + # https://docs.ferretdb.io/installation/documentdb/deb/ + cat > "${pg_confd}/documentdb.conf" </dev/null < /etc/systemd/system/ferretdb.service.d/override.conf </dev/null 2>&1 || true + systemctl enable ferretdb >/dev/null 2>&1 || true + # Explicit restart: the deb postinst may have already started ferretdb with + # default settings, in which case 'enable --now' would not re-read our override. + systemctl restart ferretdb || log_warn "ferretdb failed to start — check 'journalctl -u ferretdb'." + + # --- Log hygiene --- + # PostgreSQL ships /etc/logrotate.d/postgresql-common already. FerretDB logs to + # journald, so bound the journal instead of adding a logrotate stanza. + log_info "Bounding the systemd journal size..." + mkdir -p /etc/systemd/journald.conf.d + cat > /etc/systemd/journald.conf.d/ferretdb.conf <<'EOF' +[Journal] +SystemMaxUse=200M +EOF + systemctl restart systemd-journald >/dev/null 2>&1 || true + + # --- Console auto-login on tty1 (Proxmox web console / pct console) --- + log_info "Enabling console auto-login on tty1..." + mkdir -p /etc/systemd/system/container-getty@1.service.d + cat > /etc/systemd/system/container-getty@1.service.d/autologin.conf <<'EOF' +[Service] +ExecStart= +ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 $TERM +EOF + systemctl daemon-reload + systemctl restart container-getty@1.service 2>/dev/null || true + + configure_tailscale + + log_info "Configuring MOTD..." + # /etc/profile.d/ runs for every interactive login shell — works for both the + # auto-login tty and Tailscale SSH. Quoted heredoc except the values we want + # frozen at install time, which we inject via a small companion env file. + cat > /etc/ferretdb-motd.env < /etc/profile.d/00-ferretdb.sh <<'MOTD' +[ -f /etc/ferretdb-motd.env ] && . /etc/ferretdb-motd.env + +TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' ' + /"Self"/ { in_self=1 } + in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit } +') +[ -z "$TS_FQDN" ] && TS_FQDN="$(hostname).ts.net" +LAN_IP=$(ip -4 addr show eth0 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1) +FERRET_VERSION=$(/usr/bin/ferretdb --version 2>/dev/null | head -n1 || echo "unknown") + +systemctl is-active --quiet ferretdb && FERRET_STATE="active" || FERRET_STATE="DOWN" +systemctl is-active --quiet postgresql && PG_STATE="active" || PG_STATE="DOWN" + +echo "" +echo " _____ _ ____ ____ " +echo "| ___|__ _ __ _ __ ___| |_| _ \\| __ ) " +echo "| |_ / _ \\ '__| '__/ _ \\ __| | | | _ \\ " +echo "| _| __/ | | | | __/ |_| |_| | |_) |" +echo "|_| \\___|_| |_| \\___|\\__|____/|____/ " +echo "" +echo "FerretDB (MongoDB-compatible) — ${FERRET_VERSION}" +echo "─────────────────────────────────────────" +echo "Status:" +echo " • FerretDB : ${FERRET_STATE} (listening on ${FERRETDB_LISTEN_ADDR})" +echo " • PostgreSQL : ${PG_STATE} (127.0.0.1:5432)" +echo "" +echo "LibreChat MONGO_URI:" +echo " mongodb://${FERRETDB_USER}:@${LAN_IP:-}:27017/LibreChat" +echo " (tailnet) mongodb://${FERRETDB_USER}:@${TS_FQDN}:27017/LibreChat" +echo "" +echo "Useful commands:" +echo " systemctl status ferretdb postgresql" +echo " journalctl -u ferretdb -f" +echo " mongosh \"mongodb://${FERRETDB_USER}:@127.0.0.1:27017/\"" +echo "─────────────────────────────────────────" +echo "" +MOTD + chmod +x /etc/profile.d/00-ferretdb.sh + + log_info "Cleaning up..." + apt-get clean >/dev/null 2>&1 || true + + local ip + ip=$(ip -4 addr show eth0 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1 || true) + + echo "" + log_info "=========================================" + log_info "FerretDB installation complete!" + log_info "=========================================" + echo "" + echo "Set this as MONGO_URI in LibreChat's .env:" + echo " mongodb://${FERRETDB_USER}:${FERRETDB_PASSWORD}@${ip:-}:27017/LibreChat" + echo "" + echo "Credentials (store safely — not persisted on the Proxmox host):" + echo " user : ${FERRETDB_USER}" + echo " password : ${FERRETDB_PASSWORD}" + echo "" +} + +# ============================================================ +# MODE: inside LXC — update only +# ============================================================ +update_inside_lxc() { + log_info "=== FerretDB — update ===" + export DEBIAN_FRONTEND=noninteractive + apt-get update >/dev/null + apt-get upgrade -y >/dev/null + install_or_upgrade_packages + configure_tailscale + log_info "Update complete." +} + +# ============================================================ +# Main — dispatch on explicit mode flag or auto-detect context +# ============================================================ +main() { + case "${1:-}" in + --install) + install_inside_lxc + return + ;; + --update) + update_inside_lxc + return + ;; + esac + + if command -v pct >/dev/null 2>&1; then + # Running on a Proxmox host + require_root + + local existing="" + if existing=$(find_existing_lxc); then + log_info "Found existing FerretDB LXC (CTID ${existing}, hostname/tag match) — switching to update mode." + update_lxc "$existing" + else + create_lxc + fi + else + # Inside a container (no Proxmox tooling) + require_root + if [[ -x /usr/bin/ferretdb ]]; then + update_inside_lxc + else + install_inside_lxc + fi + fi +} + +main "$@" -- 2.54.0 From 5caf885d838ad45f8ec2dfdf07a7aec3d57dc90c Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 29 May 2026 10:56:51 +0200 Subject: [PATCH 2/5] fix(ferretdb): pin template to debian-12 and fail fast on bad SCRIPT_URL - detect_latest_debian_template now selects the newest debian-12 template only. Auto-detecting the newest Debian overall picked debian-13, which would pair the deb12-only DocumentDB extension with a mismatched libicu. Trixie now requires setting TEMPLATE + DOCUMENTDB_DISTRO explicitly. - Add preflight_script_url: verify SCRIPT_URL is reachable on the host before creating the LXC, so a branch/path that 404s fails immediately with guidance instead of dying mid-install after the container is built. --- ferretdb/install.sh | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/ferretdb/install.sh b/ferretdb/install.sh index 6882c46..d228b63 100755 --- a/ferretdb/install.sh +++ b/ferretdb/install.sh @@ -215,16 +215,35 @@ configure_tailscale() { # Proxmox-host helpers # ============================================================ -# Detect newest Debian LXC template available from the Proxmox repos. +# The installer re-fetches itself inside the LXC from SCRIPT_URL. Verify it is +# reachable on the host *before* creating any container, so a wrong branch/path +# fails immediately with guidance instead of dying mid-install with a curl 404. +preflight_script_url() { + if curl -fsSL -o /dev/null "$SCRIPT_URL"; then + return 0 + fi + log_error "SCRIPT_URL is not reachable: ${SCRIPT_URL}" + log_error "The installer re-fetches itself inside the LXC from SCRIPT_URL, so this" + log_error "must resolve. If you are testing from a branch (not yet on main), pass it:" + log_error " SCRIPT_URL=https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch//ferretdb/install.sh \\" + log_error " bash -c \"\$(curl -fsSL \"\$SCRIPT_URL\")\"" + exit 1 +} + +# Detect newest Debian *12* LXC template available from the Proxmox repos. +# Deliberately pinned to debian-12: the DocumentDB extension only ships a deb12 +# build (DOCUMENTDB_DISTRO default), and a newer template (e.g. debian-13) would +# pair that deb against a different libicu soname. To move to trixie, set both +# TEMPLATE and DOCUMENTDB_DISTRO yourself once upstream publishes a deb13 build. detect_latest_debian_template() { local tmpl tmpl=$(pveam available --section system 2>/dev/null \ - | awk '/^system[[:space:]]+debian-/ {print $2}' \ + | awk '/^system[[:space:]]+debian-12-/ {print $2}' \ | sort -V \ | tail -n1) if [[ -z "$tmpl" ]]; then - log_warn "Could not query pveam; falling back to a known-good Debian template." + log_warn "Could not find a debian-12 template via pveam; falling back to a known-good name." tmpl="debian-12-standard_12.7-1_amd64.tar.zst" fi log_info "Selected Debian template: $tmpl" @@ -607,6 +626,7 @@ main() { if command -v pct >/dev/null 2>&1; then # Running on a Proxmox host require_root + preflight_script_url local existing="" if existing=$(find_existing_lxc); then -- 2.54.0 From f13af26a624b26a02290ba122053c59e84295e87 Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 29 May 2026 11:01:07 +0200 Subject: [PATCH 3/5] fix(ferretdb): force C.UTF-8 locale to silence apt/perl locale warnings A fresh Debian template has not generated the Proxmox host locale that pct exec forwards (e.g. fr_FR.UTF-8), so apt, perl and apt-listchanges warn on every run. Export LC_ALL=C.UTF-8 / LANG=C.UTF-8 globally and forward them through pct exec (base tooling, the piped installer, and the host-side upgrade). --- ferretdb/install.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ferretdb/install.sh b/ferretdb/install.sh index d228b63..b01d013 100755 --- a/ferretdb/install.sh +++ b/ferretdb/install.sh @@ -19,6 +19,13 @@ set -euo pipefail +# Force an always-present locale. A fresh Debian template has not generated the +# host's locale (e.g. fr_FR.UTF-8 inherited through pct exec), so apt, perl and +# apt-listchanges warn loudly about it. C.UTF-8 ships with glibc and is always +# valid; this silences the noise without installing extra locales. +export LC_ALL=C.UTF-8 +export LANG=C.UTF-8 + # --- Config (override via environment) --- CTID="${CTID:-}" HOSTNAME_LXC="${FERRETDB_HOSTNAME:-ferretdb}" @@ -292,9 +299,11 @@ exec_in_lxc() { local mode="$2" # --install or --update # Ensure base tooling exists inside the container before piping the script. - pct exec "$ctid" -- sh -c "export DEBIAN_FRONTEND=noninteractive; apt-get update >/dev/null 2>&1; apt-get install -y bash curl jq ca-certificates >/dev/null 2>&1" + pct exec "$ctid" -- sh -c "export DEBIAN_FRONTEND=noninteractive LC_ALL=C.UTF-8 LANG=C.UTF-8; apt-get update >/dev/null 2>&1; apt-get install -y bash curl jq ca-certificates >/dev/null 2>&1" curl -fsSL "$SCRIPT_URL" \ | pct exec "$ctid" -- env \ + LC_ALL=C.UTF-8 \ + LANG=C.UTF-8 \ SCRIPT_URL="$SCRIPT_URL" \ PG_VERSION="$PG_VERSION" \ DOCUMENTDB_TAG="$DOCUMENTDB_TAG" \ @@ -403,7 +412,7 @@ update_lxc() { fi log_info "Refreshing Debian packages inside LXC ${ctid}..." - pct exec "$ctid" -- sh -c "export DEBIAN_FRONTEND=noninteractive; apt-get update >/dev/null && apt-get upgrade -y >/dev/null" + pct exec "$ctid" -- sh -c "export DEBIAN_FRONTEND=noninteractive LC_ALL=C.UTF-8 LANG=C.UTF-8; apt-get update >/dev/null && apt-get upgrade -y >/dev/null" log_info "Upgrading FerretDB stack inside LXC ${ctid}..." exec_in_lxc "$ctid" "--update" -- 2.54.0 From d10efadaba6ea995f7fde2afa1c65fdc549781ef Mon Sep 17 00:00:00 2001 From: Damien Date: Fri, 29 May 2026 11:18:28 +0200 Subject: [PATCH 4/5] 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. --- ferretdb/README.md | 2 +- ferretdb/install.sh | 48 +++++++++++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/ferretdb/README.md b/ferretdb/README.md index 3e82e31..c4d15c4 100644 --- a/ferretdb/README.md +++ b/ferretdb/README.md @@ -142,7 +142,7 @@ PostgreSQL role, and the data are left untouched. - **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**: MongoDB client credentials map to PostgreSQL roles; the app role is a superuser so the single homelab user can manage collections and Mongo users (`documentdb.enableUserCrud`) +- **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` diff --git a/ferretdb/install.sh b/ferretdb/install.sh index b01d013..7cd1164 100755 --- a/ferretdb/install.sh +++ b/ferretdb/install.sh @@ -467,6 +467,10 @@ documentdb.enableBypassDocumentValidation = true documentdb.enableUserCrud = true documentdb.maxUserLimit = 100 +# Ensure any password we set is hashed with SCRAM-SHA-256 (PG default, set +# explicitly so it is active before roles are provisioned). +password_encryption = 'scram-sha-256' + # Postgres stays loopback-only; FerretDB (same LXC) is the network front door. listen_addresses = '127.0.0.1' EOF @@ -474,22 +478,36 @@ EOF log_info "Restarting PostgreSQL..." systemctl restart postgresql - log_info "Bootstrapping role '${FERRETDB_USER}' and documentdb extension..." - # The single homelab app role is a superuser so it can manage collections and - # MongoDB users (documentdb.enableUserCrud). FerretDB connects as this role - # and LibreChat authenticates as the same user/password. - local pw_escaped="${FERRETDB_PASSWORD//\'/\'\'}" - su -s /bin/sh postgres -c "psql -v ON_ERROR_STOP=1 -d postgres" >/dev/null </dev/null <<'SQL' CREATE EXTENSION IF NOT EXISTS documentdb CASCADE; +SQL + + # Provision the user THROUGH DocumentDB — never a plain CREATE ROLE ... PASSWORD. + # DocumentDB builds the SCRAM-SHA-256 verifier with its own salt length + # (documentdb.scramDefaultSaltLen = 28 bytes) via documentdb_api.create_user / + # update_user. A native PostgreSQL role instead stores a 16-byte salt, which + # MongoDB clients reject at SASL step2 with "invalid salt length of 16". + # create_user only accepts a read-only role, or the clusterAdmin + + # readWriteAnyDatabase pair we use here for full read/write (FerretDB/DocumentDB + # commands/users.c::ValidateAndObtainUserRole). The spec is built with jq so the + # password is JSON-escaped, then embedded in a $DDB$-dollar-quoted SQL literal. + log_info "Provisioning MongoDB user '${FERRETDB_USER}' via DocumentDB (28-byte SCRAM salt)..." + local role_exists cmd spec + role_exists=$(su -s /bin/sh postgres -c \ + "psql -tAX -d postgres -c \"SELECT 1 FROM pg_roles WHERE rolname = '${FERRETDB_USER}'\"" 2>/dev/null || true) + if [[ "$role_exists" == "1" ]]; then + log_info "Role '${FERRETDB_USER}' already exists — resetting its password via DocumentDB." + cmd="update_user" + spec=$(jq -nc --arg u "$FERRETDB_USER" --arg p "$FERRETDB_PASSWORD" \ + '{updateUser:$u, pwd:$p}') + else + cmd="create_user" + spec=$(jq -nc --arg u "$FERRETDB_USER" --arg p "$FERRETDB_PASSWORD" \ + '{createUser:$u, pwd:$p, roles:[{role:"clusterAdmin",db:"admin"},{role:"readWriteAnyDatabase",db:"admin"}]}') + fi + su -s /bin/sh postgres -c "psql -v ON_ERROR_STOP=1 -d postgres" >/dev/null < Date: Fri, 29 May 2026 11:49:56 +0200 Subject: [PATCH 5/5] refactor(ferretdb): keep the install agnostic, drop LibreChat-specific wiring The script installs and exposes the database only; it now prints a generic MongoDB connection string (mongodb://user:pass@ip:27017/) instead of a LibreChat MONGO_URI with a hardcoded /LibreChat database. README 'Wiring LibreChat' section replaced with a generic 'Connecting a client' section. Application wiring is left to that app's own config or a separate script. --- ferretdb/README.md | 40 +++++++++++++++------------------------- ferretdb/install.sh | 18 +++++++++--------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/ferretdb/README.md b/ferretdb/README.md index c4d15c4..12f6897 100644 --- a/ferretdb/README.md +++ b/ferretdb/README.md @@ -3,8 +3,10 @@ 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). +A drop-in MongoDB replacement for any app that speaks the MongoDB wire protocol (e.g. +[LibreChat](https://www.librechat.ai)) without running MongoDB itself. The script installs +and exposes the database only; wiring it into an application is left to that app's own +configuration (a separate script, or manually). ### Why Debian and not Alpine? @@ -72,7 +74,7 @@ Every parameter is exposed as an environment variable: | `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_USER` | `ferretdb` | App user — both the PostgreSQL role and the MongoDB user clients authenticate 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 ). If unset, finish `tailscale up` manually inside the LXC. | @@ -81,35 +83,23 @@ 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 +#### Connecting a client -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: +Any MongoDB driver or tool connects with a standard connection string (the script prints the +exact one, with the generated password, at the end of the install): -```env -# LibreChat .env -MONGO_URI=mongodb://ferretdb:@:27017/LibreChat +``` +mongodb://ferretdb:@:27017/ ``` -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:@: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. +Append a database name to target one (e.g. `…:27017/myapp`); it is created on first write. +Wiring this into a specific application (setting its Mongo connection string, disabling any +bundled MongoDB it ships, etc.) is intentionally out of scope — do it from that app's own +config or a dedicated script. #### Tailscale -The LXC joins the tailnet (`tailscale up --ssh`) so LibreChat on another node can reach the +The LXC joins the tailnet (`tailscale up --ssh`) so a client 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. diff --git a/ferretdb/install.sh b/ferretdb/install.sh index 7cd1164..40fbaed 100755 --- a/ferretdb/install.sh +++ b/ferretdb/install.sh @@ -47,10 +47,10 @@ DOCUMENTDB_TAG="${DOCUMENTDB_TAG:-latest}" DOCUMENTDB_DISTRO="${DOCUMENTDB_DISTRO:-deb12}" DOCUMENTDB_RELEASES_URL="${DOCUMENTDB_RELEASES_URL:-https://api.github.com/repos/FerretDB/documentdb/releases}" # As a database we deliberately expose the listener on all interfaces so other -# hosts (LibreChat) can reach it over the LAN / tailnet. PostgreSQL stays local. +# hosts can reach it over the LAN / tailnet. PostgreSQL stays local. FERRETDB_LISTEN_ADDR="${FERRETDB_LISTEN_ADDR:-0.0.0.0:27017}" # Application credentials. The same user/password is both the PostgreSQL role -# FerretDB connects with AND the MongoDB user LibreChat authenticates as. +# FerretDB connects with AND the MongoDB user clients authenticate as. FERRETDB_USER="${FERRETDB_USER:-ferretdb}" FERRETDB_PASSWORD="${FERRETDB_PASSWORD:-}" # auto-generated when empty # Optional: pre-authorise the LXC's Tailscale non-interactively. @@ -389,8 +389,8 @@ EOF echo " IP : ${ip:-pending}" echo " FerretDB : ${FERRETDB_LISTEN_ADDR}" echo "" - echo "LibreChat connection string (set as MONGO_URI in LibreChat's .env):" - echo " mongodb://${FERRETDB_USER}:${FERRETDB_PASSWORD}@${ip:-}:27017/LibreChat" + echo "MongoDB connection string (point your client/app at it):" + echo " mongodb://${FERRETDB_USER}:${FERRETDB_PASSWORD}@${ip:-}:27017/" echo "" echo "Store this password somewhere safe — it is not persisted on the host:" echo " user : ${FERRETDB_USER}" @@ -589,9 +589,9 @@ echo "Status:" echo " • FerretDB : ${FERRET_STATE} (listening on ${FERRETDB_LISTEN_ADDR})" echo " • PostgreSQL : ${PG_STATE} (127.0.0.1:5432)" echo "" -echo "LibreChat MONGO_URI:" -echo " mongodb://${FERRETDB_USER}:@${LAN_IP:-}:27017/LibreChat" -echo " (tailnet) mongodb://${FERRETDB_USER}:@${TS_FQDN}:27017/LibreChat" +echo "Connection string (MongoDB URI):" +echo " mongodb://${FERRETDB_USER}:@${LAN_IP:-}:27017/" +echo " (tailnet) mongodb://${FERRETDB_USER}:@${TS_FQDN}:27017/" echo "" echo "Useful commands:" echo " systemctl status ferretdb postgresql" @@ -613,8 +613,8 @@ MOTD log_info "FerretDB installation complete!" log_info "=========================================" echo "" - echo "Set this as MONGO_URI in LibreChat's .env:" - echo " mongodb://${FERRETDB_USER}:${FERRETDB_PASSWORD}@${ip:-}:27017/LibreChat" + echo "MongoDB connection string (point your client/app at it):" + echo " mongodb://${FERRETDB_USER}:${FERRETDB_PASSWORD}@${ip:-}:27017/" echo "" echo "Credentials (store safely — not persisted on the Proxmox host):" echo " user : ${FERRETDB_USER}" -- 2.54.0