NestJS API for managing Aam Digital SaaS instances.
Each instance is a customer deployment, served on its own subdomain. This
service holds the record of which instances exist, and GET /api/v1/instances
is the manifest the cluster deployment reads: it provisions what the
response lists and destroys what it does not. Creating an instance, or changing
its status, dispatches that deployment.
The OpenAPI docs at /api/docs (Swagger UI, generated at runtime) document the
endpoints, their authentication and the effect of each operation.
Taking a system down means taking it out of the manifest, and the two ways to do that differ in what is kept. Hibernating it keeps its record and its name. Deleting it frees the name, is only allowed once it is already hibernated, and triggers no deployment — it is out of the manifest already.
Neither call erases data and neither is reversible in practice: activating a hibernated instance provisions an empty one rather than restoring it. What the deployment tears down, what the cluster keeps, and how to purge or restore it belong to the cluster deployment and are documented there.
# hibernate
curl -u "admin:$ADMIN_PASSWORD" -X POST \
"https://admin.$DOMAIN/api/v1/instances/my-org/hibernate?confirm=my-org"
# put it back — as a new, empty system under the same name
curl -u "admin:$ADMIN_PASSWORD" -X POST \
"https://admin.$DOMAIN/api/v1/instances/my-org/activate?confirm=my-org"
# delete the record of a hibernated instance
curl -u "admin:$ADMIN_PASSWORD" -X DELETE \
"https://admin.$DOMAIN/api/v1/instances/my-org?confirm=my-org"Besides its <name>.<cluster domain> hostname, an instance can be served on any
number of alternativeHostnames — full hostnames such as
my-org.aam-digital.com or a domain of the organisation's own. The
infrastructure reads them from the manifest and gives each one an Ingress host
and its own certificate. A hostname only works once its DNS record points at the
cluster: until then no certificate can be issued and browsers warn about the one
they get. And because a hostname becomes routing configuration in the cluster,
it is settable by an admin through POST /api/v1/instances only, never through
the Brevo webhook, and one already claimed by another instance is rejected with
409.
How an instance stores its data is a mode, not a set of app settings:
e.g. standard is a regular system on its own database, demo runs on generated
data that is not persisted. See the API docs for all available modes.
These named modes combine into valid overall system states by the infra cluster.
Anything else is an override: admin-only, unset by default, stored as
given and applied on top of the mode. Changed (along with mode)
through PATCH /api/v1/instances/:name/app-config.
What a valid setting is, and which of them the deployment owns and refuses to
let through, is decided by the cluster deployment, not here - so an
accepted value can still be ignored when applied. Never put a secret in an
override: it ends up in config.json, which the browser fetches and any
caller of GET /instances can read.
Overrides are not settable at creation - that route also takes a user token and
serves the Brevo webhook. Nothing about a call here looks dangerous, which is
part of why confirm applies to it too: whether a change stops an instance
persisting its data can hide inside an override this API does not interpret.
Every admin route that writes to an existing instance — hibernate, activate,
app-config, DELETE — requires ?confirm=<name> repeating the name from the
path, and takes the admin password only. Valid credentials do not establish that
the caller meant this particular instance, and none of these calls has an undo:
a mistyped subdomain or a script pointed at the wrong stack is the failure the
parameter is there to catch. It is required even when the call turns out to
change nothing, so the no-op case cannot become a way past the check.
Environment variables are documented in .env.example; copy it to
.env for local development.
The service authenticates with GitHub as a GitHub App to dispatch workflow runs of
[pulumi-up-instances][] on Aam-Digital/aam-cloud-infrastructure.
Create the App in the Aam-Digital org:
- Go to Org Settings → Developer settings → GitHub Apps → New GitHub App
- Set a name (e.g.
aam-platform-admin) and the homepage URL https://github.com/Aam-Digital/platform-admin-services - Uncheck Active under Webhooks (not needed)
- Under Repository permissions, set Actions: Read and write
- Set Where can this app be installed to "Only on this account"
- Click Create GitHub App
Install the App on the target repository:
- In the App settings, click Install App
- Install on the
Aam-Digitalorg, restrict access to theaam-cloud-infrastructurerepository
Configure the service (see .env.example for all variables):
GITHUB_APP_ID: numeric App ID from theaam-platform-adminsettings pageGITHUB_APP_PRIVATE_KEY: generate a private key on that page and set it to the.pemcontents
# 1. Copy environment config
cp .env.example .env
# 2. Start PostgreSQL
docker compose up -d
# 3. Install dependencies
npm install
# 4. Run in development mode
npm run start:devSwagger UI is available at http://localhost:3000/api/docs.
npm test