1import os from "os";
2import { parseCanonicalResourceUri, type RecordKey } from "@atcute/lexicons";
3import type {
4 SystemsGazeBarometerCheck,
5 SystemsGazeBarometerHost,
6 SystemsGazeBarometerService,
7 SystemsGazeBarometerState,
8} from "barometer-lexicon";
9import {
10 err,
11 expect,
12 getRecord,
13 ok,
14 putRecord,
15 type CheckUri,
16 type CollectionUri,
17 type Result,
18 type ServiceUri,
19 type StateUri,
20} from "./utils";
21import { systemctlShow } from "./systemd";
22import { config } from "./config";
23import { now as generateTid } from "@atcute/tid";
24
25export interface State {
26 rkey: RecordKey;
27 record: SystemsGazeBarometerState.Main;
28}
29export interface Check {
30 rkey: RecordKey;
31 record: SystemsGazeBarometerCheck.Main;
32}
33export interface Service {
34 checks: Set<RecordKey>;
35 rkey: RecordKey;
36 record: SystemsGazeBarometerService.Main;
37}
38
39class Store {
40 services: Map<ServiceUri, Service>;
41 checks: Map<CheckUri, Check>;
42 states: Map<StateUri, State>;
43 host: SystemsGazeBarometerHost.Main | null;
44 hostname: string;
45
46 constructor() {
47 this.services = new Map();
48 this.checks = new Map();
49 this.states = new Map();
50 this.host = null;
51 this.hostname = os.hostname();
52 }
53
54 getOrFetch = async <
55 Nsid extends
56 | "systems.gaze.barometer.state"
57 | "systems.gaze.barometer.check"
58 | "systems.gaze.barometer.service",
59 Uri extends CollectionUri<Nsid>,
60 >(
61 uri: Uri,
62 ): Promise<
63 Result<
64 Uri extends StateUri
65 ? State
66 : Uri extends CheckUri
67 ? Check
68 : Uri extends ServiceUri
69 ? Service
70 : never,
71 string
72 >
73 > => {
74 const parsedUri = expect(parseCanonicalResourceUri(uri));
75 const nsid = parsedUri.collection;
76 const record = await getRecord(nsid as Nsid, parsedUri.rkey);
77 if (!record.ok)
78 return err(`record not found or is invalid: ${record.error}`);
79 const data = {
80 record: record.value,
81 rkey: parsedUri.rkey,
82 };
83
84 switch (nsid) {
85 case "systems.gaze.barometer.state":
86 this.states.set(uri as StateUri, data as State);
87 return ok(data as any);
88 case "systems.gaze.barometer.check":
89 this.checks.set(uri as CheckUri, data as Check);
90 return ok(data as any);
91 case "systems.gaze.barometer.service":
92 this.services.set(
93 uri as ServiceUri,
94 { checks: new Set(), ...data } as Service,
95 );
96 return ok(data as any);
97 default:
98 throw new Error(`unsupported namespace: ${nsid}`);
99 }
100 };
101
102 getServiceFromSystemd = async (
103 serviceName: string,
104 ): Promise<Result<[ServiceUri, Service], string>> => {
105 const serviceInfo = await systemctlShow(serviceName);
106 if (serviceInfo.ok) {
107 const record: SystemsGazeBarometerService.Main = {
108 $type: "systems.gaze.barometer.service",
109 name: serviceName,
110 description: serviceInfo.value.description,
111 hostedBy: `at://${config.repoDid}/systems.gaze.barometer.host/${store.hostname}`,
112 };
113 const rkey = generateTid();
114 const putAt = await putRecord(record, rkey);
115 const serviceUri = putAt.uri as ServiceUri;
116 const service: Service = {
117 record,
118 checks: new Set(),
119 rkey,
120 };
121 store.services.set(serviceUri, service);
122 return ok([serviceUri, service]);
123 } else {
124 return err(`could not fetch service from systemd: ${serviceInfo.error}`);
125 }
126 };
127}
128
129const store = new Store();
130export default store;