Benefits Agent — Setup and Operations#
The Agent is a self-hosted Windows Service that runs on each deployment target. It exposes an HTTP API that bmo calls to upload and activate packages in IIS. A built-in SPA dashboard lets operators manage installations, Windows services, and IIS app pools from a browser without needing the CLI.
Prerequisites#
- Windows Server with IIS installed (Web Server role enabled)
- .NET 10 Runtime (or higher)
- Write access to
F:\Temp(configurable) for staging uploaded packages - Write access to each environment's
BasePath(e.g.F:\Acceptatie,F:\Productie) - Inbound firewall rule on port
5100(or your configured port) - A server certificate if you want HTTPS or mTLS
Installation#
Publish or copy the Benefits.Agent binaries to a local directory, then run the install script from that directory:
The script registers and starts the service:
$binPath = Resolve-Path .\Benefits.Agent.exe | Select-Object -ExpandProperty Path
New-Service -Name Benefits-Mgmt-Agent -BinaryPathName $binPath -DisplayName "Benefits Management Agent" -StartupType Automatic
Start-Service -Name Benefits-Mgmt-Agent
The service runs under LocalSystem by default. If your deployment paths or SQL connections require a named account, change it in the Services MMC snap-in after installation.
Start, stop, restart:
Start-Service Benefits-Mgmt-Agent
Stop-Service Benefits-Mgmt-Agent
Restart-Service Benefits-Mgmt-Agent
Configuration#
All settings live in appsettings.json (and appsettings.Production.json for production overrides) next to the executable. Restart the service after any change.
Agent section#
"Agent": {
"Port": 5100,
"TempPath": "F:\\Temp",
"MaxRequestBodySizeBytes": 524288000,
"MonitoringInterval": "00:05:00",
"Name": "",
"WriteDeploymentRecords": true,
"ProtectedAppPools": []
}
| Setting | Default | Notes |
|---|---|---|
Port |
5100 |
Kestrel listen port |
TempPath |
F:\Temp |
Scratch space for uploaded zips. Must be writable by the service account. |
MaxRequestBodySizeBytes |
524288000 (500 MB) |
Max upload size for --via http deploys |
MonitoringInterval |
00:05:00 (5 min) |
How often StateMonitorService scans installations |
Name |
MachineName |
Agent identifier reported to the Orchestrator. Override when multiple agents share a hostname. |
WriteDeploymentRecords |
true |
Persist deployment audit records to the database |
ProtectedAppPools |
[] |
IIS app pools that management API calls are blocked from touching |
Environments#
One entry per deployment environment. The key (Acceptatie, Productie) must match the environment name used in bmo deploy.
"Agent": {
"Environments": {
"Productie": {
"BasePath": "F:\\Productie",
"CommandCenterUrl": "http://vmbenefitsccp1:8000",
"ConnectionStrings": {
"BenefitManager": "Data Source=localhost;Initial Catalog=BenefitManager;Integrated Security=True",
"CommandCenter": "Data Source=localhost;Initial Catalog=ReverseProxy;Integrated Security=True"
},
"NamingStrategy": "PerMajorMinor",
"AppPoolPrefix": null
}
}
}
| Setting | Default | Notes |
|---|---|---|
BasePath |
— | Root folder for this environment's deployments |
CommandCenterUrl |
— | Internal URL of the CommandCenter reverse-proxy for this environment |
ConnectionStrings.BenefitManager |
— | SQL connection for BenefitManager data |
ConnectionStrings.CommandCenter |
— | SQL connection for the reverse-proxy database |
NamingStrategy |
PerMajorMinor |
IIS App Pool naming convention. See pipeline-migration-bmo-package-push.md for the full strategy table. |
AppPoolPrefix |
null (uses env name) |
Override the prefix portion of the generated pool name |
Authentication#
The Agent accepts three inbound auth schemes simultaneously. At least one must be active.
ApiKey (default)
Callers set the X-Api-Key request header. Set the matching value in bmo via bmo config auth.
mTLS
"ServerCertificate": {
"Source": "Store",
"StoreLocation": "LocalMachine",
"StoreName": "My",
"Thumbprint": "AABB..."
},
"ClientAuthentication": {
"AllowedThumbprints": ["CCDD..."]
}
Source value |
Required fields |
|---|---|
Store (default) |
StoreLocation, StoreName, Thumbprint |
File |
FilePath, Password |
KeyVault |
KeyVaultUrl, CertificateName |
AllowedThumbprints is the allowlist of Orchestrator client certificate thumbprints. An empty array disables mTLS client authentication.
Entra ID (JWT)
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id"
}
The JWT scheme is only registered when ClientId is non-empty. Leave all AzureAd fields empty to disable it.
Package Store (blob, for --via blob deploys)#
"PackageStore": {
"AccountUri": "https://<storageaccount>.blob.core.windows.net/",
"ContainerName": "deployments"
}
When AccountUri is set, the Agent downloads packages from blob storage for --via blob deploys. If the URL contains a query string it is treated as a SAS URL; otherwise DefaultAzureCredential (managed identity) is used. Leave AccountUri empty for HTTP-only mode.
Service Bus transport (optional)#
"Agent": {
"ServiceBus": {
"Namespace": "my-namespace.servicebus.windows.net",
"StateTopic": "agent.state.v1",
"SnapshotContainerUrl": "https://...",
"CommandsTopic": "deploy.commands.v1",
"RepliesTopic": "deploy.replies.v1",
"AgentCommandsTopic": "agent.commands.v1",
"AgentRepliesTopic": "agent.replies.v1"
}
}
Leave Namespace empty (the default) to use HTTP-only transport. When set, the Agent:
- Starts a
ServiceBusDeployWorkerthat consumes deploy commands fromCommandsTopicand publishes deploy replies toRepliesTopic. - Starts an
AgentCommandsConsumerthat consumes operator commands (service control, IIS, uninstall, certificate operations, status queries) fromAgentCommandsTopicand publishes replies toAgentRepliesTopic.
The agent creates a durable per-agent subscription agent-{Name} on AgentCommandsTopic with a SQL filter targetServer = '{Name}', so only commands addressed to this agent are delivered.
In SB mode the agent host does not need an inbound firewall rule from the Command Center. Only outbound HTTPS (port 443) to <your-namespace>.servicebus.windows.net is required. The HTTP server still runs for the local management web UI and for direct deployments that don't use the SB path.
The Orchestrator must be configured with a matching ServiceBus.Namespace.
Production log path override#
appsettings.Production.json overrides the log path and adds a Windows EventLog sink:
"Serilog": {
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "D:\\Logs\\Benefits.Agent\\agent-.log",
"rollingInterval": "Day",
"retainedFileCountLimit": 30
}
},
{
"Name": "EventLog",
"Args": { "source": "Benefits.Agent", "logName": "Application" }
}
]
},
"Agent": {
"TempPath": "D:\\BenefitManager\\Temp"
}
Deploy both files side-by-side. The Production file overrides only the keys it declares; everything else comes from the base appsettings.json.
First-run smoke test#
Start-Service Benefits-Mgmt-Agent- Open
http://<host>:5100/health— expectHealthy(plain text, no credentials needed). - Open
http://<host>:5100/in a browser — the management dashboard should load. - Call
GET http://<host>:5100/api/installationswithX-Api-Key: <key>— on a fresh box it returns[].
Web dashboard#
Open http://<host>:5100/ in a browser. The sidebar has five tabs:
Dashboard#
Lists all known installations. Each row shows environment, version, service health, and IIS pool health. The page auto-refreshes every 5 seconds.
Per-installation actions:
- Start / Stop / Restart — controls the Windows Service for that version.
- Recycle / Start / Stop — controls the IIS App Pool.
- Remove — uninstalls the service, removes the IIS application, deletes the version folder, and unregisters the entry from
installations.json. This is irreversible.
Deploy#
Step-by-step wizard to trigger a new deployment from the browser. Equivalent to running bmo deploy --via http from the Orchestrator.
Audit#
Read-only log of past deployment and management actions recorded on this agent.
Security#
Certificate management — view, upload, download, and delete certificates managed by the Agent.
Settings#
Under development.
Operations#
Logs#
| Environment | Path |
|---|---|
| Default | C:\Logs\Benefits.Agent\agent-<date>.log |
| Production | D:\Logs\Benefits.Agent\agent-<date>.log + Windows Application EventLog |
Logs rotate daily. The production config retains 30 days of rolling files.
Installation registry#
The Agent tracks known installations in installations.json next to the executable. On startup, if the file is missing or empty, StateMonitorService performs a best-effort filesystem crawl and rebuilds it automatically.
On each monitoring tick (default every 5 minutes) the service:
- Checks the health of every registered installation (service status, IIS pool status).
- Logs a warning for any component in
StoppedorErrorhealth. - Removes registry entries whose folder is physically missing (ghost entries).
- Publishes an
AgentStateSnapshotfor the Service Bus integration (no-op when Service Bus is not configured).
Health endpoint#
GET /health — no credentials required. Returns Healthy, Degraded, or Unhealthy as plain text with HTTP 200 or 503. Authenticated callers receive JSON with per-check detail.
API endpoints reference#
All /api/* routes require authorization.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health status. Anonymous: plain text. Authenticated: JSON. |
GET |
/api/installations |
List all known installations on this agent. |
POST |
/api/deploy |
Upload zip (multipart/form-data). Returns 202 Accepted with a statusUrl. |
POST |
/api/deploy/reference |
Deploy by blob reference. Returns 202 Accepted. |
GET |
/api/deploy/status/{id} |
Poll job status by GUID from the accept response. |
POST |
/api/remove |
Uninstall service + IIS app + delete folder + unregister. |
POST |
/api/services/{env}/{version}/action |
Start, Stop, or Restart the Windows Service for a version. |
POST |
/api/iis/pool/{poolName}/action |
Recycle, Start, or Stop an IIS App Pool. |
POST |
/api/iis/pools/cleanup |
Delete IIS app pools no longer in the registry. |
Troubleshooting#
| Symptom | Likely cause | Fix |
|---|---|---|
| Service fails to start | Port 5100 already in use | Change Agent.Port in appsettings.json |
401 Unauthorized |
ApiKey mismatch |
Ensure Authentication.ApiKey matches the value in bmo config show |
| Server cert not loaded | Wrong thumbprint or store | Check with Get-ChildItem Cert:\LocalMachine\My and verify the thumbprint |
Blob deploy fails with 403 |
Managed identity not granted | Assign Storage Blob Data Reader to the service account on the storage account, or switch to a SAS URL |
| Ghost installations in dashboard | Files deleted manually without using Remove | They clear automatically on the next monitoring tick (within 5 minutes) |
Upload returns 413 |
Package larger than MaxRequestBodySizeBytes |
Increase the setting or use --via blob transport |
| Entra ID auth not working | AzureAd.ClientId is empty |
Fill in TenantId and ClientId; the JWT scheme activates only when ClientId is set |
Operator checklist#
- [ ]
Install.ps1run; service appears in Services as Benefits Management Agent, startup type Automatic - [ ]
Agent.Port,TempPath, and allEnvironmentsentries updated inappsettings.json - [ ]
Authentication.ApiKeyset to a non-empty secret - [ ]
appsettings.Production.jsondeployed alongsideappsettings.jsonon production servers - [ ] For HTTPS/mTLS:
ServerCertificate.Thumbprintset and cert present in the Windows cert store - [ ] For mTLS client auth:
ClientAuthentication.AllowedThumbprintspopulated with the Orchestrator's cert thumbprint - [ ] For blob deploys:
PackageStore.AccountUriset and managed identity or SAS configured - [ ]
GET /healthreturnsHealthy - [ ] Dashboard loads at
http://<host>:<port>/ - [ ]
GET /api/installationsreturns with API key header - [ ] First deployment from
bmoor the web Deploy wizard succeeds