Architecture
┌─────────────────────────────────────────────────────────┐
│ lindir server (D) │
│ ┌─────────┐ ┌──────────┐ ┌────────┐ ┌───────────────┐ │
│ │ identity│ │ policy │ │ auth │ │ vpn plugins │ │
│ │ users │ │ HBAC │ │ tokens │ │ Tailscale… │ │
│ │ groups │ │ sudo │ │ enroll │ │ (optional) │ │
│ │ hosts │ │ SSH keys │ │ PBKDF2 │ │ │ │
│ └────┬────┘ └────┬─────┘ └───┬────┘ └───────┬───────┘ │
│ ┌────┴───────────┴───────────┴──────────────┴───────┐ │
│ │ fleet: inventory · labels · packages · energy · │ │
│ │ queries/scripts · ops attention │ │
│ │ oidc · webauthn · ldap gateway · audit │ │
│ └───────────────────────┬───────────────────────────┘ │
│ repo layer (parameterized SQL) │
│ │ │
│ Database interface (?, tx) │
│ memory:// | postgres | mysql │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ lindir-client (D) │
│ enroll · sync · pam-auth · authorized-keys │
│ → extrausers · sudoers.d · package jobs · energy │
│ → inventory · query/script runs │
└─────────────────────────────────────────────────────────┘
Modules
| Path | Role |
|---|---|
source/lindir/api.d |
HTTP JSON API + static /admin SPA + OpenAPI serving |
admin/ |
Vue 3 admin UI (Vite → admin/dist) |
source/lindir/auth.d |
Bootstrap / API tokens, host enrollment auth, one-shot enroll tokens, account-policy login checks |
source/lindir/crypto/password.d |
PBKDF2-HMAC-SHA256 |
source/lindir/identity.d / policy.d |
Domain services + audit |
source/lindir/repo/* |
SQL repositories (users, hosts, fleet, account_policy, enroll_tokens, audit, …) |
source/lindir/oidc/* |
OpenID Connect IdP (RS256/JWKS, refresh, public/PKCE) |
source/lindir/webauthn/* |
Passkeys / WebAuthn (ES256, register + assert) |
source/lindir/ldap/* |
Read-only LDAP gateway (BER + bind/search) |
source/lindir/storage/* |
DB interface, memory, PostgreSQL, MySQL, migrations |
source/lindir/vpn/* |
Pluggable VPN (Tailscale, Headscale, WireGuard) |
source/lindir/fleet/* |
Native fleet (packages, labels, queries/scripts, energy) |
source/lindir/net/* |
Shared HTTPS JSON client (libcurl) |
cli/ |
lindir-admin Bearer API CLI |
client/ |
lindir-client (sync applies IdM + fleet packages + energy) |
sql/migrations/ |
Versioned schema (001 … 012_fleet_energy, …) |
Host login
Sync = cache refresh. Each successful lindir-client sync writes NSS files (/var/lib/extrausers/), policy (cache.json), and password hashes (cache-auth.json), and renews the offline login window for offlineMaxAgeSeconds (default 72 hours from the sync timestamp).
While that window is open, password login works with the Lindir server down: PAM verifies PBKDF2 against the sync cache and evaluates HBAC locally. The sync timer keeps renewing the window while the host can reach the server; if sync stops, login eventually ages out (fail closed).
When the server is reachable, PAM still tries online auth first (short timeout) so lockout / must-change / live deny apply. HTTP 401/403 never fall back to the cache. See Client.
| Concern | Needs server? |
|---|---|
| Enroll | Yes |
| Sync (refresh cache + renew TTL) | Yes |
| Password login within TTL | No (local cache) |
| Password login after TTL expiry | Yes (sync again) |
Naming
| Name | Meaning |
|---|---|
lindir |
Project / package / config dir (/etc/lindir) |
lindir-server |
Server daemon binary (dub build) |
lindir-client |
Host agent (cd client && dub build) |
lindir-admin |
Operator CLI (cd cli && dub build) |
Toolchain
- Compiler: LDC for production; DMD OK for local dev
- Build:
dub build(server →lindir-server),cd client && dub build,cd cli && dub build,cd admin && rm -rf node_modules && npm ci && npm run build - Dev DSN:
memory://or MySQL — see Storage - Operator guide: Operator handbook
- Client setup: Client
- Fleet: Fleet
- HTTP API: openapi.json (served at
GET /openapi.json) - Security: OWASP Controls
