Separate deployments on one host — one per customer, or one per environment. Not a cluster: these
are independent gateways with their own databases and their own traffic. For why several instances
against one database does not work, see Clustering.
One name, two products
--tenant is the whole idea: the same word names the same instance to both installers. One name
per customer, two products.
On update it is a lookup, not a layout
This is the part that prevents the expensive mistake.
On update, service and rollback, --tenant finds the install with that name and uses its own
paths, unit, account and ports.
Without that, an upgrade aimed by forgetting a flag would stop a unit that may not exist, replace a
directory belonging to another instance, and enable a second service against the same database.A unit whose WorkingDirectory= names a different install is refused, before anything is stopped.
Ports
Give a band and the installer takes the next free listener port, so instance two does not collide with
instance one. --http-port and --ops-port are set per instance too.
Both installers read the ports back out of the environment file on update, so an instance on
non-default ports stops failing its own verify at the end of every upgrade.
A virtualhost
Puts the instance under a per-customer home: /home/websmsc/gateway, with the panel at
/home/websmsc/panel.
What it buys: both generated units mount a tmpfs over /home and bind only that one
virtualhost back in, so a gateway cannot see any other customer’s home directory.
--user is required here, and it is the step most easily missed. A home directory is 0750 and
owned by its own account, and the installer will not chown a directory that already exists — that
guard is deliberate, so an install can never rewrite the ownership of everything already in someone’s
home.So a service running as any other account simply cannot traverse the virtualhost. ProtectHome=tmpfs
and BindPaths= make the directory visible inside the unit’s namespace; neither changes a
permission bit.The symptom does not name the cause: the panel dies at npm ci with can't cd to /home/<name>/panel/app, and the gateway registers a unit that then cannot read its own configuration.
Pass the owning account to both installers.
Under --vhost, the panel’s CDR_EXPORT_DIR must also sit inside a BindPaths= root. That failure
is silent — exports are prepared and then cannot be read.
--vhost alone, without --tenant, means something different — do not use it for a new tenant.
It keeps an older flat layout, PATH/{app,conf,data}, instead of PATH/panel.That compatibility exists so panels installed under /home/<name> before --tenant are reached
exactly as before, and an upgrade never relocates a running install. On a new tenant it silently
puts the gateway and the panel in different layouts — the gateway under /srv/fireflo/<name>/gateway
and the panel loose in /home/<name> — after which update --tenant <name> no longer finds the
panel. Give both flags together.
The three directory flags — --app-dir, --conf-dir, --data-dir — still win over either scheme
wherever they are given.
What each instance needs of its own
Removing a tenant
There is no uninstall verb. Both installers offer check, install, update, service and
rollback — removal is manual, which is why the units are worth enumerating rather than guessing at.
A tenant is four systemd units, not two. The gateway writes three:
The logclean timer is the one that gets left behind. Remove only the main unit and it stays
enabled, firing every day against a log directory that no longer exists.
Look before removing — this changes nothing:
Then the units, all four:
Then the directories — whichever layout this tenant used:
Loaded: not-found alongside Active: failed is normal here, not a fault. It is systemd’s
runtime memory of a unit whose file has gone; reset-failed clears it. Equally, a status=143 exit
in the journal is 128 + SIGTERM — a clean stop, not a crash.
What this deliberately leaves alone
The database. A tenant’s routing, vendors, listeners, logins, call records and balances live
there, not in the install directory — which is what makes a reinstall safe, and what makes dropping
the database a separate decision:
The service account, which may be shared with other services or be a real customer login. Remove
it by hand only once you are sure nothing else uses it.
Firewall rules, if the install used --firewall. Check sudo ufw status numbered for this
tenant’s SMPP range and HTTP port.
Reinstalling rather than removing
Relocating an instance is not a mv. fireflo.env records its own absolute paths, and only some are
rewritten by later verbs — FIREFLO_CONF_DIR, FIREFLO_DATA_DIR and QUARKUS_LOG_FILE_PATH are
written by install alone. A moved install keeps writing its call records and logs to the old
location while the new unit’s ReadWritePaths= covers only the new one.
Remove and reinstall instead. The database survives, so the fresh install omits --bootstrap — it
refuses on a database that already has a routing table, a worker or a login.