Docs Reference
Run as a server
For a team that wants one shared instance, Partner Center Bridge also runs as a server:
Postgres, the API and web Docker images, and (for production) Kubernetes. For one technician on
one Windows machine, the Windows download is simpler: SQLite,
no server, no Docker. Both run the same API, EF Core model and workflow/operation catalog. Only
the hosting profile (Hosting:Profile), the persistence provider
(Persistence:Provider), and the auth defaults differ. Direct interactive tenant
sign-in is available only in the loopback-only Windows workbench; server deployments use partner credentials.
Windows program or server?
| Windows program (Local Workbench) | Server (Docker / Kubernetes) | |
|---|---|---|
| Best for | One technician on their own machine | A team sharing one instance and one audit trail |
| Database | SQLite in %LOCALAPPDATA%\PartnerCenterBridge | PostgreSQL |
| Sign-in | Local accounts (passkeys, password, TOTP), or no account on an already-secured PC | Your OIDC provider, or Local accounts (Auth:Mode=Local) |
| Network | Loopback only by default | Behind your ingress / reverse proxy |
| Microsoft connection | Direct tenant admin sign-in, or optional partner SAM/GDAP | Partner SAM/GDAP; no interactive direct tenant sign-in in this release |
| Install | Download, unzip, run | Published images on ghcr.io, or build from source |
Evaluation: docker-compose
One command brings up Postgres 16, the API, and the SPA with auth disabled, so you can click
through every screen without an identity provider. The published images
(ghcr.io/spillers-technology/partnercenterbridge-api and
-web) are public, so no clone or build is needed:
curl -LO https://raw.githubusercontent.com/Spillers-Technology/PartnerCenterBridge/main/docker-compose.ghcr.yml
docker compose -f docker-compose.ghcr.yml up
# Or, from a clone, build from source:
docker compose up --build
# Either way:
# SPA: http://localhost:8082
# API: http://localhost:5080 (Swagger at /swagger)
Compose is for evaluation and development. It uses obvious dev credentials and no operator auth - don't expose it.
To point the same stack at real tenants later, create a .env file next to
the compose file (see .env.example in the repo) and fill in the
PARTNER_* (and optionally EXCHANGE_*) values - compose picks
them up automatically.
Run the pieces directly
# API (needs a Postgres; connection string in appsettings.json)
dotnet run --project src/PartnerCenterBridge.Api
# SPA (proxies /api to http://localhost:5080)
cd web && npm install && npm run dev
Tests need no tenant at all - orchestration is covered against WireMock:
dotnet test
Connecting Microsoft on a server
A server uses a partner app registration, GDAP relationships, consent and
delegated permissions.
The Windows app's direct tenant sign-in flow is not available on the server.
See partner connection setup.
Supply the Partner section through environment variables, the compose
.env file, or the Kubernetes config and SOPS secret below, then seed the SAM token
once. From a clone, dotnet run --project src/PartnerCenterBridge.Api -- bootstrap-sam
runs the device-code bootstrap; the other options are in SAM bootstrap.
Production: Kubernetes (Kustomize + Flux + SOPS)
deploy/base holds the core manifests (Deployment, Service, Ingress, PVCs for the
Data Protection key ring and package store); deploy/overlays/production applies
environment specifics. Render to verify:
kubectl kustomize deploy/overlays/production
Configuration split
| Kind | Where | Examples |
|---|---|---|
| Non-secret config | configMapGenerator in the overlay |
Auth authority/audience, CORS origins, EXO cert path, notification format |
| Secrets | secrets.sops.yaml - encrypt with SOPS before committing |
Postgres connection string, Entra client secret, optional seed refresh token, notification webhook URL |
secrets.sops.yaml as a CHANGEME template.
Never commit real values unencrypted; with Flux + SOPS the encrypted file is decrypted
in-cluster at reconcile time.
What must persist
- Postgres - tenants, contracts, templates, deployments, workflow run history, and the encrypted SAM token live here.
- Data Protection key ring (
DataProtection:KeyRingPath) - loses this and the encrypted SAM token can't be decrypted; you'll need to re-bootstrap. - Package store - uploaded
.intunewintemplates. - Git sync working copy (
GitSync:LocalPath, default/git-sync) - only if git sync is enabled. Not strictly required to persist (it re-clones if missing) but avoids a full re-clone on every pod restart.
Operator auth
Two options, not just one -- full detail on both: Authentication.
- OIDC - point the
Authsection at your provider (authority + audience; tested with Authentik) and give the SPA matchingVITE_OIDC_AUTHORITY,VITE_OIDC_CLIENT_ID,VITE_API_BASE. The operator name claim (preferred_usernameby default) is what workflow run history records. Auth:Mode=Local- self-registered accounts, no IdP to stand up. SetAuth:Local:SigningKey(a secret) and, for passkeys,Auth:Local:Passkey:RelyingPartyId/Originsmatching the public hostname the SPA is served from.
Config snapshot git sync (optional)
Set GitSync:RepoUrl (and GitSync:Token, a secret) to mirror every
config snapshot capture to a real git repo. Unset by default -- see
Config Snapshots for the full key reference.
Exchange Online certificate
Mailbox workflows shell out to the EXO PowerShell V3 module with app-only certificate auth.
Set the Exchange section's AppId, then choose one of two ways to give
it the certificate:
Exchange:CertificateThumbprint(preferred on Windows) -- a certificate already imported, with its private key, intoCert:\CurrentUser\Myof the account the container/service runs as (or, failing that,Cert:\LocalMachine\My). No certificate secret is handled by the app at all.CertificatePath(+CertificatePasswordif the PFX is protected) -- mount the PFX and set the path. The password reachespwshonly over standard input, never written to a temporary file or passed on the command line.
The API container image already includes PowerShell and the ExchangeOnlineManagement module.
The Exchange organization to connect to is read from Microsoft Graph for each tenant (its
verified initial *.onmicrosoft.com domain, checked against the tenant's own Entra
id) the first time an Exchange operation runs against it, then cached; the Graph connection has
to work for that tenant. If the connection ever answers for a different organization than
expected, the operation is refused and nothing is changed.
Upgrades
EF Core migrations apply automatically at startup, so rolling a new image forward is the whole upgrade. Roll back by deploying the previous image (migrations are additive).