public uptime monitoring + (soon) observability with events saved to PDS
at main 3.3 kB view raw
1import { Client, CredentialManager, ok } from '@atcute/client'; 2import type {} from '@atcute/atproto'; 3import type { Did } from '@atcute/lexicons'; 4import { readFile } from 'node:fs/promises'; 5import { checkService } from './pinger.ts'; 6import type { ServiceConfig, UptimeCheck } from './types.ts'; 7 8/** 9 * main worker function that monitors services and publishes uptime checks to AT Protocol 10 */ 11async function main() { 12 // load configuration 13 const configPath = new URL('../config.json', import.meta.url); 14 const configData = await readFile(configPath, 'utf-8'); 15 const config = JSON.parse(configData) as { 16 pds: string; 17 identifier: string; 18 password: string; 19 services: ServiceConfig[]; 20 checkInterval: number; 21 }; 22 23 // initialize AT Protocol client with authentication 24 const manager = new CredentialManager({ service: config.pds }); 25 const rpc = new Client({ handler: manager }); 26 27 // authenticate with app password 28 await manager.login({ 29 identifier: config.identifier, 30 password: config.password, 31 }); 32 33 console.log(`authenticated as ${manager.session?.did}`); 34 35 // function to perform checks and publish results 36 const performChecks = async () => { 37 console.log(`\nchecking ${config.services.length} services...`); 38 39 // run all checks concurrently 40 const checkPromises = config.services.map(async (service) => { 41 try { 42 const check = await checkService(service); 43 await publishCheck(rpc, manager.session!.did, check); 44 console.log( 45 `${service.name}: ${check.status} (${check.responseTime}ms)`, 46 ); 47 } catch (error) { 48 console.error(`${service.name}: error publishing check`, error); 49 } 50 }); 51 52 // wait for all checks to complete 53 await Promise.all(checkPromises); 54 }; 55 56 // run checks immediately 57 await performChecks(); 58 59 // schedule periodic checks 60 console.log(`scheduling checks every ${config.checkInterval} seconds`); 61 const interval = setInterval(performChecks, config.checkInterval * 1000); 62 63 // keep the process alive 64 interval.unref(); 65 process.stdin.resume(); 66 67 // handle graceful shutdown 68 process.on('SIGINT', () => { 69 console.log('\nshutting down gracefully...'); 70 clearInterval(interval); 71 process.exit(0); 72 }); 73 74 process.on('SIGTERM', () => { 75 console.log('\nshutting down gracefully...'); 76 clearInterval(interval); 77 process.exit(0); 78 }); 79} 80 81/** 82 * publishes an uptime check record to the PDS 83 * 84 * @param rpc the client instance 85 * @param did the DID of the authenticated user 86 * @param check the uptime check data to publish 87 */ 88async function publishCheck(rpc: Client, did: Did, check: UptimeCheck) { 89 await ok( 90 rpc.post('com.atproto.repo.createRecord', { 91 input: { 92 repo: did, 93 collection: 'pet.nkp.uptime.check', 94 record: { 95 ...(check.groupName && { groupName: check.groupName }), 96 serviceName: check.serviceName, 97 ...(check.region && { region: check.region }), 98 serviceUrl: check.serviceUrl, 99 checkedAt: check.checkedAt, 100 status: check.status, 101 responseTime: check.responseTime, 102 ...(check.httpStatus && { httpStatus: check.httpStatus }), 103 ...(check.errorMessage && { errorMessage: check.errorMessage }), 104 }, 105 }, 106 }), 107 ); 108} 109 110main().catch((error) => { 111 console.error('fatal error:', error); 112 process.exit(1); 113});