Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
857798f
Initial plan
Copilot Feb 10, 2026
1665c9c
Add matrix-stack helm app
Copilot Feb 10, 2026
70d5e2f
Use access point routes for matrix stack
Copilot Feb 10, 2026
ebabc71
Rename matrix stack routes variable
Copilot Feb 10, 2026
e8d0966
Inline matrix stack routes
Copilot Feb 10, 2026
c5bb417
Adjust ingress class and rtc path
Copilot Feb 10, 2026
975314e
Refine matrix stack ingress defaults
Copilot Feb 10, 2026
2efc3ec
Tidy matrix stack ingress config
Copilot Feb 10, 2026
17b2307
Clarify matrix stack fqdn
Copilot Feb 10, 2026
ea60947
Clarify matrix stack fqdn constraint
Copilot Feb 10, 2026
7a83d46
Rename ingress class constant
Copilot Feb 10, 2026
72fa3ae
Rename ingress class variable
Copilot Feb 10, 2026
024a9e6
Refine matrix stack docs
Copilot Feb 10, 2026
f369aae
Adjust matrix stack naming
Copilot Feb 10, 2026
63f9cbb
Rename ingress class constant
Copilot Feb 10, 2026
7cc3cb8
Rename ingress class constant
Copilot Feb 10, 2026
4c2522c
Align matrix stack hostnames with README
Copilot Feb 10, 2026
874ebe1
Use helm release postrender
Copilot Feb 11, 2026
f6f5f13
Harden postrender script handling
Copilot Feb 11, 2026
d1016ce
Refine postrender handling
Copilot Feb 11, 2026
25637f6
Improve postrender robustness
Copilot Feb 11, 2026
f3419e8
Polish postrender handling
Copilot Feb 11, 2026
6e88b17
Clarify postrender errors
Copilot Feb 11, 2026
5091874
Adjust postrender splitting
Copilot Feb 11, 2026
1f981b1
Refactor matrix stack service names
Copilot Feb 11, 2026
ce1a6c5
Deduplicate matrix stack service lookups
Copilot Feb 11, 2026
a9a7a80
Memoize postrender setup
Copilot Feb 11, 2026
52f4f35
Switch postrender script to JS
Copilot Feb 11, 2026
360071d
Rename service helper param
Copilot Feb 11, 2026
8bcd59b
Clarify executable constants
Copilot Feb 11, 2026
e2a19f7
Rename service helper param
Copilot Feb 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/standard/k8s.apps/assets/charts.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
"version": "0.5.7",
"sha256": "184d60dd6b6a723842957b2d642cf13ed145c3c70564f475d5b58825aa556e00"
},
"matrix-stack": {
"repo": "oci://ghcr.io/element-hq/ess-helm",
"name": "matrix-stack",
"version": "26.2.0",
"sha256": "96881b2746acf6251a7fbdd2a06bab6c0d42b0e2c3a75b20210bb4e2cc47a165"
},
"valkey": {
"repo": "oci://ghcr.io/cloudpirates-io/helm-charts",
"name": "valkey",
Expand Down
1 change: 1 addition & 0 deletions packages/standard/k8s.apps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"./mariadb/app": "./dist/mariadb/app/index.js",
"./mariadb/database": "./dist/mariadb/database/index.js",
"./maybe": "./dist/maybe/index.js",
"./matrix-stack": "./dist/matrix-stack/index.js",
"./postgresql": "./dist/postgresql/index.js",
"./postgresql/app": "./dist/postgresql/app/index.js",
"./postgresql/database": "./dist/postgresql/database/index.js",
Expand Down
220 changes: 220 additions & 0 deletions packages/standard/k8s.apps/src/matrix-stack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { chmod, stat } from "node:fs/promises"
import { fileURLToPath } from "node:url"
import { AccessPointRoute, l4EndpointToString } from "@highstate/common"
import { getProviderAsync, Namespace, resolveHelmChart, Service } from "@highstate/k8s"
import { k8s } from "@highstate/library"
import { forUnit, toPromise } from "@highstate/pulumi"
import { helm } from "@pulumi/kubernetes"
import { charts } from "../shared"

const { args, inputs, outputs } = forUnit(k8s.apps.matrixStack)

const namespace = Namespace.create(args.appName, { cluster: inputs.k8sCluster })

const synapseHost = `matrix.${args.fqdn}`
const elementWebHost = `chat.${args.fqdn}`
const matrixAuthenticationServiceHost = `account.${args.fqdn}`
const matrixRtcHost = `mrtc.${args.fqdn}`
const elementAdminHost = `admin.${args.fqdn}`
const HELM_INGRESS_DISABLED_VALUE = "none"
const postrenderScript = fileURLToPath(new URL("./postrender.js", import.meta.url))
const EXECUTABLE_MASK = 0o111
const EXECUTABLE_MODE = 0o755
let postrenderExecutablePromise: Promise<void> | undefined

const ensurePostrenderExecutable = () => {
if (!postrenderExecutablePromise) {
postrenderExecutablePromise = (async () => {
try {
const postrenderMode = (await stat(postrenderScript)).mode
if ((postrenderMode & EXECUTABLE_MASK) === 0) {
await chmod(postrenderScript, EXECUTABLE_MODE)
}
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
throw new Error(`Helm postrender script not found: ${postrenderScript}`, {
cause: error,
})
}

throw new Error(
`Failed to mark Helm postrender script as executable: ${postrenderScript}`,
{
cause: error,
},
)
}
})()
}

return postrenderExecutablePromise
}

await ensurePostrenderExecutable()

const provider = await getProviderAsync(inputs.k8sCluster)
const chartPath = await resolveHelmChart(charts["matrix-stack"])

const release = new helm.v3.Release(
args.appName,
{
chart: chartPath,
namespace: namespace.metadata.name,

values: {
serverName: args.fqdn,
ingress: {
className: HELM_INGRESS_DISABLED_VALUE,
},

synapse: {
ingress: {
host: synapseHost,
},
},
elementWeb: {
ingress: {
host: elementWebHost,
},
},
elementAdmin: {
ingress: {
host: elementAdminHost,
},
},
matrixAuthenticationService: {
ingress: {
host: matrixAuthenticationServiceHost,
},
},
matrixRTC: {
ingress: {
host: matrixRtcHost,
},
},
wellKnownDelegation: {
baseDomainRedirect: {
enabled: false,
},
},
},

postrender: postrenderScript,
},
{ provider, dependsOn: namespace },
)

const serviceOptions = { dependsOn: release }
const serviceName = (name: string) => `${args.appName}-${name}`
const synapseServiceName = serviceName("synapse")
const elementWebServiceName = serviceName("element-web")
const elementAdminServiceName = serviceName("element-admin")
const matrixAuthenticationServiceName = serviceName("matrix-authentication-service")
const matrixRtcAuthorisationServiceName = serviceName("matrix-rtc-authorisation-service")
const matrixRtcSfuServiceName = serviceName("matrix-rtc-sfu")
const wellKnownServiceName = serviceName("well-known")
const getService = (serviceName: string) =>
Service.get(serviceName, { namespace, name: serviceName }, serviceOptions)
const synapseService = getService(synapseServiceName)
const elementWebService = getService(elementWebServiceName)
const elementAdminService = getService(elementAdminServiceName)
const matrixAuthenticationService = getService(matrixAuthenticationServiceName)
const matrixRtcAuthorisationService = getService(matrixRtcAuthorisationServiceName)
const matrixRtcSfuService = getService(matrixRtcSfuServiceName)
const wellKnownService = getService(wellKnownServiceName)

const commonRouteArgs = {
accessPoint: inputs.accessPoint,
type: "http" as const,
tlsCertificateNativeData: namespace,
}

new AccessPointRoute(
`${args.appName}-synapse`,
{
...commonRouteArgs,
fqdn: synapseHost,
endpoints: synapseService.endpoints,
gatewayNativeData: synapseService,
},
{ dependsOn: release },
)
new AccessPointRoute(
`${args.appName}-element-web`,
{
...commonRouteArgs,
fqdn: elementWebHost,
endpoints: elementWebService.endpoints,
gatewayNativeData: elementWebService,
},
{ dependsOn: release },
)
new AccessPointRoute(
`${args.appName}-element-admin`,
{
...commonRouteArgs,
fqdn: elementAdminHost,
endpoints: elementAdminService.endpoints,
gatewayNativeData: elementAdminService,
},
{ dependsOn: release },
)
new AccessPointRoute(
`${args.appName}-matrix-authentication-service`,
{
...commonRouteArgs,
fqdn: matrixAuthenticationServiceHost,
endpoints: matrixAuthenticationService.endpoints,
gatewayNativeData: matrixAuthenticationService,
},
{ dependsOn: release },
)
new AccessPointRoute(
`${args.appName}-matrix-rtc-sfu`,
{
...commonRouteArgs,
fqdn: matrixRtcHost,
endpoints: matrixRtcSfuService.endpoints,
gatewayNativeData: matrixRtcSfuService,
},
{ dependsOn: release },
)
new AccessPointRoute(
`${args.appName}-matrix-rtc-authorisation`,
{
...commonRouteArgs,
fqdn: matrixRtcHost,
path: "/sfu/get",
endpoints: matrixRtcAuthorisationService.endpoints,
gatewayNativeData: matrixRtcAuthorisationService,
},
{ dependsOn: release },
)
new AccessPointRoute(
`${args.appName}-well-known`,
{
...commonRouteArgs,
fqdn: args.fqdn,
path: "/.well-known/matrix",
endpoints: wellKnownService.endpoints,
gatewayNativeData: wellKnownService,
},
{ dependsOn: release },
)

const endpoints = await toPromise(synapseService.endpoints)

export default outputs({
service: synapseService.entity,
endpoints: synapseService.endpoints,

$statusFields: {
serverName: args.fqdn,
synapse: `https://${synapseHost}`,
elementWeb: `https://${elementWebHost}`,
elementAdmin: `https://${elementAdminHost}`,
matrixAuthenticationService: `https://${matrixAuthenticationServiceHost}`,
matrixRtc: `https://${matrixRtcHost}`,
endpoints: endpoints.map(l4EndpointToString),
},
})
14 changes: 14 additions & 0 deletions packages/standard/k8s.apps/src/matrix-stack/postrender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs"

const input = readFileSync(0, "utf8")
const normalizedInput = input.replace(/\r\n/g, "\n")
const rawDocuments = normalizedInput.split(/\n---\s*\n/)
const documents = (normalizedInput.startsWith("---") ? rawDocuments.slice(1) : rawDocuments).filter(
Boolean,
)
const ingressPattern = /^\s*kind:\s*Ingress\s*(?:#.*)?$/m
const filtered = documents.filter(document => !ingressPattern.test(document))
const output = filtered.filter(document => document.trim().length > 0).join("\n---\n")

process.stdout.write(output.endsWith("\n") ? output : `${output}\n`)
2 changes: 1 addition & 1 deletion packages/standard/k8s.apps/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "./node_modules/@highstate/cli/assets/tsconfig.base.json",
"include": ["./src/**/*.ts", "./package.json", "./assets/**/*.json"]
"include": ["./src/**/*.{js,ts}", "./package.json", "./assets/**/*.json"]
}
1 change: 1 addition & 0 deletions packages/standard/library/src/k8s/apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./grocy"
export * from "./hubble"
export * from "./kubernetes-dashboard"
export * from "./mariadb"
export * from "./matrix-stack"
export * from "./maybe"
export * from "./minio"
export * from "./mongodb"
Expand Down
47 changes: 47 additions & 0 deletions packages/standard/library/src/k8s/apps/matrix-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { defineUnit, z } from "@highstate/contract"
import { pick } from "remeda"
import { l4EndpointEntity } from "../../network"
import { serviceEntity } from "../service"
import { appName, sharedInputs, source } from "./shared"

/**
* The Matrix stack deployed on Kubernetes.
*/
export const matrixStack = defineUnit({
type: "k8s.apps.matrix-stack.v1",

args: {
...appName("matrix-stack"),

/**
* The base domain for the Matrix stack services.
*
* Subdomains for Matrix services are generated automatically.
* This value cannot be changed after the first deployment.
*/
fqdn: {
schema: z.string(),
},
},

inputs: {
...pick(sharedInputs, ["k8sCluster", "accessPoint"]),
},

outputs: {
service: serviceEntity,
endpoints: {
entity: l4EndpointEntity,
multiple: true,
},
},

meta: {
title: "Matrix Stack",
icon: "simple-icons:matrixdotorg",
secondaryIcon: "simple-icons:element",
category: "Communication",
},

source: source("matrix-stack"),
})