SQL storage

PostgreSQL and MySQL/MariaDB as system of record.

SQL storage

Persistent state for lindir lives in PostgreSQL or MySQL/MariaDB. Development can use memory://.

Choice at deploy time

Config Value
dbEngine postgres (default) or mysql / mariadb
dbDsn Connection URL, or memory:// for local/dev without a DB

PostgreSQL example

{
  "dbEngine": "postgres",
  "dbDsn": "postgresql://lindir:YOUR_PASSWORD@127.0.0.1:5432/lindir"
}

Also accepted: postgres://... or libpq keywords host=... user=... password=... dbname=... port=....

CREATE USER lindir WITH PASSWORD 'YOUR_PASSWORD';
CREATE DATABASE lindir OWNER lindir;

Requires libpq at link/runtime (postgresql-libs / libpq).

MySQL / MariaDB example

{
  "dbEngine": "mysql",
  "dbDsn": "mysql://lindir:YOUR_PASSWORD@127.0.0.1:3306/lindir"
}

Also accepted: mariadb://... or mysql-native form host=127.0.0.1;port=3306;user=lindir;pwd=...;db=lindir.

Create the database and user before starting lindir:

CREATE DATABASE lindir CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'lindir'@'%' IDENTIFIED BY 'YOUR_PASSWORD';
GRANT ALL PRIVILEGES ON lindir.* TO 'lindir'@'%';
FLUSH PRIVILEGES;

MySQL / MariaDB: max_allowed_packet

Fleet inventory and vulnerability feed caches (Arch AVG, Debian DSA list, Ubuntu/OSV JSON) are large. mysql-native sends big parameters with COM_STMT_SEND_LONG_DATA, but MariaDB/MySQL still enforce max_allowed_packet on the whole value (not per chunk). Defaults (often 16 MiB) are too small; Lindir does not change this at runtime (max_allowed_packet is GLOBAL-only / read-only as SESSION on current MariaDB).

Set 64 MiB (or higher) on the database server before using Vulns Refresh or large inventory syncs:

  1. Create a server config snippet (path varies by distro):
# /etc/my.cnf.d/lindir.cnf   (MariaDB on many RPM/Arch layouts)
# /etc/mysql/mariadb.conf.d/99-lindir.cnf   (Debian/Ubuntu MariaDB)
[mysqld]
max_allowed_packet = 64M
  1. Restart the database:
sudo systemctl restart mariadb
# or: sudo systemctl restart mysqld
  1. Confirm:
SHOW VARIABLES LIKE 'max_allowed_packet';
-- Value should be >= 67108864

Optional one-shot until the next restart (needs a privileged DB account):

SET GLOBAL max_allowed_packet = 67108864;

If you see errors similar to Parameter … longer than ‘max_allowed_packet’ bytes during vuln feed refresh, raise this setting and retry.

After a MariaDB major upgrade, run sudo mariadb-upgrade -u root -p if the server reports Column count of mysql.proc is wrong (error 1558).

MySQL / MariaDB: nullable timestamps

MariaDB in strict mode rejects empty string for TIMESTAMP NULL / DATETIME NULL (Incorrect datetime value: ''). Repos must bind the SQL_NULL sentinel from lindir.storage.database for “no value yet” fields such as last_used_at, started_at, finished_at, retired_at, locked_until. Do not pass D null or "" for those parameters — the MySQL driver turns those into non-null ''.

Driver status

Engine Status
memory:// SQL subset for unit tests / offline dev
PostgreSQL Implemented via dpq2 (libpq); ? → $n rewrite
MySQL/MariaDB Implemented via mysql-native

Schema

Versioned SQL under sql/migrations/. Migrations run automatically on server start.

Migration Adds
001–003 Core IdM (users, groups, hosts, policy, POSIX)
002 / tokens Auth tokens + audit
004–006 OIDC + WebAuthn
007 WireGuard peers
008–010 Fleet labels/inventory/queries + packages + reboot fields
011_ops_qol Account policy, enroll tokens, host last_seen_at, user lockout/must-change
012_fleet_energy Energy policies + host/label targets

Empty string → SQL NULL handling for MySQL is intentional in repos (timestamps / optional fields).