diff --git a/CHANGELOG.md b/CHANGELOG.md index f17e385d..e63b3c0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,31 @@ Versioning follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [0.4.3] — 2026-07-31 + +### Fixed + +- The Workspace UI load balancer now owns an HTTP-only default site, so its + internal nodes no longer expose a fallback TLS listener without a configured + certificate. The site-specific public TLS layer continues to forward traffic + to the Workspace load balancer on port 80. +- Message-route links render their anchor before surrounding context finishes + loading, preventing valid deep links from appearing unavailable temporarily. +- URL-shaped URNs in message content are rendered as links. + +### Requirements and compatibility + +- Requirements are unchanged from `0.4.2`: Exordos Core `0.2.3` or newer and + Workspace backend `0.1.18` or newer. +- No Workspace API, persisted-data format, or client data migration changes are + introduced by this release. + +### Migration notes + +- Update `workspace_ui` to `0.4.3`. + +No client or server data migration is required. + ## [0.4.2] — 2026-07-29 ### Fixed diff --git a/docs/exordos-element.md b/docs/exordos-element.md index bfd71454..727d627c 100644 --- a/docs/exordos-element.md +++ b/docs/exordos-element.md @@ -51,3 +51,8 @@ the Workspace origin to port 80 of the load balancer reported by the deployed load balancer. The element sets the backend `Host` and `X-Forwarded-Proto` headers from `forwarded_host` and `forwarded_proto` so realm URLs remain canonical behind an external TLS or port-forwarding layer. + +The element also replaces the load-balancer image's packaged default site with +an HTTP-only port 80 catch-all. This keeps TLS certificates and the public 443 +listener at the site-specific edge instead of exposing an unconfigured fallback +TLS listener on the internal Workspace load balancer. diff --git a/exordos/manifests/workspace_ui.yaml.j2 b/exordos/manifests/workspace_ui.yaml.j2 index adbd90c8..9a77a707 100644 --- a/exordos/manifests/workspace_ui.yaml.j2 +++ b/exordos/manifests/workspace_ui.yaml.j2 @@ -28,6 +28,31 @@ resources: disk_size: 10 nodes_number: 1 + $core.config.configs: + workspace_ui_lb_default_site: + project_id: "12345678-c625-4fee-81d5-f691897b8142" + path: /etc/nginx/sites-enabled/default + mode: "0644" + owner: root + target: + kind: node_set + node_set: $core.network.lb.$workspace_ui_lb:uuid + on_change: + kind: shell + command: nginx -t && systemctl reload nginx + body: + kind: text + content: | + server { + listen 80 default_server reuseport; + listen [::]:80 default_server; + server_name _; + + location / { + return 444; + } + } + $core.network.lb.$workspace_ui_lb.backend_pools: workspace_backend_http: name: workspace-backend-http diff --git a/lerna.json b/lerna.json index 9b7bdb1b..dabb8c4f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "0.4.2", + "version": "0.4.3", "npmClient": "npm", "packages": [".", "packages/*"], "command": { diff --git a/package-lock.json b/package-lock.json index 0ed9309f..a8008562 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "workspace", - "version": "0.4.2", + "version": "0.4.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "workspace", - "version": "0.4.2", + "version": "0.4.3", "license": "Apache-2.0", "workspaces": [ ".", @@ -24130,7 +24130,7 @@ }, "packages/electron": { "name": "exordos-workspace", - "version": "0.4.2", + "version": "0.4.3", "license": "Apache-2.0", "dependencies": { "electron-updater": "6.8.9" @@ -24656,7 +24656,7 @@ } }, "packages/web": { - "version": "0.4.2", + "version": "0.4.3", "license": "Apache-2.0", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "2.0.1", diff --git a/package.json b/package.json index 0585ea65..3f05d625 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "workspace", "private": true, - "version": "0.4.2", + "version": "0.4.3", "description": "Exordos Workspace - smart corporate messenger built with React/Electron", "workspaces": [ ".", diff --git a/packages/electron/package.json b/packages/electron/package.json index c970cc2b..da50f34e 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -1,6 +1,6 @@ { "name": "exordos-workspace", - "version": "0.4.2", + "version": "0.4.3", "private": true, "description": "Exordos Workspace desktop app", "author": "Exordos ", diff --git a/packages/web/package.json b/packages/web/package.json index 460515bf..49ca6b9f 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "0.4.2", + "version": "0.4.3", "private": true, "type": "module", "author": "Exordos ", diff --git a/scripts/validate-exordos-element.mjs b/scripts/validate-exordos-element.mjs index 83c7e0ea..30352dd4 100644 --- a/scripts/validate-exordos-element.mjs +++ b/scripts/validate-exordos-element.mjs @@ -43,6 +43,31 @@ for (const [name, route] of Object.entries(routes)) { } } +const defaultSite = manifest.resources["$core.config.configs"]?.workspace_ui_lb_default_site; +if (!defaultSite) { + throw new Error("Workspace UI manifest must own the load-balancer default site"); +} +if ( + defaultSite.target?.kind !== "node_set" || + defaultSite.target.node_set !== "$core.network.lb.$workspace_ui_lb:uuid" +) { + throw new Error("Workspace UI load-balancer default site must target its node set"); +} +if (defaultSite.path !== "/etc/nginx/sites-enabled/default") { + throw new Error("Workspace UI load-balancer default site must replace the packaged site"); +} +if (defaultSite.on_change?.command !== "nginx -t && systemctl reload nginx") { + throw new Error("Workspace UI load-balancer default site must validate and reload nginx"); +} + +const defaultSiteContent = defaultSite.body?.content ?? ""; +if (!defaultSiteContent.includes("listen 80 default_server")) { + throw new Error("Workspace UI load-balancer default site must keep the port 80 catch-all"); +} +if (/listen\s+(?:\[::\]:)?443\b|ssl_certificate/.test(defaultSiteContent)) { + throw new Error("Workspace UI load-balancer default site must not configure TLS"); +} + const webAction = routes.workspace_web?.condition?.actions?.[0]; if (webAction?.kind !== "local_dir_download" || !webAction.url.endsWith("/workspace-ui.tar.zst")) { throw new Error("workspace_web must download the workspace-ui.tar.zst artifact");