Sesame-Server/Sources/App/configure.swift
2023-11-28 08:46:26 +01:00

107 lines
3.3 KiB
Swift
Executable File

import Vapor
import Clairvoyant
import ClairvoyantVapor
import ClairvoyantBinaryCodable
var deviceManager: DeviceManager!
private var provider: VaporMetricProvider!
private var asyncScheduler = MultiThreadedEventLoopGroup(numberOfThreads: 2)
private let df: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .short
df.timeStyle = .short
return df
}()
enum ServerError: Error {
case invalidAuthenticationFileContent
case invalidAuthenticationToken
}
// configures your application
public func configure(_ app: Application) throws {
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
let configUrl = storageFolder.appendingPathComponent("config.json")
let config = try Config(loadFrom: configUrl)
let logFolder = config.logURL(possiblyRelativeTo: storageFolder)
let monitor = MetricObserver(logFileFolder: logFolder, logMetricId: "sesame.log")
MetricObserver.standard = monitor
let status = Metric<ServerStatus>("sesame.status")
asyncScheduler.schedule {
_ = try await status.update(.initializing)
}
app.http.server.configuration.port = config.port
let keyFile = storageFolder.appendingPathComponent(config.keyFileName)
let (deviceKey, remoteKey) = try loadKeys(at: keyFile)
deviceManager = DeviceManager(deviceKey: deviceKey, remoteKey: remoteKey, deviceTimeout: config.deviceTimeout)
try routes(app)
provider = .init(observer: monitor, accessManager: config.authenticationTokens)
provider.asyncScheduler = asyncScheduler
provider.registerRoutes(app)
monitor.saveCurrentListOfMetricsToLogFolder()
asyncScheduler.schedule {
_ = try await status.update(.nominal)
await deviceManager.updateDeviceConnectionMetric()
}
log("[\(df.string(from: Date()))] Server started")
// Gracefully shut down by closing potentially open socket
// Must be done after app is running, otherwise error is thrown
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
_ = app.server.onShutdown.always { _ in
print("[\(df.string(from: Date()))] Server shutdown")
asyncScheduler.schedule {
await deviceManager.removeDeviceConnection()
}
}
}
}
public func shutdown() {
try? asyncScheduler.syncShutdownGracefully()
}
private func loadKeys(at url: URL) throws -> (deviceKey: Data, remoteKey: Data) {
let authContent: [Data] = try String(contentsOf: url)
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "\n")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.map {
guard let key = Data(fromHexEncodedString: $0) else {
throw ServerError.invalidAuthenticationToken
}
guard key.count == SHA256.byteCount else {
throw ServerError.invalidAuthenticationToken
}
return key
}
guard authContent.count == 2 else {
throw ServerError.invalidAuthenticationFileContent
}
return (deviceKey: authContent[0], remoteKey: authContent[1])
}
func log(_ message: String) {
guard let observer = MetricObserver.standard else {
print(message)
return
}
asyncScheduler.schedule {
await observer.log(message)
}
}