Consolidate ai-workspace / developer-portal setup scripts and fix compose profile gating - #2892
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThe change centralizes portal provisioning in ChangesPortal setup and profile migration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Developer
participant Setup as portals/scripts/setup.sh
participant Files as api-platform.env and resources/keys
participant Compose as docker compose
participant Portal as devportal
Developer->>Setup: Run provisioning script
Setup->>Files: Write credentials and generated key material
Setup->>Compose: Configure Compose profiles
Compose->>Portal: Mount selected secret files
Portal->>Files: Load file-based security configuration
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@portals/ai-workspace/distribution/README.md`:
- Around line 72-74: Update the Quick Start startup command to use the
ai-workspace Compose profile, replacing plain docker compose up -d with docker
compose --profile ai-workspace up -d. Revise the nearby “by default” wording to
reflect that AI Workspace and the Platform API start through this profile, while
preserving the optional Developer Portal description.
- Line 92: Update the Developer Portal lifecycle instructions in the README to
provide an explicit command that stops or removes the running service, such as
the profile-scoped compose down or service stop command, instead of saying to
omit the profile on later commands.
In `@portals/ai-workspace/Makefile`:
- Line 122: Update the e2e-ci recipe around the setup.sh --force invocation to
preserve persistent resources: back up and restore resources/keys/encryption.key
and the generated TLS certificate files alongside the existing api-platform.env
and docker-compose.yaml backups, ensuring cleanup leaves all keys and
certificates unchanged.
In `@portals/developer-portal/distribution/README.md`:
- Line 94: Clarify the AI Workspace profile guidance in the README: explain that
later docker compose up commands without --profile ai-workspace neither start
nor reconcile AI Workspace, and that an already-running instance remains
running. Document the explicit cleanup options using docker compose stop
ai-workspace or docker compose --profile ai-workspace down.
In `@portals/developer-portal/README.md`:
- Line 252: Update the Docker Compose volume mounts for the Developer Portal and
optional AI workspace portal to expose only jwt_public.pem, not the entire
resources/keys directory; then revise the README statement to accurately
describe the public-key-only mount and ensure private keys and encryption.key
remain unavailable to portal containers.
In `@portals/scripts/setup.sh`:
- Around line 214-239: Update the existing-credentials check around
APIP_CP_ADMIN_USERNAME and APIP_CP_ADMIN_PASSWORD_HASH so provisioning is
skipped only when both variables are present. If exactly one is set, fail with a
clear recovery instruction or regenerate and write both credentials together;
never combine a newly generated username with an existing hash.
- Around line 107-110: Update the setup script’s FILE_MODE and generated-file
permission handling so private keys remain unreadable to other host users while
still accessible to the non-root container user via an appropriate owner/group
or ACL. Apply restrictive permissions to api-platform.env as well, and ensure
generated initial credentials are persisted only as a restrictive hash.
- Around line 203-209: Update the encryption.key generation branch in the setup
script so the generic FORCE/--force path never overwrites an existing at-rest
key. Preserve the existing key by default, and gate any intentional key
replacement behind a separate explicit destructive rotation or migration option
that requires confirmation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 25c3a8df-37b0-466b-8317-fcc5a3fe69e5
📒 Files selected for processing (15)
portals/ai-workspace/Makefileportals/ai-workspace/QUICKSTART.mdportals/ai-workspace/README.mdportals/ai-workspace/distribution/README.mdportals/ai-workspace/docker-compose.yamlportals/ai-workspace/setup.shportals/developer-portal/Makefileportals/developer-portal/README.mdportals/developer-portal/distribution/README.mdportals/developer-portal/docker-compose.yamlportals/developer-portal/it/Makefileportals/developer-portal/it/docker-compose.test.postgres.yamlportals/developer-portal/it/docker-compose.test.yamlportals/developer-portal/scripts/setup.shportals/scripts/setup.sh
💤 Files with no reviewable changes (2)
- portals/ai-workspace/setup.sh
- portals/developer-portal/scripts/setup.sh
…mpose configuration
|
Generated platform-api.env @renuka-fernando Are we following the approach of storing encryption keys in files rather than environment variables? If so, should we remove the Devportal encryption key and security session secret from the environment variables and use file-mounted secrets instead? cc: @Piumal1999 |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
portals/ai-workspace/Makefile (1)
374-374: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winIgnore generated key material in distributions.
The shared setup script generates
resources/keys/encryption.keyandresources/keys/jwt_private.pem, but the.gitignoregenerated immediately above only excludesresources/certificates/*. Addresources/keys/*before shipping the distribution.Proposed ignore rule
'*.env' \ 'resources/certificates/*' \ + 'resources/keys/*' \ > $(DIST_DIR)/.gitignore🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@portals/ai-workspace/Makefile` at line 374, Add resources/keys/* to the generated distribution .gitignore alongside the existing resources/certificates/* rule, ensuring setup.sh-generated encryption.key and jwt_private.pem files are excluded before shipping the distribution.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ai-workspace-pr-check.yml:
- Line 63: Update the setup command in the workflow step using the
portals/ai-workspace working directory to invoke the shared script via
../scripts/setup.sh instead of ./scripts/setup.sh, preserving the existing
environment variables and command behavior.
---
Outside diff comments:
In `@portals/ai-workspace/Makefile`:
- Line 374: Add resources/keys/* to the generated distribution .gitignore
alongside the existing resources/certificates/* rule, ensuring
setup.sh-generated encryption.key and jwt_private.pem files are excluded before
shipping the distribution.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c37b58f6-a590-4060-ae79-8f7918249ffe
📒 Files selected for processing (9)
.github/workflows/ai-workspace-pr-check.ymlportals/ai-workspace/Makefileportals/ai-workspace/distribution/README.mdportals/ai-workspace/docker-compose.yamlportals/developer-portal/README.mdportals/developer-portal/distribution/README.mdportals/developer-portal/docker-compose.yamlportals/developer-portal/it/docker-compose.test.postgres.yamlportals/scripts/setup.sh
🚧 Files skipped from review as they are similar to previous changes (4)
- portals/developer-portal/distribution/README.md
- portals/developer-portal/docker-compose.yaml
- portals/developer-portal/README.md
- portals/scripts/setup.sh
Yes, let's follow the same pattern. |
…le for service selection
…d update documentation for AI Workspace and Developer Portal
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
portals/developer-portal/README.md (1)
252-252: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winCorrect the Developer Portal secret-mount documentation.
The new configuration reads
/etc/devportal/keys/encryption.keyand/etc/devportal/keys/session-secret, so saying thatencryption.keyis never mounted contradicts the file-based secret change. Clarify that the Platform API’s private key and encryption key remain unavailable, while the Developer Portal’s own encryption and session-secret files are mounted at these target paths; ensure the Compose mounts match.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@portals/developer-portal/README.md` at line 252, Update the secret-mount documentation in the Developer Portal README to distinguish Platform API secrets from portal secrets: state that the Platform API’s jwt_private.pem and encryption.key remain unavailable to the portal, while the portal mounts its own encryption.key and session-secret at /etc/devportal/keys/encryption.key and /etc/devportal/keys/session-secret. Verify the corresponding docker-compose.yaml mounts use these target paths..github/workflows/ai-workspace-pr-check.yml (1)
63-63: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not ship hardcoded CI credentials.
This workflow provisions a functional
admin/adminaccount in every run. Generate a per-run password and export the resulting credentials to Cypress instead.As per coding guidelines, files matching
**/*.{go,toml,yaml,yml,Dockerfile}must not ship functional default or hardcoded credentials.Proposed fix
- run: ADMIN_USERNAME=admin ADMIN_PASSWORD=admin ../scripts/setup.sh + run: | + ADMIN_USERNAME="ci-${GITHUB_RUN_ID}" + ADMIN_PASSWORD="$(openssl rand -hex 32)" + export ADMIN_USERNAME ADMIN_PASSWORD + echo "::add-mask::$ADMIN_PASSWORD" + { + printf 'ADMIN_USERNAME=%s\n' "$ADMIN_USERNAME" + printf 'ADMIN_PASSWORD=%s\n' "$ADMIN_PASSWORD" + printf 'CYPRESS_ADMIN_USER=%s\n' "$ADMIN_USERNAME" + printf 'CYPRESS_ADMIN_PASSWORD=%s\n' "$ADMIN_PASSWORD" + } >> "$GITHUB_ENV" + ../scripts/setup.sh🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ai-workspace-pr-check.yml at line 63, Update the workflow step invoking setup.sh to generate a unique password for each run instead of using the hardcoded admin/admin credentials, then export the generated username and password through the environment or mechanism consumed by Cypress so its tests authenticate with those per-run credentials.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@portals/developer-portal/configs/config.toml`:
- Around line 93-94: Update the config entries for encryption_key and
session_secret to use explicit environment-variable fallbacks for local startup,
or extend the start:local template allowlist to permit the generated
resources/keys host directory. Ensure requireHexSecret() receives the generated
host secrets without relying on unavailable /etc/devportal/keys bind mounts.
---
Outside diff comments:
In @.github/workflows/ai-workspace-pr-check.yml:
- Line 63: Update the workflow step invoking setup.sh to generate a unique
password for each run instead of using the hardcoded admin/admin credentials,
then export the generated username and password through the environment or
mechanism consumed by Cypress so its tests authenticate with those per-run
credentials.
In `@portals/developer-portal/README.md`:
- Line 252: Update the secret-mount documentation in the Developer Portal README
to distinguish Platform API secrets from portal secrets: state that the Platform
API’s jwt_private.pem and encryption.key remain unavailable to the portal, while
the portal mounts its own encryption.key and session-secret at
/etc/devportal/keys/encryption.key and /etc/devportal/keys/session-secret.
Verify the corresponding docker-compose.yaml mounts use these target paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c05f67b-9a76-4ae2-bf5a-a8994000655d
📒 Files selected for processing (14)
.github/workflows/ai-workspace-pr-check.ymlportals/ai-workspace/.gitignoreportals/ai-workspace/Makefileportals/ai-workspace/README.mdportals/ai-workspace/distribution/README.mdportals/ai-workspace/docker-compose.yamlportals/developer-portal/Makefileportals/developer-portal/README.mdportals/developer-portal/configs/config-template.tomlportals/developer-portal/configs/config.tomlportals/developer-portal/distribution/README.mdportals/developer-portal/docker-compose.yamlportals/developer-portal/src/config/configLoader.jsportals/scripts/setup.sh
🚧 Files skipped from review as they are similar to previous changes (5)
- portals/ai-workspace/docker-compose.yaml
- portals/developer-portal/Makefile
- portals/ai-workspace/Makefile
- portals/developer-portal/docker-compose.yaml
- portals/scripts/setup.sh
…ntials and update Docker Compose documentation for clarity
this is fixed. @Piumal1999 pls review |
…ior and profile management
|
@CodeRabbit review |
✅ Action performedReview finished.
|
Summary
portals/ai-workspace/setup.shandportals/developer-portal/scripts/setup.shwere two near-duplicate copies of the same provisioning logic that had drifted apart. This PR replaces both with a single shared script (portals/scripts/setup.sh), fixes the docker-composeprofilesconfig in both packs so thatplatform-api— previously always-on with no profile — is now correctly gated like every other service, and hassetup.shwrite a defaultCOMPOSE_PROFILESinto.envso a plaindocker compose up -d(no--profileflag) still starts the right services for whichever pack you're in. It also moves every secret that can be file-mounted — including the Developer Portal's at-rest encryption key and session secret, previously env vars — off ofapi-platform.envand ontoresources/keys/*files read via{{ file }}.Changes
1. Single shared
setup.sh(portals/scripts/setup.sh)portals/ai-workspace/setup.shandportals/developer-portal/scripts/setup.sh; one script now serves both quickstarts and their distribution zips.$PWD, then next to the script, then its parent) so the same file works whether invoked as../scripts/setup.shfrom a dev checkout or./scripts/setup.shfrom a distribution zip.--forcerotates the TLS cert / JWT keypair / admin credentials,--certs-onlygenerates just the TLS cert (used bymake bff-run). (--force's scope was narrowed during review — see item 6.)make distin bothai-workspace/Makefileanddeveloper-portal/Makefilenow copies this shared script into the distributionscripts/directory instead of each pack's own copy, and bakes in two pack-specific values viased(see item 3).2.
docker-compose.yaml— fixed profile gating (both packs)platform-apipreviously had noprofilesentry, so it always started regardless of which--profileflag was passed — it wasn't actually optional/gated, it just always ran alongside whatever else was selected. It's now explicitly listed under every profile that needs it:["platform-api", "ai-workspace", "all"](ai-workspace pack) and["platform-api", "developer-portal", "all"](developer-portal pack) — making--profile platform-apia real "platform-api only" mode.with-developer-portal/with-ai-workspaceto plaindeveloper-portal/ai-workspace, and both now also participate in a newallprofile alongside their own compose file's default service.platform-apivia--no-deps(skips thedepends_on: service_healthywait — documented as a prerequisite the caller must satisfy themselves).3.
COMPOSE_PROFILESdefault via.env— a plaindocker compose up -dworks againplatform-apibehind a profile (item 2) meant a baredocker compose up -dwith no flags would start nothing. Fixed by havingsetup.shwriteCOMPOSE_PROFILES=<pack's default profiles>into a project-level.envfile (which Compose reads automatically, unlikeapi-platform.envwhich is only ever passed to containers explicitly via each service'senv_file:) — so a plaindocker compose up -dafter runningsetup.shonce again starts the right services, and an explicit--profileflag is only needed to add an optional component or override the default.--profiles=<a,b,...>flag tosetup.shalways wins; otherwise a valuemake distbakes intoDEFAULT_COMPOSE_PROFILESfor a shipped distribution zip; otherwise the script auto-detects the pack fromdocker-compose.yaml's own service topology (see below) and picks that pack's usual two-service default.COMPOSE_PROFILESdefault and for the printed "next step" hint) no longer greps for a comment's exact wording, which would silently break if the comment were ever reworded. It now checks the compose file's actual service topology — each pack's mandatory service has a structurally distinct name (devportal:vsdeveloper-portal:) — andmake distalso bakes aDEFAULT_PACK_NAMEthe same way it bakesDEFAULT_COMPOSE_PROFILES, so a shipped distribution zip never needs to detect anything at runtime at all.--profileCLI flag does not merge withCOMPOSE_PROFILESfrom.env— passing--profile ai-workspacealone, withCOMPOSE_PROFILES=ai-workspace,platform-apialready set, activates onlyai-workspace. It's safe as an "add an optional service" pattern anyway, because Compose only reconciles containers within the profile(s) you name on that invocation — already-running containers from an earlier defaultup -dare left alone, not stopped.4. Command surface — before / after
docker compose up -ddocker compose up -d(aftersetup.shhas run — reads the default from.env) ordocker compose --profile ai-workspace --profile platform-api up -d(explicit, works with no.envdefault at all)docker compose --profile with-developer-portal up -ddocker compose --profile developer-portal up -d(adds to the already-running default) ordocker compose --profile ai-workspace --profile developer-portal --profile platform-api up -d(explicit, full set in one invocation)docker compose up -ddocker compose up -d(aftersetup.shhas run — reads the default from.env) ordocker compose --profile developer-portal --profile platform-api up -d(explicit, works with no.envdefault at all)docker compose --profile with-ai-workspace up -ddocker compose --profile ai-workspace up -d(adds to the already-running default) ordocker compose --profile developer-portal --profile ai-workspace --profile platform-api up -d(explicit, full set in one invocation)docker compose --profile all up -ddocker compose --profile platform-api up -ddocker compose up -d --no-deps ai-workspace(aftersetup.sh) ordocker compose --profile ai-workspace up -d --no-deps ai-workspace(explicit)docker compose up -d --no-deps devportal(aftersetup.sh) ordocker compose --profile developer-portal up -d --no-deps devportal(explicit)setup.sh's closing "next step" hint reflects whicheverCOMPOSE_PROFILESvalue was actually just written to.env(which can differ from the pack's usual default if--profiles=<...>or a dist-baked value was used), defaults-first, including--profile alland--profile platform-api.5. At-rest encryption keys and session secrets — env vars → mounted files (both packs)
main): the at-rest encryption key is no longer written asAPIP_CP_ENCRYPTION_KEYinapi-platform.env. It's generated toresources/keys/encryption.key(32-byte key, 64 hex chars) and read byconfig.tomlvia{{ file "/etc/platform-api/keys/encryption.key" }}— the sameresources/keysmount already used for the JWT keypair.APIP_DP_SECURITY_ENCRYPTION_KEYandAPIP_DP_SECURITY_SESSION_SECRETare gone fromapi-platform.envtoo. Devportal'sconfig.tomlalready supports{{ file }}interpolation with an allowlist covering/etc/devportal, and both compose files already mountresources/keys/jwt_public.peminto the container at/etc/devportal/keys/— so the fix was to generate two more files there (resources/keys/devportal-encryption.key,devportal-session-secret), mount them alongside the JWT public key, and switchconfig.tomlto{{ file "/etc/devportal/keys/encryption.key" }}/{{ file ".../session-secret" }}.api-platform.envnow holds onlyAPIP_CP_ADMIN_USERNAME/APIP_CP_ADMIN_PASSWORD_HASH.src/dao/subscriptionDao.js,webhookSubscriberDao.js) — the same risk profile as Platform API's own at-rest key — so it follows the identical rotation policy: preserved across--force, rotated only via--rotate-encryption-key. The session secret only signs session cookies (rotating it just invalidates sessions), so it follows--forcelike the TLS cert/JWT keypair. Both keys' rotation now share a single confirmation prompt instead of prompting twice.secretKeyRef-style env vars in the Kubernetes Helm chart (a standard, accepted k8s pattern) and in the IT/test compose configs anddistribution/all-in-one, none of which were touched.6. Docs and CI
README.md,QUICKSTART.md, anddistribution/README.mdin both packs updated for the new script path (../scripts/setup.sh) and, in this final round, to drop the now-redundant--profile <default>flag from every primary "bring the stack up" example in favor of a baredocker compose up(orup -d) — sincesetup.shalready wrote the right default into.env. The two genuinely-additive "turn on the optional sibling service" examples (docker compose --profile ai-workspace up -dfrom the devportal pack's docs and vice versa) were deliberately left alone, since they're correct as written (see item 3).developer-portal/distribution/README.md,ai-workspace/distribution/README.md) also picked up--force-recreate, matching the one place that already had it correctly — a plainup -dwon't recreate an already-running container just because a bind-mounted file changed underneath it..github/workflows/ai-workspace-pr-check.yml's "Start quickstart stack" step switched fromdocker compose --profile ai-workspace up -d --wait ...to a baredocker compose up -d --wait ..., since thesetup.shinvocation earlier in the same job already writesCOMPOSE_PROFILESto.env; the step's comment previously (and incorrectly, as of this PR) claimed--profilewas required or Compose would select zero services.ai-workspace/Makefile'sbff-runande2e-citargets, and both Makefiles'disttargets, updated to call the shared script path.developer-portal/it/docker-compose.test*.yaml,it/Makefile) updated to reference the new script location (../../scripts/setup.sh); the IT stack's own cert/key generation (make ensure-certs) is unaffected — it doesn't use this script and still writesAPIP_CP_ENCRYPTION_KEYas an env var for its own fixtures.developer-portal/docs/introduction/quick-start.mddescribes an older layout (a./scripts/setup.shpath that no longer exists, aconfigs/config-platform-api.tomlthat isn't generated by the current script) predating the shared-script design entirely. Flagged for a follow-up rather than folded into this diff.7. Security hardening found in review
docker-compose.yaml(anddeveloper-portal/it/docker-compose.test.postgres.yaml) previously bind-mounted the entireresources/keys/.certsdirectory into the devportal/ai-workspace containers, which quietly also exposedjwt_private.pemandencryption.keyto a service that only ever needsjwt_public.pemto verify tokens. Changed to mount justjwt_public.pem(matching the pattern the sqlite IT compose file already used) — and, this round, the two new devportal-specific secret files are mounted individually the same way, never the whole directory. Verified withdocker compose configthatplatform-apistill gets the full directory it actually needs.setup.shpreviously wrote every generated secret (TLS private key, JWT private key, encryption key,api-platform.env) as644— world-readable to any other host user — with a comment calling it an accepted local-quickstart tradeoff. Now:600plus, wheresetfaclis available (Linux), an ACL entry granting read access specifically to the fixed non-root UID (10001) every service image runs as; falls back to the previous644when no working ACL mechanism exists (macOS'schmod +acan't target a numeric UID with no resolvable local account — tested directly, confirmed it fails silently, so the fallback path matters, not just theoretical).api-platform.envis600unconditionally since it's read by the Docker CLI/daemon, never bind-mounted into a container. The new devportal key/secret files go through the samerestrict_secret_filehelper.--forceno longer touches either at-rest encryption key: previously--forcerotated everything includingresources/keys/encryption.key, silently making any previously-encrypted data unreadable. Rotating it now requires a new, explicit--rotate-encryption-keyflag, with an interactivetype 'rotate' to confirmprompt (or the flag itself as confirmation in CI/non-interactive mode, i.e. whenADMIN_USERNAME/ADMIN_PASSWORDare set) — and, this round, the same flag and the same single confirmation prompt now also gate devportal's encryption key.ai-workspace/Makefile'se2e-cinow also backs up/restoresresources/keysandresources/certificatesaround itssetup.sh --forcecall, not justdocker-compose.yaml/api-platform.env— protects a localmake e2e-cirun from clobbering keys/certs from a prior manualsetup.shrun in the same checkout.setup.shpreviously only checkedAPIP_CP_ADMIN_USERNAMEfor "already provisioned," so a partially-set env file (username present, hash missing — or vice versa) would either silently skip provisioning while broken, or pair a freshly generated username with a stale hash. Now checks both variables and fails with a specific recovery instruction if exactly one is present.Behavior notes / things to verify
docker compose up -dstart nothing, sinceplatform-apibecame profile-gated with no default. That's resolved — after runningsetup.shonce, a baredocker compose up -dagain starts the pack's usual two services, via.env'sCOMPOSE_PROFILES. Anyone who runsdocker compose up -dwithout having runsetup.shfirst (e.g. copies an old.env-less checkout) still needs an explicit--profile.with-developer-portal,with-ai-workspace) in scripts/muscle memory needs to switch to the new flags (see table in item 4).api-platform.envfrom before this change has staleAPIP_CP_ENCRYPTION_KEY/APIP_DP_SECURITY_ENCRYPTION_KEY/APIP_DP_SECURITY_SESSION_SECRETlines that are no longer read; both services refuse to start until the correspondingresources/keys/*files exist (re-runsetup.sh, which fills in only what's missing).--forceto also rotateresources/keys/encryption.keyorresources/keys/devportal-encryption.keynow needs--rotate-encryption-keyexplicitly —--forcealone leaves both untouched.--profile allis symmetric between both packs' compose files (same three services), so--profile allbehaves identically regardless of which pack'sdocker-compose.yamlyou're driving it from.setup.sh's pack-detection no longer depends on a comment's exact wording (see item 3) — verified the structural fallback (devportal:vsdeveloper-portal:service keys) resolves correctly for both packs' checked-in compose files.Test plan
docker compose --profile ai-workspace up -dfromportals/ai-workspace— only platform-api + ai-workspace startdocker compose --profile developer-portal up -dfromportals/developer-portal— only platform-api + developer-portal start--profile <x>on the CLI does not merge withCOMPOSE_PROFILESfrom.env, and that Compose leaves already-running out-of-scope containers alone on such an invocation../scripts/setup.shthen a baredocker compose up -dfrom each pack — starts platform-api + the pack's own service, no--profileneededdocker compose --profile all up -dfrom each pack — all three services startdocker compose --profile platform-api up -d— only platform-api starts../scripts/setup.shrun from bothportals/ai-workspaceandportals/developer-portal— correct pack-specific "next step" hint printed,resources/keys/encryption.key,devportal-encryption.key, anddevportal-session-secretall generatedsetup.shwithout--force— existingencryption.key,devportal-encryption.key, and JWT keypair left untouchedsetup.sh --force— TLS cert, JWT keypair, admin credentials, anddevportal-session-secretrotate; both encryption keys do NOTsetup.sh --rotate-encryption-key— bothencryption.keyanddevportal-encryption.keyrotate, with a single shared confirmation prompt interactivelyjwt_public.pem+ the two new key files mounted (both packs'docker-compose.yaml, andit/docker-compose.test.postgres.yaml)encryption.keyorsession-secretis missing/malformed (matches the existing 64-hex-char check, now sourced from a file instead of an env var)make distfor both packs — distribution zip containsscripts/setup.shwithDEFAULT_COMPOSE_PROFILESandDEFAULT_PACK_NAMEbaked in, and runs correctly standalonemake bff-run(ai-workspace) and devportal IT suite still pass with updated script pathsai-workspace-pr-check.yml's "Start quickstart stack" step succeeds with the baredocker compose up -d --waitcommand