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:
- Download and run the standard OpenWork installer.
- Return to Den and click Open OpenWork.
- 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_ENABLEDis deprecated and inert; platform admins can turn an org dark from/adminas a kill switch.
Helm
- Keep
migrations.enabled=truefor the upgrade. The migration Job creates bothinstall_linkanddesktop_connect_grant. - Keep
config.public.connectLinkMode: exchange(the default). No key is required. - Restart or roll the deployment. No install-link capability toggle is required for a normal self-hosted installation.
- No hosted-style rollout flag is required: install links are on by default.
DEN_INSTALL_LINKS_GATING_ENABLEDis deprecated and inert; configureconfig.public.bootstrapAdminEmailsonly if platform admins need/adminaccess 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>.dmgopenwork-mac-x64-<version>.dmgopenwork-win-x64-<version>.exeopenwork-linux-x86_64-<version>.AppImageopenwork-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:
- posts the code back to the exact HTTPS API origin carried in the deep link to preview the organization and server;
- shows an explicit confirmation without changing local configuration; and
- 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_IDis configured;DEN_CONNECT_LINK_PRIVATE_KEYcontains 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. |