|
// retoor <retoor@molodetz.nl>
|
|
|
|
import Foundation
|
|
import Logging
|
|
|
|
let logger = Logger(label: "molouptime.main")
|
|
|
|
@main
|
|
struct Molouptime {
|
|
static let engine = CheckEngine()
|
|
|
|
static func main() async {
|
|
logger.info("molouptime engine starting")
|
|
|
|
let stdin = FileHandle.standardInput
|
|
let stdout = FileHandle.standardOutput
|
|
var buffer = Data()
|
|
|
|
// Set stdin to read line-by-line
|
|
stdin.readabilityHandler = { handle in
|
|
let data = handle.availableData
|
|
guard !data.isEmpty else { return }
|
|
buffer.append(data)
|
|
|
|
while let newlineRange = buffer.firstIndex(of: UInt8(ascii: "\n")) {
|
|
let lineData = buffer[..<newlineRange]
|
|
buffer = buffer[buffer.index(after: newlineRange)...]
|
|
|
|
guard let line = String(data: lineData, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!line.isEmpty else {
|
|
continue
|
|
}
|
|
|
|
Task {
|
|
await handleLine(line, stdout: stdout)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep the process alive
|
|
dispatchMain()
|
|
}
|
|
|
|
static func handleLine(_ line: String, stdout: FileHandle) async {
|
|
guard let request = IpcProtocol.readRequest(from: line) else {
|
|
let errorResp = IpcResponse(
|
|
reqId: 0, kind: "error",
|
|
payload: "Invalid JSON", checks: nil, metrics: nil,
|
|
error: "Failed to parse request"
|
|
)
|
|
writeResponse(errorResp, to: stdout)
|
|
return
|
|
}
|
|
|
|
switch request.command {
|
|
case .ping:
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "pong", payload: "ok", checks: nil, metrics: nil, error: nil)
|
|
writeResponse(resp, to: stdout)
|
|
|
|
case .addCheck:
|
|
guard let payload = request.payload,
|
|
let uid = payload["uid"],
|
|
let type = payload["type"],
|
|
let target = payload["target"],
|
|
let intervalStr = payload["interval"],
|
|
let interval = Int(intervalStr) else {
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "error", payload: nil, checks: nil, metrics: nil, error: "Missing required fields")
|
|
writeResponse(resp, to: stdout)
|
|
return
|
|
}
|
|
let port = payload["port"].flatMap { Int($0) }
|
|
let protocolType = payload["protocol"]
|
|
|
|
let check = CheckTarget(
|
|
uid: uid, type: type, target: target,
|
|
intervalSeconds: interval, port: port,
|
|
protocolType: protocolType
|
|
)
|
|
await engine.addCheck(check)
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "added", payload: uid, checks: nil, metrics: nil, error: nil)
|
|
writeResponse(resp, to: stdout)
|
|
|
|
case .removeCheck:
|
|
let uid = request.payload?["uid"] ?? ""
|
|
await engine.removeCheck(uid)
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "removed", payload: uid, checks: nil, metrics: nil, error: nil)
|
|
writeResponse(resp, to: stdout)
|
|
|
|
case .updateCheck:
|
|
guard let payload = request.payload,
|
|
let uid = payload["uid"] else {
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "error", payload: nil, checks: nil, metrics: nil, error: "Missing uid")
|
|
writeResponse(resp, to: stdout)
|
|
return
|
|
}
|
|
let type = payload["type"] ?? "http"
|
|
let target = payload["target"] ?? ""
|
|
let interval = Int(payload["interval"] ?? "60") ?? 60
|
|
let port = payload["port"].flatMap { Int($0) }
|
|
let protocolType = payload["protocol"]
|
|
|
|
let check = CheckTarget(
|
|
uid: uid, type: type, target: target,
|
|
intervalSeconds: interval, port: port,
|
|
protocolType: protocolType
|
|
)
|
|
await engine.updateCheck(check)
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "updated", payload: uid, checks: nil, metrics: nil, error: nil)
|
|
writeResponse(resp, to: stdout)
|
|
|
|
case .listChecks:
|
|
let checks = await engine.listChecks()
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "checks", payload: nil, checks: checks, metrics: nil, error: nil)
|
|
writeResponse(resp, to: stdout)
|
|
|
|
case .getMetrics:
|
|
let metrics = await MetricSample?.none // Placeholder
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "metrics", payload: nil, checks: nil, metrics: nil, error: nil)
|
|
writeResponse(resp, to: stdout)
|
|
|
|
case .shutdown:
|
|
await engine.stop()
|
|
let resp = IpcResponse(reqId: request.reqId, kind: "shutdown", payload: "bye", checks: nil, metrics: nil, error: nil)
|
|
writeResponse(resp, to: stdout)
|
|
exit(0)
|
|
}
|
|
}
|
|
|
|
static func writeResponse(_ response: IpcResponse, to handle: FileHandle) {
|
|
guard let json = IpcProtocol.writeResponse(response) else { return }
|
|
handle.write(Data("\(json)\n".utf8))
|
|
}
|
|
}
|