General Architecture

A bird's-eye view of how the sos-vault appliance is put together. This is reference material for operators evaluating, integrating, or auditing the appliance.

Container layout

Everything ships as docker compose services in /opt/sos-vault/docker-compose.yml:

  • nginx — TLS termination, reverse proxy to php-fpm. Mounts the cert directory from the host. Reloaded by sysadmin/cert-helper on a new cert install.
  • app — PHP 8.4 / Laravel 12 / Filament 4 / Livewire 3. Runs the web tier, queue workers, and scheduler.
  • db — persistent data store. Schema managed via php artisan migrate.
  • redis — cache + queue + session store.
  • ollama (optional) — local LLM for the bot. Skippable during install.

Storage

  • ZFS pool sosvault mounted at /vault. Compression lz4, atime=off, xattr=sa, ashift=12. The pool can grow via zpool add from the Disk Manager page.
  • System trust store at /usr/local/share/ca-certificates/ — corporate root CAs land here via the Certificate Manager page.
  • App-level encrypted storage under the vault for case artifacts.

Key management

  • Master vault key: GPG keyring named svault0 held in the kernel keyring at runtime. The keyring's master passphrase is collected by the installer in step 5 and stored encrypted in the Wave settings table; it is never written to .env.
  • License verification keyring: config('license.gpg_home_verify'). Holds the SaaS-side public half only — signing keys never leave the SaaS build host.
  • Support recipient keyring: the same verify keyring doubles as the recipient for sos-vault:capture-server-report output. Encryption is --trust-model always because the appliance keyring has no signed trust chain.

Licensing

  • Licenses are GPG-signed JSON payloads. The appliance verifies via LicenseGeneratorService::verify() against the public keyring described above.
  • Each license binds to one or more machine tokens. The primary token is sha256(/etc/machine-id). The appliance refuses to install a .lic whose token set does not include the live host's token.
  • Seat enforcement is in User::creating(): refuse a new user when no LocalLicense is active, or when User::count() >= seats.
  • Renewals extend from the previous expiry, not from "now" — renewing early never loses you days.

Privileged helpers

The PHP tier never invokes zpool, openssl against live cert paths, or docker exec directly. Every privileged verb goes through a wrapper script under sysadmin/:

  • sysadmin/zfs-helperstatus, capacity, add, mount-nas, umount-nas.
  • sysadmin/cert-helperinspect, install, install-corp-ca, reload.
  • sysadmin/init.sh — one-shot GPG keyring bootstrap on install.

Each helper is scoped by a narrow /etc/sudoers.d/ fragment that grants www-data NOPASSWD on only the verbs the helper invokes and nothing else.

What leaves the appliance

  • Outbound HTTPS to the Customer Portal for module downloads (operator-initiated).
  • Optional outbound HTTPS for LLM model pulls.
  • Nothing for licensing. Nothing for telemetry. The sos-vault:capture-server-report command produces an encrypted file for support — you choose if and when to send it.

Branch model

The codebase ships two git branches. master is the SaaS build; appliance differs by one line in config/product.php ('saas''appliance'). All gating logic lives on master and propagates to appliance via merge. Runtime guards check config('product.type') rather than environment variables, so tests can flip product type per case.