On-Premises Server
A Consystence site server is the Site-mode build of the Consystence server — the same source tree as the cloud organisation runtime, published as a Site-baked artifact. It is the on-premises tier of the deployment model: it serves operators in the browser, holds the site’s device model and runtime state in Orleans grains, and talks down to the PLCnext edge runtime over gRPC. It is built for facilities with intermittent or no internet connectivity — a CHPP, a mine site, a water-treatment plant — where operator control must keep working whether or not the cloud is reachable.
This page covers what an on-prem site server actually is today, how to install and run it under systemd, and how it joins the cloud. For cloud-hosted deployments see Azure deployment; for the edge box see edge deployment.
What “on-prem” means today
Section titled “What “on-prem” means today”The same source tree publishes as either a Cloud org manager or a Site server, but the mode is baked into the artifact at dotnet publish (-p:DeploymentMode=Site|Cloud, defaulting fail-closed to Site) and is never read from configuration — a Site binary cannot be flipped to Cloud at runtime. A site server:
- Runs
ServerMode.Site— the provisioning and org-management endpoints (/api/provision/*and the like) are guarded off and return 404. The audited configuration, API-key, and sync-key admin surfaces (/api/admin/config*,/api/admin/api-keys,/api/admin/sync-keys,/api/admin/orgs/{slug}/config*) are served locally, so site-PIN admins can manage configuration and keys while offline, and the operator-initiated restore surfaces (/api/admin/orgs/{slug}/model-restoreand/api/admin/orgs/{slug}/tenant/restore) are reachable on a site server. - Serves one organisation. The org is reached at its subdomain (
{slug}.consystence.com); a site is a path under that org (/sites/{code}), never a separate domain. See the org and site model. - Persists Orleans grain state to PostgreSQL 16 and serves operators over SignalR with the server-driven UI.
Single-silo Orleans is the on-prem default
Section titled “Single-silo Orleans is the on-prem default”An on-prem site server runs a single Orleans silo. That is the supported on-prem topology today.
- Clustering defaults to localhost membership — a single silo has no membership table to coordinate.
- The PostgreSQL ADO.NET provider backs grain storage and reminders — the durability layer for grain state and durable scheduled callbacks. It also provides opt-in multi-silo membership (
Clustering:Mode=AdoNet, with a mandatory per-siloClustering:AdvertisedIp) alongside SignalR silo-affinity placement. - Multi-silo clustering is the cloud’s production serving posture, not the on-prem default. The clustering and affinity code is shared across both artifacts, but an on-prem site’s serving posture for it — shared DataProtection keys, connection affinity under sustained load, NTP/clock-skew prerequisites — is still hardening. Run single-silo on-prem until that lands.
Prerequisites
Section titled “Prerequisites”- A Linux x64 host (physical or VM) on the site’s IT/OT-segmented network.
- .NET 10 SDK on a build host — only for the manual-publish fallback; the packages are self-contained.
- PostgreSQL 16, reachable from the server host, with a dedicated database and login.
- A TLS-terminating reverse proxy (or Kestrel TLS certificates) to serve the org subdomain over HTTPS.
Install from the Linux package
Section titled “Install from the Linux package”The site server ships as .deb/.rpm packages (consystence-site-server) and a tarball with an install.sh installer. All three lay down the same layout: the self-contained app at /usr/lib/consystence/site-server (no .NET runtime prerequisite), the consystence-site-server.service systemd unit, the consystence system user, and a config template at /etc/consystence/site-server.env.example.
sudo apt install ./consystence-site-server_<version>_amd64.deb # or dnf install the .rpm, # or sudo ./install.sh from the tarballsudo cp /etc/consystence/site-server.env.example /etc/consystence/site-server.envsudo chown root:consystence /etc/consystence/site-server.envsudo chmod 0640 /etc/consystence/site-server.env# fill in the connection strings (see Configuration below), then:sudo systemctl enable --now consystence-site-serverjournalctl -u consystence-site-server -fManual publish (fallback)
Section titled “Manual publish (fallback)”If you cannot use the package, publish a self-contained, framework-independent linux-x64 build (deployment mode defaults to Site at publish):
dotnet publish backend/Server/Consystence.Server/Consystence.Server.csproj \ -c Release \ -r linux-x64 \ --self-contained true \ -o ./stage/site-serverThis produces a native Consystence.Server apphost. Copy the output to /usr/lib/consystence/site-server on the target host (ensure the frontend static assets under wwwroot/ are present), create the consystence system user, and install the unit below — mirroring the package layout keeps hand-rolled installs supportable.
Run under systemd
Section titled “Run under systemd”Whether installed by the package or by hand, the service runs under the shipped consystence-site-server.service unit. Keep secrets (connection strings, keys) in the EnvironmentFile, not in the unit. Two settings are load-bearing and easy to miss when hand-rolling: StateDirectory=consystence (systemd creates /var/lib/consystence, where the server persists server-state.json — the write-once install id and activation state) and DataProtection__KeyPath (the service user has no home directory to fall back to, so without a pinned key ring the site-PIN JWT signing key, cookies and antiforgery tokens reset on every restart).
# /usr/lib/systemd/system/consystence-site-server.service — as shipped in the package[Unit]Description=Consystence site serverAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=consystenceGroup=consystenceWorkingDirectory=/usr/lib/consystence/site-serverExecStart=/usr/lib/consystence/site-server/Consystence.ServerEnvironment=ASPNETCORE_ENVIRONMENT=ProductionEnvironment=ASPNETCORE_URLS=http://0.0.0.0:8080Environment=DOTNET_gcServer=1Environment=Server__StateFilePath=/var/lib/consystence/server-state.json# Persistent DataProtection key ring — without it the site-PIN JWT signing key,# cookies and antiforgery tokens reset on every restart.Environment=DataProtection__KeyPath=/var/lib/consystence/dp-keys# systemd creates /var/lib/consystence (state file + DP keys) owned by User= on start.StateDirectory=consystence# Bootstrap config (connection strings and similar allow-listed settings) as Key__Sub=value env vars.EnvironmentFile=-/etc/consystence/site-server.env# Allow binding :443/:80 without root when in-process TLS is enabled.AmbientCapabilities=CAP_NET_BIND_SERVICERestart=on-failureRestartSec=5
[Install]WantedBy=multi-user.target# /etc/consystence/site-server.env — env vars map to config with `__` as the section separatorConnectionStrings__Postgres=Host=db.internal;Database=consystence;Username=consystence;Password=CHANGE_MEConnectionStrings__OrleansStorage=Host=db.internal;Database=consystence;Username=consystence;Password=CHANGE_MEEnable and start it:
sudo systemctl daemon-reloadsudo systemctl enable --now consystence-site-serversudo systemctl status consystence-site-serverjournalctl -u consystence-site-server -fThe default listen address is http://0.0.0.0:8080; front it with a reverse proxy that terminates TLS for the org subdomain, or enable in-process TLS (the unit grants CAP_NET_BIND_SERVICE so Kestrel can bind :443 without root).
Configuration
Section titled “Configuration”Environment variables (including the systemd EnvironmentFile) are restricted to a bootstrap allow-list: connection strings, the listen address and environment, Kestrel, clustering identity, the state and DataProtection paths, Server__PublicUrl / Server__StateFilePath, and the API-key pepper. Everything else comes from appsettings.json or the server’s audited configuration store (encrypted secret rows, reloaded cluster-wide) — a non-bootstrap setting supplied as an environment variable is ignored. In particular, set Server:CloudUrl in appsettings.json or the config store: a Server__CloudUrl env var is ignored and activation fails with Cloud URL not configured. Deployment mode is not a setting — the published site artifact is Site-mode regardless of configuration, and a Server:Mode / Server__Mode entry is inert. The key settings are the PostgreSQL connections and the URL of the site’s cloud cell:
{ "Server": { "CloudUrl": "https://acme.consystence.com" }, "ConnectionStrings": { "Postgres": "Host=db.internal;Port=5432;Database=consystence;Username=consystence;Password=...", "OrleansStorage": "Host=db.internal;Port=5432;Database=consystence;Username=consystence;Password=..." }}ConnectionStrings:Postgresis the application/auth database;ConnectionStrings:OrleansStorageis the Orleans grain-storage and reminders database (they may point at the same physical database). On the collapsed single-database layout, oneConnectionStrings:ConsystenceDbreplaces both. Without a resolvable Orleans connection string the silo silently falls back to in-memory grain storage — no durability across restarts.Server:CloudUrlis the base URL of the org’s cloud cell ({slug}.consystence.com) — not the account console. The site posts activation to{CloudUrl}/api/provision/site-activationand syncs historian and model data to the same cell; the cell mediates with the account control plane, which serves neither surface itself.
On a fresh install the server starts in setup mode and waits for a one-time activation code issued from the account console: POST /api/setup/site-activation relays { code, installId, siteCode, siteName } to the cloud cell and applies the returned bundle, establishing the server’s identity. There is no multi-step wizard or configuration import — the Fresh Install Wizard was retired, and code-keyed activation is the sole onboarding path. Authentication is the MultiAuth selector — see offline operation below for what works without the cloud.
Joining the cloud
Section titled “Joining the cloud”A site server is not enrolled as a cell. It runs its own Orleans cluster (single-silo by default) and joins the cloud as a synchronising peer:
- The account control plane issues a one-time activation code for the site — codes are site-bound against the tenant’s declared-site registry — and holds the org’s identity, entitlement, and elevated roles (Owner/Admin). It does not hold the plant model: devices, scenes, electrical topology, and device types — the
tenant.yamlsurface — are site-authored and applied to both the site server and its cloud cell. - The site server redeems that code through its cloud cell: the cell forwards only
{ code, installId }to the account under its own enrolment pull credential, uses the submittedsiteCode/siteNameto create or join the site, mints the site→cloud sync key (bound to{orgSlug}/{siteCode}), and returns the activation bundle. The sync key is an output of activation, never the credential the site presents to enrol. The site thereafter synchronises upward through dedicated sync grains — historian samples always, and site-model changes once enabled (model sync is opt-in during beta:Sync:ModelSyncEnabled, default off). In the other direction, an operator can restore selected model scopes from the cloud’s custody copy back onto the site — restore is always operator-initiated and site-pulled, shows a full preview (including removals) and requires exact confirmation before anything is applied; the cloud never pushes model changes down automatically. There are no cross-cluster Orleans calls: the site cluster and the cloud cluster are hard-isolated by design and communicate only through the sync surface.
flowchart TB
subgraph cloud["Cloud (not yet GA)"]
acct["Account control plane<br/>account.consystence.com<br/>activation codes + tenant provisioning"]
cell["Cloud cell<br/>{slug}.consystence.com"]
end
subgraph site["On-premises site server — Site-mode binary"]
silo["Orleans silo<br/>(single-silo)"]
pg[("PostgreSQL 16<br/>grain storage + reminders")]
silo --- pg
end
cell -->|redeem code under pull credential| acct
ops["Operators (browser)"] -->|SignalR| silo
silo -->|gRPC| edge["PLCnext edge runtime"]
silo <-->|"activation → sync key<br/>historian sync + opt-in model sync"| cell
This is distinct from the cell-mediated provisioning of cloud-hosted orgs — see cell topology and the provisioning API for the control-plane side.
Licensing
Section titled “Licensing”Licences are issued by the account control plane and cell-mediated; entitlement is bound to provisioned identity, not hardware. An org carries a tier — a rung on the integrator (development) or enterprise (operating) tier ladder, with Basic as the null-tier free org — plus per-seat SKU pools. For a site that loses connectivity, the cloud issues an account-signed (ECDSA) offline licence the server verifies against the published JWKS, so the site keeps running while disconnected.
Licence enforcement is degrade-not-kill and, critically, never gates operator control writes. An expired or unreachable licence may restrict cloud-tier or administrative features; it does not stop an operator commanding equipment through a running site server. See licensing for the full model.
Offline and degraded operation
Section titled “Offline and degraded operation”A site server is designed to keep operating when the cloud is unreachable:
- Authentication falls back to the locally issued site-PIN JWT (
LocallyIssuedJwt) for operators, since CIAM bearer validation againstid.consystence.comneeds cloud reachability. Tenant API keys (csk_prefix) remain available. See the auth model. - Field data continues to flow: the edge runtime buffers store-and-forward over SQLite and replays to the site server on reconnect.
- Licensing runs on the cached offline licence until its expiry, degrading rather than blocking control.
Verify the install
Section titled “Verify the install”# Service is upsystemctl is-active consystence-site-server.service
# Local health probe (through the reverse proxy or Kestrel directly)curl -s http://127.0.0.1:8080/api/setup/statusThe setup/status response reports the server mode, version and activation state — confirm it shows Site mode and the expected org identity. Endpoint shapes are in the API reference.
Next steps
Section titled “Next steps”- Edge deployment — the PLCnext border gateway and edge runtime that feed this server.
- Azure deployment — the cloud-hosted alternative.
- Architecture overview — where the site server sits in the four-tier model.
- PLC communication — how field data reaches the site server.