@evanp/activitypub-bot 0.9.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.github/workflows/main.yml +34 -0
  2. package/.github/workflows/{tag-docker.yml → tag.yml} +57 -5
  3. package/.nvmrc +1 -0
  4. package/Dockerfile +10 -16
  5. package/README.md +87 -13
  6. package/bin/activitypub-bot.js +68 -0
  7. package/lib/activitydeliverer.js +260 -0
  8. package/lib/activityhandler.js +14 -0
  9. package/lib/activitypubclient.js +52 -1
  10. package/lib/activitystreams.js +31 -0
  11. package/lib/actorstorage.js +18 -28
  12. package/lib/app.js +18 -7
  13. package/lib/bot.js +7 -0
  14. package/lib/botcontext.js +62 -0
  15. package/lib/botdatastorage.js +0 -13
  16. package/lib/botfactory.js +24 -0
  17. package/lib/botmaker.js +23 -0
  18. package/lib/index.js +3 -0
  19. package/lib/keystorage.js +7 -24
  20. package/lib/migrations/001-initial.js +107 -0
  21. package/lib/migrations/index.js +28 -0
  22. package/lib/objectcache.js +4 -1
  23. package/lib/objectstorage.js +0 -36
  24. package/lib/remotekeystorage.js +0 -24
  25. package/lib/routes/collection.js +6 -2
  26. package/lib/routes/inbox.js +7 -20
  27. package/lib/routes/sharedinbox.js +54 -0
  28. package/lib/routes/user.js +11 -5
  29. package/lib/routes/webfinger.js +11 -2
  30. package/lib/urlformatter.js +8 -0
  31. package/package.json +14 -7
  32. package/tests/activitydistributor.test.js +3 -3
  33. package/tests/activityhandler.test.js +96 -5
  34. package/tests/activitypubclient.test.js +115 -130
  35. package/tests/actorstorage.test.js +26 -4
  36. package/tests/authorizer.test.js +3 -8
  37. package/tests/botcontext.test.js +109 -63
  38. package/tests/botdatastorage.test.js +3 -2
  39. package/tests/botfactory.provincebotfactory.test.js +430 -0
  40. package/tests/fixtures/bots.js +13 -1
  41. package/tests/fixtures/eventloggingbot.js +57 -0
  42. package/tests/fixtures/provincebotfactory.js +53 -0
  43. package/tests/httpsignature.test.js +3 -4
  44. package/tests/httpsignatureauthenticator.test.js +3 -3
  45. package/tests/index.test.js +10 -0
  46. package/tests/keystorage.test.js +37 -2
  47. package/tests/microsyntax.test.js +3 -2
  48. package/tests/objectstorage.test.js +4 -3
  49. package/tests/remotekeystorage.test.js +10 -8
  50. package/tests/routes.actor.test.js +7 -0
  51. package/tests/routes.collection.test.js +0 -1
  52. package/tests/routes.inbox.test.js +1 -0
  53. package/tests/routes.object.test.js +44 -38
  54. package/tests/routes.sharedinbox.test.js +473 -0
  55. package/tests/routes.webfinger.test.js +27 -0
  56. package/tests/utils/nock.js +250 -27
  57. package/.github/workflows/main-docker.yml +0 -45
  58. package/index.js +0 -23
@@ -3,10 +3,20 @@ import nock from 'nock'
3
3
  import crypto from 'node:crypto'
4
4
  import { promisify } from 'node:util'
5
5
 
6
+ const PAGE_SIZE = 20
7
+
6
8
  const generateKeyPair = promisify(crypto.generateKeyPair)
7
9
 
10
+ const defaultDomain = 'social.example'
11
+
8
12
  const domains = new Map()
9
- domains['social.example'] = new Map()
13
+ domains[defaultDomain] = new Map()
14
+
15
+ const graph = new Map()
16
+ graph[defaultDomain] = new Map()
17
+
18
+ const collections = new Map()
19
+ collections[defaultDomain] = new Map()
10
20
 
11
21
  const newKeyPair = async () => {
12
22
  return await generateKeyPair(
@@ -25,7 +35,7 @@ const newKeyPair = async () => {
25
35
  )
26
36
  }
27
37
 
28
- export const getPair = async (username, domain = 'social.example') => {
38
+ export const getPair = async (username, domain = defaultDomain) => {
29
39
  if (!domains.has(domain)) {
30
40
  domains.set(domain, new Map())
31
41
  }
@@ -36,17 +46,57 @@ export const getPair = async (username, domain = 'social.example') => {
36
46
  return domains.get(domain).get(username)
37
47
  }
38
48
 
39
- export const getPublicKey = async (username, domain = 'social.example') => {
49
+ export const getPublicKey = async (username, domain = defaultDomain) => {
40
50
  const pair = await getPair(username, domain)
41
51
  return pair.publicKey
42
52
  }
43
53
 
44
- export const getPrivateKey = async (username, domain = 'social.example') => {
54
+ export const getPrivateKey = async (username, domain = defaultDomain) => {
45
55
  const pair = await getPair(username, domain)
46
56
  return pair.privateKey
47
57
  }
48
58
 
49
- export const nockSignature = async ({ method = 'GET', url, date, digest = null, username, domain = 'social.example' }) => {
59
+ function ensureGraph (domain, username) {
60
+ if (!graph.has(domain)) {
61
+ graph.set(domain, new Map())
62
+ }
63
+ if (!graph.get(domain).has(username)) {
64
+ graph.get(domain).set(username, new Map())
65
+ graph.get(domain).get(username).set('followers', [])
66
+ graph.get(domain).get(username).set('following', [])
67
+ }
68
+ return graph.get(domain).get(username)
69
+ }
70
+
71
+ export function addFollower (username, id, domain = defaultDomain) {
72
+ ensureGraph(domain, username).get('followers').unshift(id)
73
+ }
74
+
75
+ export function addFollowing (username, id, domain = defaultDomain) {
76
+ ensureGraph(domain, username).get('following').unshift(id)
77
+ }
78
+
79
+ function ensureCollection (domain, username, collection) {
80
+ if (!collections.has(domain)) {
81
+ collections.set(domain, new Map())
82
+ }
83
+ const dc = collections.get(domain)
84
+ if (!dc.has(username)) {
85
+ dc.set(username, new Map())
86
+ }
87
+ const dcu = dc.get(username)
88
+ if (!dcu.has(collection)) {
89
+ dcu.set(collection, [])
90
+ }
91
+ const dcuc = dcu.get(collection)
92
+ return dcuc
93
+ }
94
+
95
+ export function addToCollection (username, collection, item, domain = defaultDomain) {
96
+ ensureCollection(domain, username, collection).unshift(item)
97
+ }
98
+
99
+ export const nockSignature = async ({ method = 'GET', url, date, digest = null, username, domain = defaultDomain }) => {
50
100
  const privateKey = await getPrivateKey(username, domain)
51
101
  const keyId = nockFormat({ username, key: true, domain })
52
102
  const parsed = new URL(url)
@@ -66,7 +116,7 @@ export const nockSignature = async ({ method = 'GET', url, date, digest = null,
66
116
  return `keyId="${keyId}",headers="(request-target) host date${(digest) ? ' digest' : ''}",signature="${signature.replace(/"/g, '\\"')}",algorithm="rsa-sha256"`
67
117
  }
68
118
 
69
- export const nockSignatureFragment = async ({ method = 'GET', url, date, digest = null, username, domain = 'social.example' }) => {
119
+ export const nockSignatureFragment = async ({ method = 'GET', url, date, digest = null, username, domain = defaultDomain }) => {
70
120
  const keyId = nockFormat({ username, domain }) + '#main-key'
71
121
  const privateKey = await getPrivateKey(username, domain)
72
122
  const parsed = new URL(url)
@@ -86,10 +136,10 @@ export const nockSignatureFragment = async ({ method = 'GET', url, date, digest
86
136
  return `keyId="${keyId}",headers="(request-target) host date${(digest) ? ' digest' : ''}",signature="${signature.replace(/"/g, '\\"')}",algorithm="rsa-sha256"`
87
137
  }
88
138
 
89
- export const nockKeyRotate = async (username, domain = 'social.example') =>
139
+ export const nockKeyRotate = async (username, domain = defaultDomain) =>
90
140
  domains.get(domain).set(username, await newKeyPair(username))
91
141
 
92
- export const makeActor = async (username, domain = 'social.example') =>
142
+ export const makeActor = async (username, domain = defaultDomain) =>
93
143
  await as2.import({
94
144
  '@context': [
95
145
  'https://www.w3.org/ns/activitystreams',
@@ -124,7 +174,8 @@ export async function makeObject (
124
174
  username,
125
175
  type,
126
176
  num,
127
- domain = 'social.example') {
177
+ domain = defaultDomain,
178
+ extra = {}) {
128
179
  const props = {
129
180
  '@context': [
130
181
  'https://www.w3.org/ns/activitystreams',
@@ -133,7 +184,8 @@ export async function makeObject (
133
184
  ],
134
185
  id: nockFormat({ username, type, num, domain }),
135
186
  type: uppercase(type),
136
- to: 'as:Public'
187
+ to: 'as:Public',
188
+ ...extra
137
189
  }
138
190
  if (isActivityType(type)) {
139
191
  props.actor = nockFormat({ username, domain })
@@ -149,7 +201,7 @@ export async function makeObject (
149
201
  return as2.import(props)
150
202
  }
151
203
 
152
- export const makeTransitive = (username, type, num, obj, domain = 'social.example') =>
204
+ export const makeTransitive = (username, type, num, obj, domain = defaultDomain) =>
153
205
  as2.import({
154
206
  id: nockFormat({ username, type, num, obj, domain }),
155
207
  type: uppercase(type),
@@ -159,7 +211,7 @@ export const makeTransitive = (username, type, num, obj, domain = 'social.exampl
159
211
  })
160
212
 
161
213
  const uppercase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
162
- const lowercase = (str) => str.charAt(0).toLowerCase() + str.slice(1)
214
+ const lowercase = (str) => str.toLowerCase()
163
215
 
164
216
  export const postInbox = {}
165
217
 
@@ -169,10 +221,28 @@ export const resetInbox = () => {
169
221
  }
170
222
  }
171
223
 
172
- export const nockSetup = (domain) =>
224
+ const requestHeaders = new Map()
225
+
226
+ export function getRequestHeaders (uri) {
227
+ return requestHeaders.get(uri)
228
+ }
229
+
230
+ export const resetRequestHeaders = () => {
231
+ requestHeaders.clear()
232
+ }
233
+
234
+ const captureRequestHeaders = (domain, uri, req) => {
235
+ const url = new URL(uri, `https://${domain}`).toString()
236
+ const headers = req?.headers || {}
237
+ requestHeaders.set(url, headers)
238
+ }
239
+
240
+ export const nockSetup = (domain, logger = null) =>
173
241
  nock(`https://${domain}`)
242
+ .persist()
174
243
  .get(/^\/.well-known\/webfinger/)
175
- .reply(async (uri, requestBody) => {
244
+ .reply(async function (uri, requestBody) {
245
+ captureRequestHeaders(domain, uri, this?.req)
176
246
  const parsed = new URL(uri, `https://${domain}`)
177
247
  const resource = parsed.searchParams.get('resource')
178
248
  if (!resource) {
@@ -194,17 +264,18 @@ export const nockSetup = (domain) =>
194
264
  { 'Content-Type': 'application/jrd+json' }]
195
265
  })
196
266
  .get(/^\/user\/(\w+)$/)
197
- .reply(async (uri, requestBody) => {
267
+ .reply(async function (uri, requestBody) {
268
+ captureRequestHeaders(domain, uri, this?.req)
198
269
  const username = uri.match(/^\/user\/(\w+)$/)[1]
199
270
  const actor = await makeActor(username, domain)
200
- const actorText = await actor.prettyWrite(
271
+ const actorText = await actor.write(
201
272
  { additional_context: 'https://w3id.org/security/v1' }
202
273
  )
203
274
  return [200, actorText, { 'Content-Type': 'application/activity+json' }]
204
275
  })
205
- .persist()
206
276
  .post(/^\/user\/(\w+)\/inbox$/)
207
- .reply(async (uri, requestBody) => {
277
+ .reply(async function (uri, requestBody) {
278
+ captureRequestHeaders(domain, uri, this?.req)
208
279
  const username = uri.match(/^\/user\/(\w+)\/inbox$/)[1]
209
280
  if (username in postInbox) {
210
281
  postInbox[username] += 1
@@ -213,9 +284,13 @@ export const nockSetup = (domain) =>
213
284
  }
214
285
  return [202, 'accepted']
215
286
  })
216
- .persist()
287
+ .get(/^\/user\/(\w+)\/inbox$/)
288
+ .reply(async function (uri, requestBody) {
289
+ return [403, 'forbidden']
290
+ })
217
291
  .get(/^\/user\/(\w+)\/publickey$/)
218
- .reply(async (uri, requestBody) => {
292
+ .reply(async function (uri, requestBody) {
293
+ captureRequestHeaders(domain, uri, this?.req)
219
294
  const username = uri.match(/^\/user\/(\w+)\/publickey$/)[1]
220
295
  const publicKey = await as2.import({
221
296
  '@context': [
@@ -227,25 +302,169 @@ export const nockSetup = (domain) =>
227
302
  type: 'CryptographicKey',
228
303
  publicKeyPem: await getPublicKey(username, domain)
229
304
  })
230
- const publicKeyText = await publicKey.prettyWrite(
305
+ const publicKeyText = await publicKey.write(
231
306
  { additional_context: 'https://w3id.org/security/v1' }
232
307
  )
233
308
  return [200, publicKeyText, { 'Content-Type': 'application/activity+json' }]
234
309
  })
235
- .persist()
310
+ .get(/^\/user\/(\w+)\/followers$/)
311
+ .reply(async function (uri, requestBody) {
312
+ captureRequestHeaders(domain, uri, this?.req)
313
+ const username = uri.match(/^\/user\/(\w+)\/followers$/)[1]
314
+ const items = ensureGraph(domain, username).get('followers')
315
+ const followers = await as2.import({
316
+ '@context': [
317
+ 'https://www.w3.org/ns/activitystreams',
318
+ 'https://w3id.org/fep/5711'
319
+ ],
320
+ id: `https://${domain}/user/${username}/followers`,
321
+ attributedTo: `https://${domain}/user/${username}`,
322
+ to: 'as:Public',
323
+ followersOf: `https://${domain}/user/${username}`,
324
+ type: 'OrderedCollection',
325
+ totalItems: items.length,
326
+ items
327
+ })
328
+ const followersText = await followers.write(
329
+ { additional_context: 'https://w3id.org/fep/5711' }
330
+ )
331
+ return [200, followersText, { 'Content-Type': 'application/activity+json' }]
332
+ })
333
+ .get(/^\/user\/(\w+)\/following$/)
334
+ .reply(async function (uri, requestBody) {
335
+ captureRequestHeaders(domain, uri, this?.req)
336
+ const username = uri.match(/^\/user\/(\w+)\/following$/)[1]
337
+ const items = ensureGraph(domain, username).get('following')
338
+ const following = await as2.import({
339
+ '@context': [
340
+ 'https://www.w3.org/ns/activitystreams',
341
+ 'https://w3id.org/fep/5711'
342
+ ],
343
+ id: `https://${domain}/user/${username}/following`,
344
+ attributedTo: `https://${domain}/user/${username}`,
345
+ to: 'as:Public',
346
+ followersOf: `https://${domain}/user/${username}`,
347
+ type: 'OrderedCollection',
348
+ totalItems: items.length,
349
+ items
350
+ })
351
+ const followingText = await following.write(
352
+ { additional_context: 'https://w3id.org/fep/5711' }
353
+ )
354
+ return [200, followingText, { 'Content-Type': 'application/activity+json' }]
355
+ })
356
+ .get(/^\/user\/(\w+)\/collection\/(\d+)$/)
357
+ .reply(async function (uri, requestBody) {
358
+ logger?.debug('Capturing request headers')
359
+ captureRequestHeaders(domain, uri, this?.req)
360
+ logger?.debug('Matching parameters')
361
+ const match = uri.match(/^\/user\/(\w+)\/collection\/(\d+)$/)
362
+ const username = match[1]
363
+ const type = 'Collection'
364
+ const num = parseInt(match[2])
365
+ logger?.debug('Ensuring collection')
366
+ const items = ensureCollection(domain, username, num)
367
+ logger?.debug('Making collection object')
368
+ const summary = `${num} collection by ${username}`
369
+ const obj = await makeObject(username, type, num, domain, { items, summary })
370
+ logger?.debug('Writing collection object to text')
371
+ const objText = await obj.write({ useOriginalContext: true })
372
+ logger?.debug('Sending output')
373
+ return [200, objText, { 'Content-Type': 'application/activity+json' }]
374
+ })
375
+ .get(/^\/user\/(\w+)\/orderedcollection\/(\d+)$/)
376
+ .reply(async function (uri, requestBody) {
377
+ captureRequestHeaders(domain, uri, this?.req)
378
+ const match = uri.match(/^\/user\/(\w+)\/orderedcollection\/(\d+)$/)
379
+ const username = match[1]
380
+ const type = 'OrderedCollection'
381
+ const num = parseInt(match[2])
382
+ const orderedItems = ensureCollection(domain, username, num)
383
+ const summary = `${num} ordered collection by ${username}`
384
+ const obj = await makeObject(username, type, num, domain, { orderedItems, summary })
385
+ const objText = await obj.write({ useOriginalContext: true })
386
+ return [200, objText, { 'Content-Type': 'application/activity+json' }]
387
+ })
388
+ .get(/^\/user\/(\w+)\/pagedcollection\/(\d+)$/)
389
+ .reply(async function (uri, requestBody) {
390
+ captureRequestHeaders(domain, uri, this?.req)
391
+ const match = uri.match(/^\/user\/(\w+)\/pagedcollection\/(\d+)$/)
392
+ const username = match[1]
393
+ const type = 'Collection'
394
+ const num = parseInt(match[2])
395
+ const items = ensureCollection(domain, username, num)
396
+ const totalItems = items.length
397
+ const first = (totalItems > 0)
398
+ ? nockFormat({ username, domain, type: 'PagedCollection', num, page: 0 })
399
+ : undefined
400
+ const obj = await makeObject(username, type, num, domain, { totalItems, first })
401
+ const objText = await obj.write({ useOriginalContext: true })
402
+ return [200, objText, { 'Content-Type': 'application/activity+json' }]
403
+ })
404
+ .get(/^\/user\/(\w+)\/pagedcollection\/(\d+)\/page\/(\d+)$/)
405
+ .reply(async function (uri, requestBody) {
406
+ captureRequestHeaders(domain, uri, this?.req)
407
+ const match = uri.match(/^\/user\/(\w+)\/pagedcollection\/(\d+)\/page\/(\d+)$/)
408
+ const username = match[1]
409
+ const type = 'CollectionPage'
410
+ const num = parseInt(match[2])
411
+ const page = parseInt(match[3])
412
+ const allItems = ensureCollection(domain, username, num)
413
+ const items = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
414
+ const next = (allItems.length > (page + 1) * PAGE_SIZE)
415
+ ? nockFormat({ username, domain, type: 'PagedCollection', num, page: page + 1 })
416
+ : undefined
417
+ const obj = await makeObject(username, type, num, domain, { items, next })
418
+ const objText = await obj.write({ useOriginalContext: true })
419
+ return [200, objText, { 'Content-Type': 'application/activity+json' }]
420
+ })
421
+ .get(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)$/)
422
+ .reply(async function (uri, requestBody) {
423
+ captureRequestHeaders(domain, uri, this?.req)
424
+ const match = uri.match(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)$/)
425
+ const username = match[1]
426
+ const type = 'OrderedCollection'
427
+ const num = parseInt(match[2])
428
+ const items = ensureCollection(domain, username, num)
429
+ const totalItems = items.length
430
+ const first = (totalItems > 0)
431
+ ? nockFormat({ username, domain, type: 'PagedOrderedCollection', num, page: 0 })
432
+ : undefined
433
+ const obj = await makeObject(username, type, num, domain, { totalItems, first })
434
+ const objText = await obj.write({ useOriginalContext: true })
435
+ return [200, objText, { 'Content-Type': 'application/activity+json' }]
436
+ })
437
+ .get(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)\/page\/(\d+)$/)
438
+ .reply(async function (uri, requestBody) {
439
+ captureRequestHeaders(domain, uri, this?.req)
440
+ const match = uri.match(/^\/user\/(\w+)\/pagedorderedcollection\/(\d+)\/page\/(\d+)$/)
441
+ const username = match[1]
442
+ const type = 'OrderedCollectionPage'
443
+ const num = parseInt(match[2])
444
+ const page = parseInt(match[3])
445
+ const allItems = ensureCollection(domain, username, num)
446
+ const orderedItems = allItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
447
+ const next = (allItems.length > (page + 1) * PAGE_SIZE)
448
+ ? nockFormat({ username, domain, type: 'PagedOrderedCollection', num, page: page + 1 })
449
+ : undefined
450
+ const obj = await makeObject(username, type, num, domain, { orderedItems, next })
451
+ const objText = await obj.write({ useOriginalContext: true })
452
+ return [200, objText, { 'Content-Type': 'application/activity+json' }]
453
+ })
236
454
  .get(/^\/user\/(\w+)\/(\w+)\/(\d+)$/)
237
- .reply(async (uri, requestBody) => {
455
+ .reply(async function (uri, requestBody) {
456
+ captureRequestHeaders(domain, uri, this?.req)
238
457
  const match = uri.match(/^\/user\/(\w+)\/(\w+)\/(\d+)$/)
239
458
  const username = match[1]
240
459
  const type = uppercase(match[2])
241
460
  const num = match[3]
242
461
  const obj = await makeObject(username, type, num, domain)
243
- const objText = await obj.prettyWrite({ useOriginalContext: true })
462
+ const objText = await obj.write({ useOriginalContext: true })
244
463
  return [200, objText, { 'Content-Type': 'application/activity+json' }]
245
464
  })
246
- .persist()
247
465
  .get(/^\/user\/(\w+)\/(\w+)\/(\d+)\/(.*)$/)
248
- .reply(async (uri, requestBody) => {
466
+ .reply(async function (uri, requestBody) {
467
+ captureRequestHeaders(domain, uri, this?.req)
249
468
  const match = uri.match(/^\/user\/(\w+)\/(\w+)\/(\d+)\/(.*)$/)
250
469
  const username = match[1]
251
470
  const type = match[2]
@@ -256,10 +475,12 @@ export const nockSetup = (domain) =>
256
475
  return [200, actText, { 'Content-Type': 'application/activity+json' }]
257
476
  })
258
477
 
259
- export function nockFormat ({ username, type, num, obj, key, domain = 'social.example' }) {
478
+ export function nockFormat ({ username, type, num, obj, key, collection, page, domain = defaultDomain }) {
260
479
  let url = `https://${domain}/user/${username}`
261
480
  if (key) {
262
481
  url = `${url}/publickey`
482
+ } else if (collection) {
483
+ url = `${url}/${collection}`
263
484
  } else {
264
485
  if (type && num) {
265
486
  url = `${url}/${lowercase(type)}/${num}`
@@ -269,6 +490,8 @@ export function nockFormat ({ username, type, num, obj, key, domain = 'social.ex
269
490
  } else {
270
491
  url = `${url}/${obj}`
271
492
  }
493
+ } else if (typeof page === 'number') {
494
+ url = `${url}/page/${page}`
272
495
  }
273
496
  }
274
497
  }
@@ -1,45 +0,0 @@
1
- name: Build and push Docker image
2
-
3
- on:
4
- workflow_dispatch:
5
- push:
6
- branches:
7
- - main
8
-
9
- permissions:
10
- contents: read
11
- packages: write
12
-
13
- jobs:
14
- build:
15
- runs-on: ubuntu-latest
16
-
17
- env:
18
- REGISTRY: ghcr.io
19
- REPOSITORY: evanp/activitypub-bot
20
-
21
- steps:
22
- - name: Checkout repository
23
- uses: actions/checkout@v2
24
-
25
- - name: Set up Docker Buildx
26
- uses: docker/setup-buildx-action@v1
27
-
28
- - name: Log in to GitHub Container Registry
29
- uses: docker/login-action@v1
30
- with:
31
- registry: ${{ env.REGISTRY }}
32
- username: ${{ github.actor }}
33
- password: ${{ secrets.GITHUB_TOKEN }}
34
-
35
- - name: Build and push
36
- uses: docker/build-push-action@v5
37
- with:
38
- context: .
39
- push: true
40
- platforms: linux/amd64,linux/arm64
41
- tags: |
42
- ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest
43
- ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ github.sha }}
44
- cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest
45
- cache-to: type=inline
package/index.js DELETED
@@ -1,23 +0,0 @@
1
- import { makeApp } from './lib/app.js'
2
-
3
- const DATABASE_URL = process.env.DATABASE_URL || 'sqlite::memory:'
4
- const ORIGIN = process.env.ORIGIN || 'https://activitypubbot.test'
5
- const PORT = process.env.PORT || 9000 // HAL
6
- const BOTS_CONFIG_FILE = process.env.BOTS_CONFIG_FILE || './bots/index.js'
7
- const LOG_LEVEL = process.env.LOG_LEVEL || (process.env.NODE_ENV === 'test' ? 'silent' : 'info')
8
-
9
- const bots = (await import(BOTS_CONFIG_FILE)).default
10
-
11
- const app = await makeApp(DATABASE_URL, ORIGIN, bots, LOG_LEVEL)
12
-
13
- const server = app.listen(parseInt(PORT), () => {
14
- app.locals.logger.info(`Listening on port ${PORT}`)
15
- })
16
-
17
- process.on('SIGTERM', () => {
18
- console.log('Received SIGTERM')
19
- server.close(async () => {
20
- await app.cleanup()
21
- process.exit(0)
22
- })
23
- })