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.
storageLimit is a floor on the storage the deployment gives the instance, as
a whole number of Mi, Gi or Ti (e.g. "5Gi") — meant for an instance whose
data has outgrown the cluster deployment's default volume size.
null until set, and changed through PATCH /api/v1/instances/:name/storage.
It only ever grows: the underlying volume can be expanded but never shrunk, so a value that is not larger than what is already stored is rejected rather than accepted and then having no effect where it is applied.
versions are the image tags an instance runs, per component — ndb-core,
aam-services and replication-backend, named after their images — e.g.
{ "ndb-core": "stable" }. null until set, which runs the
cluster deployment's defaults, and changed through
PATCH /api/v1/instances/:name/versions: a component in the body is set, or
with null unset, and the others keep their value.
The deployment follows a tag as it moves, so a release number rather than a moving tag pins that component until it is changed again. Which components it applies a version to is decided there — one it does not deploy per instance ignores the value.
Every admin route that writes to an existing instance
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