quest'n'click 4.5.0 · r1

Built for quest'n'click 4.5.0, revision r1. Whether it is published, and which one is current, is recorded at publication.json.

Configuration

Français · Type: how-to + reference · Audience: admin · Context: hosted Studio · Status: ready · Applies from: 4.2.3

The Studio Server (hosted team Studio) · contract: experimental · verified by CI (support matrix of 4.4.0, 2026-10-23): Postgres, S3 and Keycloak in CI; Chromium and Firefox (job studio-hosted); WebKit on macOS, by hand only · automated checks: jobs studio-hosted, studio-install-smoke; CI plan studioHosted; 3 test files · people's passes: studio-hosted not run (#149), studio-collab not run (#150), studio-preview not run (#166), studio-assets-hosted not run (#167), studio-mcp-remote not run (#196), studio-oidc-generic not run (#197) · known limits: clever-env-prints-values

The Studio Server reads a JSON configuration, which holds no secret, and a few secrets, each from a variable or a file. This page sets both on the two Clever Cloud apps, and is the reference of STUDIO_CONFIG_JSON, field by field.

What you get

The studio app and the worker app carry every variable they need to build and start: the run commands, the build hooks, the configuration on one line, and the secrets, set from files so that no value is typed or shown.

Context and versions

Prerequisites

Where to run the commands

On your machine, in the engine repository where both apps are linked under their alias (Apps and add-ons).

Limit · clever-env-prints-values clever env prints every value, multi-line ones included (a PEM key spans many lines). Never paste its output anywhere; filter it to names: clever env --alias my-game-studio | grep -oE '^[A-Z][A-Z0-9_]*='.

Steps

  1. Make the job keys. The worker only runs jobs the Studio signs with its private key:

    # on your machine, in the engine repository
    mkdir -p ~/secrets/my-game && npm run studio-server -- worker-keys --out ~/secrets/my-game
    

    studio-worker.key (0600) is the Studio's, studio-worker.pub the worker's. The command refuses to overwrite them, and never prints them.

  1. Make the session key and, if you want /metrics, its token:

    # on your machine
    openssl rand -base64 48 > ~/secrets/my-game/session-key && chmod 600 ~/secrets/my-game/session-key
    openssl rand -base64 32 > ~/secrets/my-game/metrics-token && chmod 600 ~/secrets/my-game/metrics-token
    
  1. Set the studio app's plain variables:

    # on your machine, in the engine repository
    clever env set --alias my-game-studio CC_NODE_DEV_DEPENDENCIES install
    clever env set --alias my-game-studio CC_POST_BUILD_HOOK "npm run studio-client"
    clever env set --alias my-game-studio CC_RUN_COMMAND "npm run studio-server -- serve"
    clever env set --alias my-game-studio CC_HEALTH_CHECK_PATH /startz
    clever env set --alias my-game-studio STUDIO_BACKUP_CRON 1
    
  1. Set its secrets from their files. A key's text begins with -----BEGIN, which clever env set reads as an option unless -- comes first:

    # on your machine, in the engine repository
    clever env set --alias my-game-studio -- STUDIO_WORKER_KEY "$(cat ~/secrets/my-game/studio-worker.key)"
    clever env set --alias my-game-studio -- STUDIO_SESSION_KEY "$(cat ~/secrets/my-game/session-key)"
    clever env set --alias my-game-studio -- STUDIO_METRICS_TOKEN "$(cat ~/secrets/my-game/metrics-token)"
    

    STUDIO_OIDC_CLIENT_SECRET was set with the provider (Keycloak). The database and Cellar credentials arrive from the add-ons (POSTGRESQL_ADDON_URI, CELLAR_ADDON_KEY_ID, CELLAR_ADDON_KEY_SECRET): set nothing for them.

  1. Write the configuration in a file, check it, and set it on one line as STUDIO_CONFIG_JSON:

    {
      "publicUrl": "https://studio.example.fr",
      "playUrl": "https://play.example.fr",
      "game": "my-game",
      "workDir": "/home/bas/work",
      "host": "0.0.0.0",
      "trustProxy": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
      "store": { "kind": "postgres" },
      "blobs": { "kind": "s3", "host": "cellar-c2.services.clever-cloud.com", "bucket": "my-game-studio" },
      "oidc": { "issuer": "https://keycloak.example.fr/realms/web-scumm", "clientId": "studio", "audience": "studio-api", "cliClientId": "web-scumm-cli" },
      "worker": { "url": "https://worker.example.fr", "instance": "my-game-studio" }
    }
    
    # on your machine, in the engine repository, the file above saved as ~/secrets/my-game/studio.config.json
    clever env set --alias my-game-studio STUDIO_CONFIG_JSON "$(node -p 'JSON.stringify(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")))' ~/secrets/my-game/studio.config.json)"
    

    The repository's ops/studio/studio.config.example.json is the same example. The configuration holds no secret.

  1. Set the worker app's variables. WORKER_INSTANCE is the configuration's worker.instance:

    # on your machine, in the engine repository
    clever env set --alias my-game-worker CC_NODE_DEV_DEPENDENCIES install
    clever env set --alias my-game-worker CC_RUN_COMMAND "npm run studio-worker"
    clever env set --alias my-game-worker CC_POST_BUILD_HOOK "python3 -m pip install --target .python --require-hashes --only-binary=:all: -r ops/studio/worker-requirements.lock"
    clever env set --alias my-game-worker WORKER_INSTANCE my-game-studio
    clever env set --alias my-game-worker -- WORKER_PUBLIC_KEY "$(cat ~/secrets/my-game/studio-worker.pub)"
    

    The hook installs the asset tools' Python packages (Pillow, NumPy, SciPy), every hash checked, wheels only.

Measure · worker-hook The worker's install, run by hand on the worker app: 7.0 s, 226 MB installed, no compilation. Not measured during a real Clever build at the time; the reference deployment then saw the hook take about 7 s in a worker deploy of 36 s.

  • date: 2026-10-19
  • version: 4.2.1
  • volume: Pillow 12.3.0, NumPy 2.4.6, SciPy 1.18.1 (one run, pip cache empty)
  • conditions: worker app of spike S, Clever Cloud test organisation, region par, size XS; installed into a tmpfs folder over SSH
  • source: QUALIF-4.2.2-ASSETS.md §2

Expected result, and how to check it

The names, never the values:

# on your machine, in the engine repository
clever env --alias my-game-studio | grep -oE '^[A-Z][A-Z0-9_]*='
clever env --alias my-game-worker | grep -oE '^[A-Z][A-Z0-9_]*='

The studio lists the variables of steps 3 to 5, STUDIO_OIDC_CLIENT_SECRET and the add-ons'; the worker lists those of step 6 and no add-on variable. The configuration is checked for real when the server starts: a refused one stops it with a line naming the field (First deployment).

Common errors

SymptomCauseFix
clever env set says an option is unknownA PEM value without -- before the nameStep 4's form
<NAME>: the secret is empty or too short (8 characters at least)An empty file, or a wrong path in $(cat …)Check the file
a session key is 32 characters or moreA short STUDIO_SESSION_KEYopenssl rand -base64 48
STUDIO_METRICS_TOKEN: 32 characters at least (openssl rand -base64 32)A short tokenStep 2
STUDIO_CONFIG_JSON: unknown key "…"A typo, or a field of a newer versionThe field list below
trustProxy is required when the server is reachable beyond the loopbacktrustProxy left out with OIDCKeep it
<NAME>_FILE (…) is readable by others: chmod 600A secret file with a loose mode (outside Clever)chmod 600

Files or data changed

Recovery or going back

clever env rm <NAME> --alias <app> removes a variable. Changing a secret later is Rotating keys.

Reference: how secrets are read

Each variable has its entry in Environment variables.

Reference: where the configuration comes from

In order: --config <file> on the command line; else the file STUDIO_CONFIG names; else STUDIO_CONFIG_JSON, the JSON itself (what a platform without files, such as Clever Cloud, uses). Without any, a command says usage: studio-server <serve|import <dir>|export <dir>> --config <file> (or STUDIO_CONFIG_JSON). In clever ssh, the shell has the app's variables, so no command needs --config.

Reference: STUDIO_CONFIG_JSON, field by field

An unknown top-level key is refused at start (unknown key "…"). Inside a field, an unknown sub-key is ignored.

FieldRequiredValueDefault
publicUrlyesthe Studio's origin, http(s); the only Origin a cookie mutation may carry—
playUrlyesthe game's origin, http(s); must differ from publicUrl's origin—
gameyesthe game's id (letters, digits, _, -); one game per instance—
workDiryesthe instance's working folder (the game materialised, scratch), ephemeral on Clever Cloud: /home/bas/work—
portnothe port; leave it out on Clever Cloud, which gives PORT (a port here would override it)PORT, else 8080
hostnothe address listened on; without oidc, only the loopback is accepted0.0.0.0
trustProxywith oidc beyond the loopbackaddresses or IPv4 networks whose X-Forwarded-For / X-Forwarded-Proto are believed[]
store.kindyespostgres (hosted) or sqlite (local, with store.file)—
blobs.kindyess3 (with host, bucket) or fs (with dir); s3 with a worker—
oidcfor a shared Studiothe provider (its fields); without it, the local server on the loopbacknone
sessions.idleHours, sessions.absoluteHoursnoa browser session's idle and absolute lifetimes8 and 24
worker.url, worker.instancewith oidcthe worker's URL, and a name of 3 to 64 letters, digits, _ or - (the worker's WORKER_INSTANCE)—
clientDirnothe built Studio; with oidc the server refuses to start without it.cache/studio-client
publicDirnothe game's prepared images and sounds (npm run assets writes them, they are committed); the Studio shows them to members and every preview plays them; read only on a hosted Studiopublic/ of the deployed repository
publicBuildsnotrue: the published builds (/play/ on the game's origin) are open to anyone without a session, with the icons their page names (D42, the owner's choice)false
gitnothe game's Git repository: remote (no credentials in it; the deploy key is STUDIO_GIT_KEY), branch (main), path (a folder of the repository), committer {name, email}, knownHosts (without it, trusted on first use)none

trustProxy on Clever Cloud

The private ranges of the example (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) are an assumption: Clever's proxies sit in them, so each person is told apart. They are to be narrowed to Clever's actual proxies by the studio-hosted pass. Until then, an address in those ranges could claim any X-Forwarded-For, which matters for the per-address rate limits.

References


Edit the source of this page · Report an error