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-helperon 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
svault0held in the kernel keyring at runtime. The keyring's master passphrase is collected by the installer in step 5 and stored encrypted in thesettingstable; 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-reportoutput. Encryption is--trust-model alwaysbecause 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 issha256(machine-id + baseboard-serial); the tertiary issha256(baseboard-serial + cpu-id). The appliance refuses to install a.licwhose token set does not include the live host's primary token. - Seat enforcement is in
User::creating(): refuse a new user when noLocalLicenseis active, or whenUser::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-helperreturns a usable baseboard or system serial viadmidecode. The host can derive a secondary and (when/proc/cpuinfoexposes a model name) a tertiary token. The install gate requires at least one stronger-than-primary token to match. Copying/etc/machine-idfrom 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-helper—inspect,install,install-corp-ca,reload.sysadmin/machine-token-helper— reads baseboard / system serial viadmidecodefor the license install gate. PrintsUNKNOWNwhen 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-reportcommand 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.