Skip to main content

Running Entrosity Edge

Entrosity Edge runs next to Entrosity Hub and Axis on the same host:

PartWhere
Web app (entrosity-edge.frontend)https://hub.entrosity.com/edge/, served by Caddy from the web image
API (entrosity-edge.backend, image ghcr.io/entrosity/edge-backend)https://hub.entrosity.com/edge/api/…; Caddy strips /edge, so the backend serves /api/v1 (browsers) and /api/connector/v1 (connectors)
DatabaseIts own PostgreSQL database (edge), migrated by the backend
Sign-inEntrosity Hub, product edge

The backend binary is edge-server:

edge-server serve run the HTTP API and the background jobs (default)
edge-server migrate up|down [N]|version|force N
database migrations (up also enables the edge_app
login when EDGE_APP_DATABASE_PASSWORD is set)
edge-server healthcheck exit 0 when /readyz answers (container health check)
edge-server release-keygen print a new connector release signing key pair:
EDGE_RELEASE_SIGNING_KEY (server) and
RELEASE_PUBLIC_KEY (connector builds)
edge-server version print the version

serve refuses to start against a database schema it was not built for: run migrate up first.

On the Hub, the product edge (roles tenant_admin, operator, viewer) is registered by the Hub's migration 0003 disabled: disabled products are hidden from the launcher and cannot be given to organizations. It is enabled once Edge is deployed; then platform admins turn it on per organization (Platform administration). The Hub must know Edge's token: PLATFORM_PRODUCT_TOKENS includes edge:<token>, the same value as EDGE_PLATFORM_TOKEN.

Configuration reference​

The backend is configured only through EDGE_* environment variables (entrosity-edge.backend/internal/config). Invalid values stop it at start with a list of every problem. The RETENTION section accepts one or two underscores: EDGE_RETENTION_EVENT_DAYS = EDGE_RETENTION__EVENT_DAYS.

Server​

VariableDefaultMeaning
EDGE_ENVdevdev, test or prod. prod refuses the development example secrets.
EDGE_PUBLIC_URLhttp://localhost:8082Edge's public address including the /edge prefix the proxy strips (https://hub.entrosity.com/edge). Connectors are given <EDGE_PUBLIC_URL>/api/connector/v1/ws, and the install commands shown with new enrollment tokens use it as SERVER_URL.
EDGE_HTTP_ADDR:8082API listener.
EDGE_METRICS_ADDR:9092Internal listener for Prometheus /metrics. Never expose it publicly; empty disables it.
EDGE_LOG_LEVELinfodebug, info, warn or error.
EDGE_CORS_ORIGINShttp://localhost:5176Allowed browser origins, comma separated, no wildcards or paths (production: https://hub.entrosity.com).
EDGE_TRUSTED_PROXIESnoneCIDRs or IPs allowed to set X-Forwarded-For (the proxy's network).
EDGE_API_RATE_PER_SECOND, EDGE_API_RATE_BURST20, 60Web API rate limit per client IP (an office behind one NAT address shares it).
EDGE_ENROLL_RATE_PER_MINUTE60Connector enrollments per client IP.
EDGE_CONNECTOR_DOWNLOAD_URLnoneWhere the connector MSI can be downloaded; shown as Download the connector installer with new enrollment tokens.

Database​

VariableDefaultMeaning
EDGE_DATABASE_URLnone (required)Connection URL. Production connects as edge_app.
EDGE_DATABASE_ROLEedge_appRole the pool switches to after connecting (SET ROLE), so row-level security applies even when connecting as the owner. Empty when the login already is the application role and cannot switch.
EDGE_DATABASE_MAX_CONNS20Pool size.
EDGE_DATABASE_STATEMENT_TIMEOUT30sUpper bound for any statement.
EDGE_APP_DATABASE_PASSWORDnoneRead by migrate up: enables the edge_app login with this password.

Secrets​

VariableMeaning
EDGE_JWT_SECRETRequired. Signs the one-minute live-update stream tokens (POST /auth/sse-token). At least 32 bytes, raw or base64 (openssl rand -base64 32). Users' access tokens come from the Hub.
EDGE_JWT_SECRET_OLDThe previous secret during a rotation (still accepted).
EDGE_MASTER_KEYOptional. Base64 of exactly 32 bytes (openssl rand -base64 32). Encrypts the controller PINs and keys the connector download links. Keep it: stored PINs cannot be read without it. Without it the server starts, but PINs cannot be set (master_key_missing) and connector self-update is off. An invalid value stops the server.
EDGE_MASTER_KEY_OLDPrevious master keys during a rotation, comma separated; they only decrypt (and still verify download links). Only with EDGE_MASTER_KEY.
EDGE_RELEASE_SIGNING_KEYOptional. Base64 Ed25519 private key (64 bytes) or 32-byte seed that signs connector releases, from edge-server release-keygen. Needed, with the master key, to publish and roll out connector releases.
EDGE_RELEASE_TOKENOptional. CI's bearer token for publishing connector releases, at least 32 characters. Unset: the publishing endpoint answers 404.

Sign-in on Entrosity Hub​

VariableDefaultMeaning
EDGE_PLATFORM_URLnone (required)The Hub's public origin (https://hub.entrosity.com): the issuer of product tokens and where browsers sign in.
EDGE_PLATFORM_INTERNAL_URLnone (required)The Hub's internal API, reached directly and not through the proxy (compose: http://platform:8081): signing keys and the access snapshot.
EDGE_PLATFORM_TOKENnone (required)Edge's bearer token on the Hub's internal API, at least 32 characters; edge:<token> in the Hub's PLATFORM_PRODUCT_TOKENS.
EDGE_PLATFORM_SYNC_INTERVAL30sHow often Edge pulls users, organizations and roles from the Hub (at least 1s).

Retention windows​

VariableDefaultMeaning
EDGE_RETENTION_EVENT_DAYS365Access log (events).
EDGE_RETENTION_JOB_DAYS90Finished connector jobs. Tenants may override it (7–730).
EDGE_RETENTION_AUDIT_DAYS365Audit log. Tenants may override it (30–3650).

0 or unset keeps the default.

Connector releases​

Edge connectors update themselves from releases stored in Edge (Connectors → Updates).

Setting it up​

  1. Generate the signing key pair once:

    edge-server release-keygen
    # production:
    docker compose run --rm --no-deps edge release-keygen

    It prints EDGE_RELEASE_SIGNING_KEY (keep it secret) and the matching RELEASE_PUBLIC_KEY.

  2. On the server set EDGE_MASTER_KEY, EDGE_RELEASE_SIGNING_KEY and EDGE_RELEASE_TOKEN (32+ characters) (in production in deploy/.env), and restart Edge.

  3. In the entrosity-edge-connector repository set the secret RELEASE_PUBLIC_KEY (compiled into the connector: builds without it refuse updates), the secret EDGE_RELEASE_TOKEN (the same value as the server's) and the variable EDGE_RELEASE_API, the Edge address (https://hub.entrosity.com/edge). Without EDGE_RELEASE_API builds are not published to Edge (CI workflows).

  4. Upgrade the connectors installed before self-update by hand once (Connectors → Updates).

Publishing​

CI publishes every connector build:

BuildVersionChannel
Tagged release vX.Y.ZX.Y.Zstable
Pre-release vX.Y.Z-suffixX.Y.Z-suffixbeta
Dev build (every push to main)0.0.<build>-dev.<commit>beta
curl -X POST -H "Authorization: Bearer $EDGE_RELEASE_TOKEN" \
-H "Content-Type: application/octet-stream" --data-binary @edge-connector.msi \
"https://hub.entrosity.com/edge/api/v1/admin/connector-releases?version=1.4.0&channel=stable&notes=release%20v1.4.0"

The body is the raw MSI (at most 64 MiB). Edge stores it in its database and signs it with EDGE_RELEASE_SIGNING_KEY.

AnswerMeaning
201New release.
200This version is already released with the same MSI (a retry).
409 release_existsThis version is already released with a different MSI.
409 signing_key_missing, master_key_missingEDGE_RELEASE_SIGNING_KEY or EDGE_MASTER_KEY is not set.
401Wrong token.
404EDGE_RELEASE_TOKEN is not set on the server.

Edge keeps the newest 10 releases of each channel, plus every version a connector still runs (the watchdog's rollback). Global admins list them with GET /api/v1/admin/connector-releases, which also returns the public_key connector builds must embed.

Rollout​

Every connector follows an update channel, Stable by default; tenant admins switch a connector to Beta (development builds) on the Connectors page. Stable connectors get the newest stable release, beta connectors the newest of the beta and stable releases.

Edge offers a newer version when a connector connects and every 5 minutes (releases.rollout): an update_agent job with signed download links valid 24 hours (GET /api/connector/v1/releases/{id}/msi?exp=&sig=, no login: the signed link is the authorization). A version is offered only to connectors that announce edge_update (builds with self-update), when no update job of the connector is open, and not again within an hour. The job and the installation: Edge connector protocol, Edge connector → Self-update.

Retention​

A daily retention.cleanup job deletes old data in batches of 10,000 rows:

DataKept
Access logEDGE_RETENTION_EVENT_DAYS, for every tenant. Events are stored in monthly partitions; a month is dropped as a whole once it is entirely older than the window, so events are kept between the window and the window plus one month.
Finished connector jobsThe tenant's Keep connector job history (days), else EDGE_RETENTION_JOB_DAYS (never less than 7 days).
Audit logThe tenant's Keep audit log (days), else EDGE_RETENTION_AUDIT_DAYS (never less than 30 days).
Enrollment tokens30 days after they expired or were revoked.
Hub sessions ended, used step-up tokensAn hour after they ended; once expired.

Tenant overrides are set under Tenant settings (Tenant settings, sites and audit log). edge_retention_rows_deleted_total{category} counts what was removed.

Background jobs​

The backend runs its jobs in PostgreSQL (River), so several replicas share them safely:

JobWhenWhat
controller.syncAfter every change, per controllerCompiles the controller's configuration and sends it as an edge.config.apply job, unless it already holds it. Bursts for one controller merge.
controller.reconcileEvery 5 minutesRe-queues controllers whose wanted version is not applied (connector was offline, lost job, a sync stuck in Syncing for over 20 minutes).
cardholder.validityEvery minuteResyncs the controllers of cardholders whose validity period started or ended.
holidays.horizonEvery hourResyncs the controllers that have a holiday newly within the 90 days a configuration carries (local to each controller's time zone).
releases.rolloutEvery 5 minutesOffers a newer connector release to online connectors that update themselves (Rollout). Does nothing without EDGE_MASTER_KEY and EDGE_RELEASE_SIGNING_KEY.
realtime.sweepEvery 30 secondsMarks connectors silent for 3 minutes offline; expires overdue connector jobs and resends unacknowledged ones (after 60 seconds).
events.partitionsDailyCreates the monthly event partitions two months ahead.
retention.cleanupDailySee Retention.

Database and migrations​

Edge has its own database. Migrations are embedded in the binary (entrosity-edge.backend/db/migrations) and applied by migrate up:

MigrationAdds
0001_initEdge's copy of the Hub's tenants, users and roles (tenants, users, tenant_memberships, Hub sync state, ended sessions, used step-up tokens), sites, the append-only audit_log, enrollment_tokens, connectors, jobs, and row-level security with the edge_app role.
0002_access_controlcontrollers (with wanted/applied versions and sync status), doors, readers, cardholders, cards, schedules, holidays, access_groups with their doors and members, the monthly-partitioned events log with its partition functions, and worker_watermarks.
0003_controller_pincontrollers.pin_enc: the controller's PIN, encrypted with EDGE_MASTER_KEY (NULL: no PIN).
0004_connector_releasesconnector_releases (connector MSIs with their channel, SHA-256, size and signature; global, without row-level security), and the connectors' update_channel, update_version and update_offered_at.
0005_controller_enabledcontrollers.enabled (default true) and the sync status disabled, which a controller has exactly while it is disabled.
  • Tenant isolation: every tenant table has a row-level security policy; the server runs as edge_app (NOBYPASSRLS). References between tenant tables are composite (tenant_id, id) keys, so a cross-tenant reference is impossible.
  • Append-only: edge_app cannot update or delete events (retention drops partitions) or audit_log (retention purges through a dedicated function).
  • Events whose time falls outside the monthly partitions land in events_default and are moved when their month's partition is created.

Monitoring​

  • GET /healthz (liveness) and GET /readyz (readiness: the database answers; it also reports the age of the copy of the Hub's data).
  • Prometheus metrics on EDGE_METRICS_ADDR: edge_ws_connections (connected connectors), edge_jobs_total{type,status}, edge_job_dispatch_seconds, edge_http_request_duration_seconds, edge_platform_syncs_total, edge_platform_sync_age_seconds, edge_sse_subscribers, edge_retention_rows_deleted_total.
  • Logs are JSON in prod.