Files
openwork/docs/org-install-links.md
T

9.4 KiB

Organization Install Links

Status: self-host operator guide

Owner: platform/self-host Related: ee/apps/den-api/src/install-links.ts, ee/apps/den-api/src/routes/org/install-links.ts, ee/apps/den-api/src/desktop-connect-grants.ts, packaging/helm/openwork-ee/README.md

What users get

An organization install link opens a three-step Den page:

  1. Download and run the standard OpenWork installer.
  2. Return to Den and click Open OpenWork.
  3. Confirm the exact organization and server in the app, then complete normal organization sign-in.

This flow does not build a customer-specific installer or ZIP. Branding and server configuration are applied only after the user confirms the deep link. Possession of the install link never signs a user in or grants workspace access.

The guided flow is scoped to Den organization install links. The public /desktop and landing-page downloads keep their existing behavior.

Upgrade checklist

Default: Install links are active for every organization on every deployment. DEN_INSTALL_LINKS_GATING_ENABLED is deprecated and inert; platform admins can turn an org dark from /admin as a kill switch.

Helm

  1. Keep migrations.enabled=true for the upgrade. The migration Job creates both install_link and desktop_connect_grant.
  2. Keep config.public.connectLinkMode: exchange (the default). No key is required.
  3. Restart or roll the deployment. No install-link capability toggle is required for a normal self-hosted installation.
  4. No hosted-style rollout flag is required: install links are on by default. DEN_INSTALL_LINKS_GATING_ENABLED is deprecated and inert; configure config.public.bootstrapAdminEmails only if platform admins need /admin access to turn an org dark as a kill switch.

Docker Compose

Run the migration once, then restart Den:

docker compose -f packaging/docker/docker-compose.den-dev.yml exec den sh -lc \
  "node /app/ee/packages/den-db/dist/scripts/bootstrap.js"

If the stack has a custom Compose project name, include the same -p <project> flag.

Public origins

Set DEN_BASE_URL to the externally reachable Den web origin, for example https://openwork.example.com. Den derives Better Auth, CORS/trusted origins, web-app hosts, API defaults, and MCP resource defaults from that one value. Set DEN_API_PUBLIC_URL only as a compatibility override when you publish a separate Den API origin or an externally reachable proxy path, such as https://openwork.example.com/api/den. If you publish a separate Den API origin, the install-link exchange and external MCP clients must be able to reach it.

Invitation acceptance links use the first non-wildcard trusted origin. In the single-origin setup this is derived from DEN_BASE_URL; set DEN_BETTER_AUTH_TRUSTED_ORIGINS only when you need extra web origins during a migration.

Installer delivery

Customer-facing planning guidance for this section is published at packages/docs/start-here/installer-delivery.mdx. This repository page remains authoritative for implementation and security details.

Den first validates the organization install token. It then chooses exactly one of these paths:

Deployment Download behavior Required client access
Internet-connected Immediate 302 to the exact standard asset under OPENWORK_INSTALLER_RELEASE_REPO and OPENWORK_INSTALLER_RELEASE_TAG. The organization token is not forwarded. Den web/API, github.com, release-assets.githubusercontent.com, and objects.githubusercontent.com for legacy or rollback paths.
Semi-air-gapped Stream the matching standard installer from OPENWORK_INSTALLER_ARTIFACTS_DIR. There is no ZIP or in-memory whole-file buffer. Den web/API only.
Fully internal installer delivery Same mounted-artifact path, with Den web/API and the installer artifact available entirely inside the isolated network. This describes installer bytes only, not full product isolation. Internal Den web/API only.

The standard filenames use the release tag without a leading v:

  • openwork-mac-arm64-<version>.dmg
  • openwork-mac-x64-<version>.dmg
  • openwork-win-x64-<version>.exe
  • openwork-linux-x86_64-<version>.AppImage
  • openwork-linux-arm64-<version>.AppImage

There is no first-request GitHub download inside Den, artifact lookup API call, ZIP creation, per-pod cold cache, or different repeated-download path. Every internet-connected request redirects immediately. Every mounted request streams the same provisioned file. With more than one Den API replica, mount the same read-only PVC on every replica.

Default connection handoff (no key)

DEN_CONNECT_LINK_MODE=exchange is the default. When the user clicks Open OpenWork, Den mints a fresh five-minute bearer code and stores only its SHA-256 hash. The app:

  1. posts the code back to the exact HTTPS API origin carried in the deep link to preview the organization and server;
  2. shows an explicit confirmation without changing local configuration; and
  3. posts the code to the exchange endpoint after confirmation, consuming it exactly once before writing desktop-bootstrap.json.

The grant is stored in MySQL, not pod memory, so preview and consumption can land on different Kubernetes replicas. A conditional database update makes concurrent exchange attempts single-use. Expired rows are removed while new grants are minted.

The web page fetches a new code at the moment the user clicks Open, rather than relying on the code loaded before a potentially long installer run. If a code expires or was already used, the user can return to the same Den page and click Open again.

Optional signed handoff

Signed handoffs remain an explicit upgrade path. They are disabled unless all of the following are true:

  • DEN_CONNECT_LINK_MODE=signed;
  • DEN_CONNECT_LINK_KEY_ID is configured;
  • DEN_CONNECT_LINK_PRIVATE_KEY contains the dedicated Ed25519 private key;
  • the matching public key is already embedded in the desktop build.

For Helm:

config:
  public:
    connectLinkMode: signed
    connectLinkKeyId: "owc-2026-07"

secret:
  values:
    connectLinkPrivateKey: |-
      -----BEGIN PRIVATE KEY-----
      ...
      -----END PRIVATE KEY-----

Generate a pair with scripts/generate-connect-link-keypair.mjs. Do not enable signed mode until the production release embeds the corresponding public key. This optional mode changes only the configuration handoff; it still downloads the same standard installer through the same direct or mounted route.

MDM alternative

Customer-facing guidance for this path is published at packages/docs/start-here/enterprise-desktop-deployment.mdx.

Managed deployments can skip the deep-link handoff by deploying a standard binary — typically the enterprise distribution (openwork-enterprise-<os>-<arch>-<version>.<ext>, published on the enterprise release channel), or the public installer — and writing desktop-bootstrap.json directly:

OS Canonical path
Windows %LOCALAPPDATA%\openwork\desktop-bootstrap.json (%XDG_CONFIG_HOME%\openwork\desktop-bootstrap.json wins if set)
macOS/Linux $XDG_CONFIG_HOME/openwork/desktop-bootstrap.json, falling back to ~/.config/openwork/desktop-bootstrap.json
{
  "baseUrl": "https://openwork.example.com",
  "apiBaseUrl": "https://api.openwork.example.com",
  "requireSignin": true,
  "writtenAt": "2026-07-14T12:00:00.000Z"
}

Current builds still read the older ~/.config/openwork path for compatibility. When both files exist, the valid configuration with the newest writtenAt wins.

Security properties

  • Install-link tokens and exchange codes are stored only as SHA-256 hashes.
  • Connection codes expire after five minutes and are consumed exactly once in MySQL across all Den API replicas.
  • The desktop requires HTTPS outside explicit development loopback mode and refuses redirects during preview and exchange.
  • Returned claims must identify the same API origin carried by the deep link.
  • The app displays the exact organization and server before changing local configuration; cancel and every validation failure leave it untouched.
  • Branding URLs from the keyless exchange are not loaded before confirmation.
  • The standard release installer bytes are never modified, preserving normal macOS and Windows signature verification.

Troubleshooting

Symptom Fix
Download redirects to GitHub Expected for internet-connected deployments. Allow github.com and its release-asset redirect/CDN host on the user's network.
Download returns 404 The install token is invalid, expired, or revoked. Ask an org member for the current install page link.
Mounted download fails Verify the filename exactly matches the configured release tag and every Den replica mounts the same readable PVC path.
Open OpenWork cannot prepare a connection Verify the browser can reach Den API and DEN_API_PUBLIC_URL is the correct public HTTPS origin.
The app refuses an expired or used link Return to the same Den install page and click Open OpenWork again to mint a fresh code.
Signed mode reports an unknown key Return to exchange mode, or ship a desktop build containing the matching public key before re-enabling signed.
Install links point at the wrong web host Correct BETTER_AUTH_URL and DEN_BETTER_AUTH_TRUSTED_ORIGINS, then restart Den.