Skip to content

Commit 36a0fd2

Browse files
committed
feat(synapse): add appserviceRegistration FileHelper and public API for dependents
Refactor appservice actions to use a typed FileHelper instead of raw YAML parsing and hardcoded volume paths. Export ensureAppserviceRegistration for dependent bridges to use in their init scripts.
1 parent cfe2252 commit 36a0fd2

File tree

5 files changed

+232
-121
lines changed

5 files changed

+232
-121
lines changed

startos/actions/deleteAppservice.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { unlink } from 'node:fs/promises'
2+
import { appservicesSubpath } from '../fileModels/appserviceRegistration.yaml'
23
import { homeserverYaml } from '../fileModels/homeserver.yml'
34
import { sdk } from '../sdk'
45
import { mountpoint } from '../utils'
56

67
const { InputSpec, Value } = sdk
78

8-
const appservicesSubpath = 'appservices'
9-
109
export const inputSpec = InputSpec.of({
1110
id: Value.dynamicSelect(async ({ effects }) => {
1211
const files =
@@ -15,7 +14,7 @@ export const inputSpec = InputSpec.of({
1514

1615
const values: Record<string, string> = {}
1716
for (const f of files) {
18-
const match = f.match(/\/appservices\/(.+)\.yaml$/)
17+
const match = f.match(new RegExp(`/${appservicesSubpath}/(.+)\\.yaml$`))
1918
if (match) values[match[1]] = match[1]
2019
}
2120

@@ -50,12 +49,10 @@ export const deleteAppservice = sdk.Action.withInput(
5049
async ({ effects, input }) => {
5150
const { id } = input
5251

53-
const registrationFile = `${appservicesSubpath}/${id}.yaml`
54-
const registrationPath = `${mountpoint}/${registrationFile}`
55-
const volumePath = `/media/startos/volumes/main/${registrationFile}`
52+
const registrationPath = `${mountpoint}/${appservicesSubpath}/${id}.yaml`
5653

5754
try {
58-
await unlink(volumePath)
55+
await unlink(sdk.volumes.main.subpath(`${appservicesSubpath}/${id}.yaml`))
5956
} catch (e: any) {
6057
if (e.code !== 'ENOENT') throw e
6158
}

startos/actions/listAppservices.ts

Lines changed: 88 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,84 @@
1-
import { YAML } from '@start9labs/start-sdk'
1+
import type { T } from '@start9labs/start-sdk'
2+
import {
3+
appserviceRegistrationYaml,
4+
appservicesSubpath,
5+
type AppserviceRegistration,
6+
} from '../fileModels/appserviceRegistration.yaml'
27
import { homeserverYaml } from '../fileModels/homeserver.yml'
38
import { sdk } from '../sdk'
4-
import { mountpoint } from '../utils'
59

6-
type ResultEntry =
7-
| {
8-
type: 'single'
9-
name: string
10-
description: string | null
11-
value: string
12-
masked: boolean
13-
copyable: boolean
14-
qr: boolean
15-
}
16-
| {
17-
type: 'group'
18-
name: string
19-
description: string | null
20-
value: ResultEntry[]
21-
}
10+
const appserviceFields = (reg: AppserviceRegistration): T.ActionResultMember[] => {
11+
const fields: T.ActionResultMember[] = [
12+
{
13+
type: 'single',
14+
name: 'ID',
15+
description: null,
16+
value: reg.id,
17+
masked: false,
18+
copyable: true,
19+
qr: false,
20+
},
21+
{
22+
type: 'single',
23+
name: 'URL',
24+
description: null,
25+
value: reg.url,
26+
masked: false,
27+
copyable: true,
28+
qr: false,
29+
},
30+
{
31+
type: 'single',
32+
name: 'Sender Localpart',
33+
description: null,
34+
value: reg.sender_localpart,
35+
masked: false,
36+
copyable: true,
37+
qr: false,
38+
},
39+
{
40+
type: 'single',
41+
name: 'AS Token',
42+
description: null,
43+
value: reg.as_token,
44+
masked: true,
45+
copyable: true,
46+
qr: false,
47+
},
48+
{
49+
type: 'single',
50+
name: 'HS Token',
51+
description: null,
52+
value: reg.hs_token,
53+
masked: true,
54+
copyable: true,
55+
qr: false,
56+
},
57+
{
58+
type: 'single',
59+
name: 'Rate Limited',
60+
description: null,
61+
value: String(reg.rate_limited),
62+
masked: false,
63+
copyable: false,
64+
qr: false,
65+
},
66+
]
67+
68+
if (reg.namespaces.users[0]?.regex) {
69+
fields.push({
70+
type: 'single',
71+
name: 'User Namespace',
72+
description: null,
73+
value: reg.namespaces.users[0].regex,
74+
masked: false,
75+
copyable: true,
76+
qr: false,
77+
})
78+
}
79+
80+
return fields
81+
}
2282

2383
export const listAppservices = sdk.Action.withoutInput(
2484
'list-appservices',
@@ -47,93 +107,27 @@ export const listAppservices = sdk.Action.withoutInput(
47107
}
48108
}
49109

50-
const groups: ResultEntry[] = []
110+
const groups: T.ActionResultMember[] = []
51111

52112
for (const filePath of files) {
53-
const subpath = filePath.replace(`${mountpoint}/`, '')
54-
try {
55-
const content = await sdk.volumes.main.readFile(subpath, 'utf-8')
56-
const parsed = YAML.parse(content as string)
57-
58-
const fields: ResultEntry[] = [
59-
{
60-
type: 'single',
61-
name: 'ID',
62-
description: null,
63-
value: parsed.id || 'unknown',
64-
masked: false,
65-
copyable: true,
66-
qr: false,
67-
},
68-
{
69-
type: 'single',
70-
name: 'URL',
71-
description: null,
72-
value: parsed.url || 'not configured',
73-
masked: false,
74-
copyable: true,
75-
qr: false,
76-
},
77-
{
78-
type: 'single',
79-
name: 'Sender Localpart',
80-
description: null,
81-
value: parsed.sender_localpart || 'unknown',
82-
masked: false,
83-
copyable: true,
84-
qr: false,
85-
},
86-
{
87-
type: 'single',
88-
name: 'AS Token',
89-
description: null,
90-
value: parsed.as_token || 'unknown',
91-
masked: true,
92-
copyable: true,
93-
qr: false,
94-
},
95-
{
96-
type: 'single',
97-
name: 'HS Token',
98-
description: null,
99-
value: parsed.hs_token || 'unknown',
100-
masked: true,
101-
copyable: true,
102-
qr: false,
103-
},
104-
{
105-
type: 'single',
106-
name: 'Rate Limited',
107-
description: null,
108-
value: String(parsed.rate_limited ?? 'unknown'),
109-
masked: false,
110-
copyable: false,
111-
qr: false,
112-
},
113-
]
113+
const match = filePath.match(new RegExp(`/${appservicesSubpath}/(.+)\\.yaml$`))
114+
if (!match) continue
115+
const id = match[1]
114116

115-
if (parsed.namespaces?.users?.[0]?.regex) {
116-
fields.push({
117-
type: 'single',
118-
name: 'User Namespace',
119-
description: null,
120-
value: parsed.namespaces.users[0].regex,
121-
masked: false,
122-
copyable: true,
123-
qr: false,
124-
})
125-
}
117+
try {
118+
const reg = await appserviceRegistrationYaml(id).read().once()
119+
if (!reg) throw new Error(`appservices/${id}.yaml not found`)
126120

127121
groups.push({
128122
type: 'group',
129-
name: parsed.id || subpath,
123+
name: reg.id,
130124
description: null,
131-
value: fields,
125+
value: appserviceFields(reg),
132126
})
133127
} catch {
134128
groups.push({
135129
type: 'single',
136-
name: subpath,
130+
name: id,
137131
description: null,
138132
value: 'Error reading registration file',
139133
masked: false,

startos/actions/registerAppservice.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import {
2+
appserviceRegistrationYaml,
3+
appservicesSubpath,
4+
} from '../fileModels/appserviceRegistration.yaml'
15
import { homeserverYaml } from '../fileModels/homeserver.yml'
26
import { sdk } from '../sdk'
37
import { mountpoint } from '../utils'
48

59
const { InputSpec, Value } = sdk
610

7-
const appservicesSubpath = 'appservices'
8-
911
export const inputSpec = InputSpec.of({
1012
id: Value.text({
1113
name: 'Appservice ID',
@@ -95,25 +97,21 @@ export const registerAppservice = sdk.Action.withInput(
9597
userNamespaceRegex,
9698
} = input
9799

98-
const registrationYaml = [
99-
`id: ${id}`,
100-
`url: ${url}`,
101-
`as_token: ${asToken}`,
102-
`hs_token: ${hsToken}`,
103-
`sender_localpart: ${senderLocalpart}`,
104-
`rate_limited: ${rateLimited}`,
105-
`namespaces:`,
106-
` users:`,
107-
` - regex: '${userNamespaceRegex}'`,
108-
` exclusive: true`,
109-
` aliases: []`,
110-
` rooms: []`,
111-
].join('\n')
112-
113-
const registrationFile = `${appservicesSubpath}/${id}.yaml`
114-
const registrationPath = `${mountpoint}/${registrationFile}`
100+
const registrationPath = `${mountpoint}/${appservicesSubpath}/${id}.yaml`
115101

116-
await sdk.volumes.main.writeFile(registrationFile, registrationYaml)
102+
await appserviceRegistrationYaml(id).write(effects, {
103+
id,
104+
url,
105+
as_token: asToken,
106+
hs_token: hsToken,
107+
sender_localpart: senderLocalpart,
108+
rate_limited: rateLimited,
109+
namespaces: {
110+
users: [{ regex: userNamespaceRegex, exclusive: true }],
111+
aliases: [],
112+
rooms: [],
113+
},
114+
})
117115

118116
const currentFiles =
119117
(await homeserverYaml.read((h) => h.app_service_config_files).once()) ||
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { matches, FileHelper } from '@start9labs/start-sdk'
2+
import { sdk } from '../sdk'
3+
4+
const { object, string, boolean, arrayOf } = matches
5+
6+
const shape = object({
7+
id: string,
8+
url: string,
9+
as_token: string,
10+
hs_token: string,
11+
sender_localpart: string,
12+
rate_limited: boolean.onMismatch(false),
13+
namespaces: object({
14+
users: arrayOf(
15+
object({
16+
regex: string,
17+
exclusive: boolean.onMismatch(true),
18+
}),
19+
).onMismatch([]),
20+
aliases: arrayOf(string).onMismatch([]),
21+
rooms: arrayOf(string).onMismatch([]),
22+
}),
23+
})
24+
25+
export type AppserviceRegistration = typeof shape._TYPE
26+
27+
export const appservicesSubpath = 'appservices'
28+
29+
export const appserviceRegistrationYaml = (id: string) =>
30+
FileHelper.yaml(
31+
{
32+
base: sdk.volumes.main,
33+
subpath: `${appservicesSubpath}/${id}.yaml`,
34+
},
35+
shape,
36+
)

0 commit comments

Comments
 (0)