Skip to content

DUPLOAI-3742: run the extension installer beside the studio - #25

Open
duplodavid wants to merge 3 commits into
duplocloud:devfrom
duplodavid:DUPLOAI-3742-devkit
Open

duplodavid wants to merge 3 commits into
duplocloud:devfrom
duplodavid:DUPLOAI-3742-devkit

Conversation

@duplodavid

@duplodavid duplodavid commented Oct 5, 2026 •

Copy link
Copy Markdown

What this changes

DUPLOAI-3742: devkit runs the extension installer beside the studio, so a build in extensions/[name]/dist/ installs on its own.

  • Adds an installer service on quay.io/duplocloud/helpdesk-installer, pinned by INSTALLER_TAG in .env.example, which every user's .env adopts on the next ./run.sh. The compose file defaults to that pin, so docker compose up before then still starts
  • Points the studio at it through Extensions__InstallerUrl, and hands it the JWT secret and the license token from .env and none of its LLM, AWS or admin credentials
  • Leaves the license-server catalog off unless LICENSE_SERVER_URL is set
  • deploy-extension.sh on a build under extensions/ hands it to the installer while it runs, waits for the installer to report that zip, and exits non-zero with the installer's reason. It posts the zip itself only for a build the installer leaves alone, a zip elsewhere, a remote target, or with the installer stopped, so the agent skill's deploy step no longer races the installer
  • The installer installs a dist/ build only once it changes while the installer runs, so a build on disk at start (as after ./run.sh --reset), a removed extension, and a build older than a working installed version stay as they are. The image INSTALLER_TAG pins predates that, so this holds once a follow-up moves the pin
  • The ready banner and the install doc send the bundled sample through deploy-extension.sh, since that build never reaches the installer. A pass with no selection key installs nothing. The docs also cover the manual recovery when a build hangs the studio at load, and how to read the installer's status when a build never loads
  • The Available tab hands Install to this installer only once the UI tag and the studio tag in .env.example are images that do that. The tags pinned there predate that handoff

How it was verified

  • ./run.sh --reset, then built and deployed a sample end to end
  • ./scripts/build-extension.sh on the affected sample, then deployed it and created one in the UI
  • npm ci && npm run build in the affected frontend/
  • ./stop.sh --wipe && ./run.sh from a clean slate
  • Ran /duplo-extension and confirmed the guidance still matches reality
  • Docs only - no runtime behavior changed
Output
$ bash tests/test-installer-wiring.sh
37 passed, 0 failed
$ bash tests/test-deploy-extension.sh
11 passed, 0 failed

Blast radius

  • This changes .env.example - image tags or ports here are adopted into every user's
    .env on their next ./run.sh after an upgrade. Say so plainly above.
  • This changes scripts/upgrade_dev_kit.sh or scripts/init-project.sh - the adoption path
  • This changes a workflow under .github/workflows/
  • None of the above

Checks

  • No .env, tokens, keys, or customer identifiers in the diff
  • No build output committed - dist/, node_modules/, **/bin/, **/obj/, backend/sdk-packages/
  • Commits are conventional, imperative, and focused
  • If a vendored ng-common-lib tarball was added or bumped, its NOTICE sits beside it

@duplodavid
duplodavid marked this pull request as draft October 5, 2026 16:32
@duplodavid
duplodavid force-pushed the DUPLOAI-3742-devkit branch from e3e5e60 to 5aa14c5 Compare October 5, 2026 20:58
@duplodavid duplodavid changed the title DUPLOAI-3742: feat(compose): run the extension installer DUPLOAI-3742: run the extension installer beside the studio Oct 6, 2026
devkit runs the extension installer beside the studio, so a build in
`extensions/[name]/dist/` installs on its own.

- Add an `installer` service on `quay.io/duplocloud/helpdesk-installer`,
  pinned by `INSTALLER_TAG` in `.env.example`, which every user's `.env`
  adopts on the next `./run.sh`
- Point the studio at it through `Extensions__InstallerUrl`, and hand it the
  JWT secret and the license token from `.env` and none of its LLM, AWS or
  admin credentials
- Leave the license-server catalog off unless `LICENSE_SERVER_URL` is set
- Send the bundled sample through `deploy-extension.sh`, since that build
  never reaches the installer. A pass with no selection key installs nothing
- The Available tab hands Install to this installer only once the UI tag and
  the studio tag in `.env.example` are images that do that. The tags pinned
  there predate that handoff
@duplodavid
duplodavid force-pushed the DUPLOAI-3742-devkit branch from 0d0a0c3 to 49d4932 Compare October 6, 2026 21:04
@duplodavid
duplodavid marked this pull request as ready for review October 6, 2026 21:05
@zafarabbas
zafarabbas requested a review from jeeva-duplo October 7, 2026 06:34
Comment thread run.sh
Comment thread run.sh
Keep the installer and the agent's dind sidecar: both volumes, and both names in the logs service list.
Comment thread README.md
docker compose start duplo-ai-studio
```

To install a fixed build, run `./scripts/remove-extension.sh [id]` and then rebuild. The rebuild has to differ in

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove-extension.sh deletes the row but leaves the zip in dist/, and for a local build the installer reads "no row + a build in dist/" as an install (the table only returns not-requested when there's no LocalPath). Removal doesn't change the fingerprint, so nothing happens right away, but the extension comes back on the next trigger: any other build, a studio restart, or the next ./run.sh. The only ways to keep it gone are disabling it in the UI, deleting dist/, or EXTENSIONS_EXCLUDE. Worth saying here, or better, have the local source only install a zip that changed while the installer was watching rather than every zip on disk.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the installer the second way you suggested, and cd37817 documents it in the README. The installer now installs a dist/ build only once its zip changes while it runs, so a removed extension stays gone until its next rebuild. The image INSTALLER_TAG pins predates that, so devkit picks it up when a follow-up bumps the pin.

Comment thread docker-compose.yml
# The studio service's image, which the status record reports and holds are keyed on.
- INSTALLER_BACKEND_IMAGE=quay.io/duplocloud/backend:${STUDIO_TAG}
- INSTALLER_STATE_DIR=/var/lib/installer
- INSTALLER_LOCAL_DIR=/extensions

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local source takes ownership of every installed id that has a build in dist/, whatever version is installed, and the table then installs whenever the installed version differs from the dist version. It never checks which is newer. So if v1.2 was loaded another way (Extension Studio's in-platform flow, or a zip outside dist/), a stale v1.1 zip left in dist/ rolls it back on the next pass. Can the local source skip a build older than the current row?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the installer, and cd37817 documents it. It skips a dist/ build older than a working installed version, with a detail naming both, and deploy-extension.sh loads it if you hand it that build on purpose. A newer version that failed to load still takes the older build, so rebuilding back to a working version needs no extra step. The image INSTALLER_TAG pins predates that, so devkit picks it up when a follow-up bumps the pin.

Comment thread docker-compose.yml
- CHANNELS_BASE_URL=${CHANNELS_BASE_URL:-}
- CHANNELS_BUCKET=${CHANNELS_BUCKET:-}
volumes:
# The status record. A named volume, so it survives a recreate and goes with the database on `down -v`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this volume goes with down -v, ./run.sh --reset now wipes the installer's state along with Mongo, and the first pass after the studio comes up loads every extensions/*/dist/extension.zip. Anyone resetting to get an empty platform gets all their old builds back about 20 seconds later. Either document that, or don't treat zips that were already on disk at startup as install requests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the installer the second way, and cd37817 documents it. A build already in dist/ when the installer starts waits for its next rebuild, so ./run.sh --reset comes up empty. The image INSTALLER_TAG pins predates that, so devkit picks it up when a follow-up bumps the pin.

Comment thread docs/troubleshooting.md
An agent running `deploy-extension.sh` beside the installer raced its pass,
and a same-version rebuild afterwards read as already installed, so the agent
could test an older build without knowing it.

- `deploy-extension.sh` on a build under `extensions/` waits for the
  installer's status record to show that zip's digest, and exits non-zero with
  the installer's reason when it does not install it. It still POSTs for a
  build the installer leaves alone, a zip elsewhere, a remote target, or with
  the installer stopped
- `docker-compose.yml` defaults `INSTALLER_TAG` to the `.env.example` pin, so
  `docker compose up` before the next `./run.sh` still starts
- The README says which `dist/` builds the installer leaves alone, and
  troubleshooting covers a build that never loads, with how to read the
  installer's status record
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants