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 forOne technician on their own machineA team sharing one instance and one audit trail
DatabaseSQLite in %LOCALAPPDATA%\PartnerCenterBridgePostgreSQL
Sign-inLocal accounts (passkeys, password, TOTP), or no account on an already-secured PCYour OIDC provider, or Local accounts (Auth:Mode=Local)
NetworkLoopback only by defaultBehind your ingress / reverse proxy
Microsoft connectionDirect tenant admin sign-in, or optional partner SAM/GDAPPartner SAM/GDAP; no interactive direct tenant sign-in in this release
InstallDownload, unzip, runPublished 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

KindWhereExamples
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
The repository ships 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 .intunewin templates.
  • 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 Auth section at your provider (authority + audience; tested with Authentik) and give the SPA matching VITE_OIDC_AUTHORITY, VITE_OIDC_CLIENT_ID, VITE_API_BASE. The operator name claim (preferred_username by default) is what workflow run history records.
  • Auth:Mode=Local - self-registered accounts, no IdP to stand up. Set Auth:Local:SigningKey (a secret) and, for passkeys, Auth:Local:Passkey:RelyingPartyId/Origins matching 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, into Cert:\CurrentUser\My of the account the container/service runs as (or, failing that, Cert:\LocalMachine\My). No certificate secret is handled by the app at all.
  • CertificatePath (+ CertificatePassword if the PFX is protected) -- mount the PFX and set the path. The password reaches pwsh only 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).