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

  • Vault directory at /vault (configurable from the Disk Manager page). A plain directory — no ZFS required. It can sit on the system disk, a dedicated volume, or a network share (NFS/CIFS) mounted by the OS.
  • 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 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 up to three machine tokens. The primary is sha256(/etc/machine-id); the secondary is sha256(machine-id + baseboard-serial); the tertiary is sha256(baseboard-serial + cpu-id). The appliance refuses to install a .lic whose token set does not include the live host's primary 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.

Hardware binding

The license install gate compares the live host's machine tokens against the machine_tokens array embedded in the signed .lic payload. The behavior depends on which identifiers the live host can produce:

  • Strong binding — the helper sysadmin/machine-token-helper returns a usable baseboard or system serial via dmidecode. The host can derive a secondary and (when /proc/cpuinfo exposes a model name) a tertiary token. The install gate requires at least one stronger-than-primary token to match. Copying /etc/machine-id from one host to another is not enough to install the same license — the second host's baseboard serial will produce different secondary/tertiary hashes.
  • Weak binding — the helper returns UNKNOWN, is not installed, or fails (typical of VMs whose hypervisor exposes blank DMI fields). Only the primary token is compared. The install gate accepts a primary-only match and logs a warning. This is intentional: a legitimate blank-DMI VM install should not be refused.

If you need strong binding on a VM, configure the hypervisor to expose real (or stable per-VM) BIOS/board serial values. KVM/libvirt sets these via <sysinfo type='smbios'>; VMware sets them via SMBIOS.baseBoardSerialNumber in the .vmx.

Privileged helpers

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

  • sysadmin/cert-helperinspect, install, install-corp-ca, reload.
  • sysadmin/machine-token-helper — reads baseboard / system serial via dmidecode for the license install gate. Prints UNKNOWN when DMI is blank so the install falls back to weak binding rather than failing.
  • 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.