Appendix
Environment Variable Reference
The .env file in the repository's quick-start/ folder configures all the
scripts. Copy
.env.example
to .env and edit as needed before running run.ps1. The defaults match the
local Docker stack.
Passwords (KEYCLOAK_ADMIN_PASSWORD, APP_DB_PASSWORD,
POSTGRES_APP_PASSWORD, SECURITY_DB_PASSWORD, POSTGRES_SECURITY_PASSWORD)
may be left empty: run.ps1 and cleanup.ps1 prompt for the ones they need,
with the input masked. Set them in the file only for unattended runs.
Identity provider
| Variable | Default | Description |
|---|---|---|
PROVIDER | keycloak | Identity provider for the machine client: keycloak |
KEYCLOAK_BASE_URL | http://localhost:8080 | Keycloak admin API base URL (bootstrap; Keycloak only) |
KEYCLOAK_ADMIN_USER | admin | Keycloak master admin user |
KEYCLOAK_ADMIN_PASSWORD | (empty) | Keycloak master admin password — used when PROVIDER=keycloak; prompted when empty |
KEYCLOAK_REALM | edfi | Realm holding the machine client |
Machine (service-account) client
| Variable | Default | Description |
|---|---|---|
MACHINE_CLIENT_ID | edfiadminapp-machine | Client id of the service account |
MACHINE_CLIENT_SECRET | edfi-machine-secret-456 | Client secret used to request tokens |
MACHINE_AUDIENCE | edfiadminapp-api | Must equal the Admin App's AUTH0_CONFIG_SECRET.MACHINE_AUDIENCE |
MACHINE_USERNAME | quick-start-machine | Username of the machine user seeded in the Admin App database |
Admin App database
Used by bootstrap.ps1 to seed the machine user and by cleanup.ps1.
| Variable | Default | Description |
|---|---|---|
DB_ENGINE | mssql | Database engine: mssql or pgsql |
DATABASE_NAME | sbaa | Admin App application database name |
APP_SQL_SERVER | tcp:localhost,1433 | SQL Server hosting the Admin App application database. May be a remote server, including a managed Azure SQL Database (see Managed Azure SQL Database) |
APP_DB_USERNAME | edfi_adminapp | SQL Server login for the dedicated least-privilege app user created by the Windows installer (install-all.ps1 -AppDbPassword) |
APP_DB_PASSWORD | (empty) | Password for APP_DB_USERNAME — used when DB_ENGINE=mssql; prompted when empty. The scripts deliberately never connect as sa |
POSTGRES_APP_PASSWORD | (empty) | PostgreSQL app-user password — used when DB_ENGINE=pgsql; prompted when empty |
POSTGRES_HOST | localhost | PostgreSQL host |
POSTGRES_PORT | 5432 | PostgreSQL port |
POSTGRES_APP_USER | edfiadminapp | PostgreSQL user |
USE_POSTGRES_DOCKER | false | true: run psql inside the edfiadminapp-postgres Docker container |
Quick start (Admin App API)
| Variable | Default | Description |
|---|---|---|
API_BASE_URL | https://localhost/adminapp-api/api | Admin App API base URL (through the reverse proxy) |
TOKEN_URL | https://localhost/auth/realms/edfi/protocol/openid-connect/token | Your issuer's token endpoint (see Finding your token endpoint) |
OAUTH_SCOPE | login:app | OAuth scope requested for the machine token (Keycloak: login:app) |
TEAM_NAME | Quick Start | Team to create |
ENVIRONMENT_NAME | Ed-Fi ODS/API v7.3 | Environment to create |
ENVIRONMENT_LABEL | QuickStart | Environment label |
ADMIN_API_URL | https://localhost/AdminApi | ODS Admin API URL |
ODS_API_DISCOVERY_URL | https://localhost/WebApi | ODS/API discovery URL |
TENANT_NAME | default | Ed-Fi tenant to create under the environment |
ADMIN_USERNAME | (empty) | Username of the human bootstrap admin. When set, quick-start.ps1 also adds that user to the team (Tenant admin role) so the Applications and Profiles pages work for them; leave empty to skip |
ODSS_JSON | (two sample instances) | JSON array of ODS instances to attach; ids must match EdFi_Admin.dbo.OdsInstances on the target ODS/API |
SKIP_CERTIFICATE_CHECK | true | true: skip TLS validation (local self-signed certificates) |
Claim set copies (EdFi_Security)
Used by copy-claimsets.ps1 to copy the built-in claim sets in the ODS/API's
EdFi_Security database. EdFi_Security is a different database from the
Admin App's — it can even run on a different engine (SECURITY_DB_ENGINE,
defaulting to DB_ENGINE). SQL Server uses its own SECURITY_DB_USERNAME /
SECURITY_DB_PASSWORD login (the APP_DB_* login has no rights there), or
Windows integrated authentication with
SECURITY_USE_INTEGRATED_SECURITY=true; PostgreSQL uses the
POSTGRES_SECURITY_* values, each falling back to the app-side POSTGRES_*
value above when empty.
| Variable | Default | Description |
|---|---|---|
COPY_CLAIMSETS | true | false: skip the claim set copy step entirely |
CLAIMSET_NAMES | (empty = every built-in claim set) | Claim sets to copy, semicolon-separated; blank copies all built-ins except internal-use ones (ForApplicationUseOnly = 1, e.g. Bootstrap Descriptors and EdOrgs) |
CLAIMSET_PREFIX | "AA " | Prefix for the copies; quote it to keep the trailing space |
SECURITY_DB_ENGINE | (empty = same as DB_ENGINE) | Engine hosting EdFi_Security: mssql or pgsql. Set it when the ODS/API side runs a different engine than the Admin App database |
SECURITY_DATABASE_NAME | EdFi_Security | Security database name |
SECURITY_SQL_SERVER | tcp:localhost,1433 | SQL Server hosting EdFi_Security. May be a remote server, including a managed Azure SQL Database, independently of APP_SQL_SERVER |
SQL_TRUST_SERVER_CERTIFICATE | false | true: accept a SQL Server certificate without validating it. Applies to APP_SQL_SERVER and SECURITY_SQL_SERVER; a local instance is trusted automatically, so set it only for a remote server presenting a self-signed certificate |
SECURITY_DB_USERNAME | (empty) | SQL Server login with rights on EdFi_Security; required unless SECURITY_USE_INTEGRATED_SECURITY=true |
SECURITY_DB_PASSWORD | (empty) | Password for SECURITY_DB_USERNAME; prompted when empty |
SECURITY_USE_INTEGRATED_SECURITY | false | true: Windows integrated authentication (SECURITY_DB_* not needed) |
POSTGRES_SECURITY_PASSWORD | (empty = POSTGRES_APP_PASSWORD) | PostgreSQL password for EdFi_Security; prompted when both are empty |
POSTGRES_SECURITY_HOST | (empty = POSTGRES_HOST) | PostgreSQL host for EdFi_Security |
POSTGRES_SECURITY_PORT | (empty = POSTGRES_PORT) | PostgreSQL port for EdFi_Security |
POSTGRES_SECURITY_USER | (empty = POSTGRES_APP_USER) | PostgreSQL user for EdFi_Security |
SECURITY_USE_POSTGRES_DOCKER | (empty = USE_POSTGRES_DOCKER) | true: run psql inside the SECURITY_POSTGRES_CONTAINER container |
SECURITY_POSTGRES_CONTAINER | ed-fi-db-admin | The ODS stack's admin/security db container (PostgreSQL in Docker) |
Managed Azure SQL Database
Both SQL Server databases the scripts reach may be a managed Azure SQL
Database, and independently of one another: the Admin App application database
(APP_SQL_SERVER) and the ODS/API's EdFi_Security (SECURITY_SQL_SERVER).
# Admin App application database
APP_SQL_SERVER=tcp:myserver.database.windows.net,1433
DATABASE_NAME=sbaa
APP_DB_USERNAME=edfi_adminapp
APP_DB_PASSWORD=<contained user's password>
# ODS/API EdFi_Security -- may be the same logical server or a different one
SECURITY_SQL_SERVER=tcp:myserver.database.windows.net,1433
SECURITY_DATABASE_NAME=EdFi_Security
SECURITY_DB_USERNAME=edfi_security
SECURITY_DB_PASSWORD=<contained user's password>
SECURITY_USE_INTEGRATED_SECURITY=false
SQL_TRUST_SERVER_CERTIFICATE=false
Each database needs its own contained user: APP_DB_USERNAME in the
application database, SECURITY_DB_USERNAME in EdFi_Security. They are
separate databases, so one login cannot serve both.
Three things differ from a local instance:
- SQL authentication is required. Azure SQL does not accept Windows
integrated authentication, so
SECURITY_USE_INTEGRATED_SECURITYmust befalseand theAPP_DB_*/SECURITY_DB_*logins must be set. The scripts refuse the combination up front rather than failing later with a driver error. - The login is a contained user in the database itself. See
Database
for the
CREATE USERstatement, and for the server, firewall rule, and database to provision in Azure before installing the Admin App. - The connection is encrypted and the certificate validated. A remote
server is reached with
sqlcmd -N, which requires both. Azure SQL presents a CA-issued certificate, so leaveSQL_TRUST_SERVER_CERTIFICATE=false; setting it totrueadds-C, which turns validation off. A local instance is reached with-Calone, because its auto-generated certificate cannot be validated and loopback has no machine-in-the-middle to protect against. Recognized local targets arelocalhost,127.0.0.1,::1,.,(local), this machine's own name, and a local named pipe, each with an optionaltcp:prefix and,portor\instancesuffix.
No schema is created or altered: the scripts only read and write rows in tables
that already exist. They do delete rows, though. cleanup.ps1 removes the
environment, team, ODS instances, education organizations, tenant, and machine
user the Quick Start created, plus the claim set copies in EdFi_Security, and
it does so on whichever server these variables point at. Confirm the values name
the intended database before running it against a remote server, and take a
backup if that database holds anything else.
Finding your token endpoint
The token endpoint is not universal. The Docker stack proxies Keycloak at
https://localhost/auth/realms/edfi/...; other deployments (e.g. IIS-hosted, or
newer standalone Keycloak that drops the /auth prefix) differ. Resolve it from
the issuer the Admin App is configured to trust (AUTH0_CONFIG_SECRET.ISSUER) —
the token must be minted by that same issuer, because the Admin App fetches
its signing keys from there:
$issuer = 'PASTE_AUTH0_CONFIG_SECRET.ISSUER' # e.g. https://localhost:8443/realms/edfi
$disco = Invoke-RestMethod -Uri "$issuer/.well-known/openid-configuration" -SkipCertificateCheck
$disco.token_endpoint # use this as TOKEN_URL
Script Options
run.ps1
| Option | Purpose |
|---|---|
-EnvFile <path> | Use a different env file (default: ./.env) |
-SkipBootstrap | Skip bootstrap.ps1 (IdP client + machine-user seed) |
-SkipClaimsets | Skip copy-claimsets.ps1 (built-in claim set copies in EdFi_Security) |
cleanup.ps1
Reads the same .env for the database connection and the names to delete; any
parameter passed explicitly overrides the .env value.
| Option | Purpose |
|---|---|
-Force | Skip the confirmation prompt (automation) |
-EnvFile <path> | Use a different env file (default: ./.env) |
-SkipClaimsets | Leave the claim set copies in EdFi_Security (e.g. an application still uses one) |
-EnvironmentName / -TeamName / -MachineUsername | Override the names to delete |
-DbEngine, -DatabaseName, -AppDbUsername, -AppDbPassword, -PostgresAppPassword | Override the database connection (mssql always uses the app login, never sa) |
-AppSqlServer | Override the SQL Server hosting the Admin App application database |
-SecuritySqlServer | Override the SQL Server hosting EdFi_Security (everything else comes from the SECURITY_* / POSTGRES_SECURITY_* .env settings, with the engine from SECURITY_DB_ENGINE defaulting to DB_ENGINE) |
-TrustServerCertificate | Skip certificate validation on a remote SQL Server presenting a self-signed certificate |
Running the scripts directly
bootstrap.ps1, quick-start.ps1, and copy-claimsets.ps1 are plain
parameter-driven scripts — run.ps1 only maps .env values onto their
parameters. To run one directly (for example, to provision a second
environment with different values, or to copy additional claim sets), see its
comment-based help:
Get-Help ./bootstrap.ps1 -Full
Get-Help ./quick-start.ps1 -Full
Get-Help ./copy-claimsets.ps1 -Full
All the scripts are idempotent: re-runs update, reuse, or skip existing resources instead of duplicating them.
Troubleshooting
Create environment failed: ... 400 (Bad Request). The API could not reach the ODS/API or Admin API while creating the environment — for exampleConnection to Ed-Fi API Discovery URL timed out. Please ensure the URL is correct and the server is reachable.Confirm the ODS/API and Admin API are up and reachable at theODS_API_DISCOVERY_URL/ADMIN_API_URLvalues (open them in a browser orcurlthem), then run the script again.Create environment failed: … 403 (Forbidden)(or, for Admin API v2 environments, environment creation reports a failed sync with no clear error). The Admin API rejected the Admin App's credential registration at/connect/registerbecauseAuthentication:AllowRegistrationisfalse— the Admin App registers its own client credentials there when creating the environment. Set the flag totruein the Admin API'sappsettings.json, restart the Admin API, and re-run./run.ps1 -SkipBootstrap(the flag can be turned off again afterwards).Create environment failed:with"adminApiUrl": … "Internal server error (500) - service may be down". The Admin API's root URL responded, but itsPOST /connect/registerendpoint threw a server-side error — usually a database problem: the Admin API's own tables (theadminapischema inEdFi_Admin) were never installed, or itsConnectionStringsare wrong. Reproduce it by manually registering a client (see the tip on the prerequisites page), then check the Admin API's log file for the underlying exception and confirm theadminapischema exists inEdFi_Admin.- 403 Forbidden on Applications / Profiles
(
{"message":"Forbidden resource"...}at…/edfi-tenants/<id>/admin-api/v2/profiles/). This is the Admin App's own authorization, not the live Admin API. Two common causes:- The signed-in user has no team membership — the Global admin role alone
lacks the team-scoped profile privilege. Set
ADMIN_USERNAMEto the human bootstrap admin's username and re-run:quick-start.ps1adds them to the team with the Tenant admin role. - The membership uses role 2 (Global admin) instead of 6 (Tenant
admin). Re-run the script (it upgrades the membership automatically) or
PUTthe membership'sroleIdto 6.
- The signed-in user has no team membership — the Global admin role alone
lacks the team-scoped profile privilege. Set
- Empty ODS instances list. The ids in
ODSS_JSONmust exist inEdFi_Admin.dbo.OdsInstanceson the target ODS/API; otherwise the sync finds nothing to attach. Query the table and use its realOdsInstanceIdvalues — see Set Up the ODS Instances. ODS instance "<name>" does not exist in Admin APIwhen creating an application. The Admin App matches the environment's ODS against the Admin API'sGET /v2/odsInstanceslist by name, and that list comes fromEdFi_Admin.dbo.OdsInstances. The named instance is missing from that table, or itsNamediffers from thenameinODSS_JSON. Check withSELECT OdsInstanceId, Name FROM dbo.OdsInstancesagainstEdFi_Admin; if the row is missing, create it (see Set Up the ODS Instances), restart the ODS/API, and make sureODSS_JSONuses the exact same name./auth/mereturns 401 with"Token claim validation failed". The token'sissdoes not equal the Admin App's configuredAUTH0_CONFIG_SECRET.ISSUER, or itsauddoes not includeMACHINE_AUDIENCE. Mint the token from the exact issuer the Admin App trusts, and confirm the audience mapper emitsedfiadminapp-api. Decode the token (its middle segment) to confirm the exact values./auth/mereturns 401 with"Authentication failed". The token verified, but the machine-user step failed. Two common causes:- No matching machine user — the
bootstrap.ps1script seeds one (clientId = edfiadminapp-machine,userType = machine, active, Global admin role). Confirm that row exists in the Admin App database. - The
audclaim is an array (e.g.["edfiadminapp-api","account"]). The Admin App compares the wholeaudclaim by strict equality againstMACHINE_AUDIENCE, so it must be the single valueedfiadminapp-api. The extraaccountaudience is injected by Keycloak's Audience Resolve mapper in the defaultrolesclient scope — on the machine client, set thatrolesscope from Default to Optional soaudis justedfiadminapp-api. Decode the token (its middle segment) to confirm.
- No matching machine user — the
- Token request fails with
404 Not Found(often an IIS/web-server error page).TOKEN_URLis not your Keycloak — the/auth/...path is the Docker stack convention and your host serves something else there. Resolve the real endpoint from your issuer's discovery document (see Finding your token endpoint). - Token request fails with
401 invalid_client/400 unauthorized_client. The service-account client does not exist, the secret is wrong, or it is missing theclient_credentialsgrant. On Keycloak confirm the client exists with Service accounts enabled and the audience /login:app/client_idmappers in place (see Setup Keycloak machine account).