packages feed

microdns-0.1.0.0: app/Main.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}

module Main where

import Control.Concurrent.Async
import Data.Coerce (coerce)
import Data.Maybe (catMaybes)
import qualified Network.DNS as DNS

import Options.Generic

import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import qualified Data.Text.IO as Text
import qualified Network.Wai.Handler.Warp as Warp
import qualified Network.Wai.Handler.WarpTLS as Warp
import qualified Network.Wai.Middleware.RequestLogger as RequestLogger
import qualified Paths_prodapi
import qualified Prod.App as Prod
import Prod.Status
import Prod.Tracer (tracePrint)
import qualified Text.Megaparsec as Megaparsec

import qualified MicroDNS
import qualified MicroDNS.DynamicRegistration as DynamicRegistration
import Servant

type DNSApex = Text

data Params
    = Plain
        { dnsPort :: Int <?> "DNS port number"
        , dnsApex :: Text <?> "delegated DNS apex"
        , webHmacSecretFile :: FilePath <?> "shared secret file for hmac"
        , webPort :: Int <?> "web console port number"
        , zoneFile :: FilePath <?> "zonefile"
        }
    | Tls
        { dnsPort :: Int <?> "DNS port number"
        , dnsApex :: Text <?> "delegated DNS apex"
        , webHmacSecretFile :: FilePath <?> "shared secret file for hmac"
        , webPort :: Int <?> "web console port number"
        , certFile :: FilePath <?> "certificate file"
        , keyFile :: FilePath <?> "key file"
        , zoneFile :: FilePath <?> "zonefile"
        }
    deriving (Generic, Show)
instance ParseRecord Params

main :: IO ()
main = do
    args <- getRecord "microdns"
    -- app glueing
    healthRt <- (Prod.alwaysReadyRuntime tracePrint)
    init <- Prod.initialize healthRt
    appRuntime <- initRuntime args
    let webapp =
            RequestLogger.logStdoutDev $
                Prod.app
                    init
                    apiStatus
                    (statusPage <> versionsSection [("prodapi", Paths_prodapi.version)])
                    (serveApi appRuntime)
                    (Proxy @Api)
    dnsrt <- MicroDNS.initRuntime (coerce $ dnsPort args) tracePrint
    configRRs <- loadConfigRRs (MicroDNS.apexFromText $ coerce $ dnsApex args) (coerce zoneFile args)
    _ <- traverse print configRRs
    let combinedRRs = DynamicRegistration.readRRs (dynamicRegistrationRuntime appRuntime) <> pure configRRs
    let dnsapp = MicroDNS.handleQuestion dnsrt (MicroDNS.ioLookup combinedRRs)

    let dns = MicroDNS.serve dnsrt dnsapp
    let web = runWebApp args webapp

    _ <-
        runConcurrently $
            (,,)
                <$> Concurrently web
                <*> Concurrently dns
    pure ()
  where
    runWebApp :: Params -> Application -> IO ()
    runWebApp params@(Plain _ _ _ _ _) webapp =
        let warpSettings = Warp.setPort (coerce $ webPort params) $ Warp.defaultSettings
         in Warp.runSettings warpSettings webapp
    runWebApp params@(Tls _ _ _ _ _ _ _) webapp =
        let tlsSettings = Warp.tlsSettings (coerce $ certFile params) (coerce $ keyFile params)
            warpTlsSettings = Warp.setPort (coerce $ webPort params) $ Warp.defaultSettings
         in Warp.runTLS tlsSettings warpTlsSettings webapp

    apiStatus :: IO Text
    apiStatus = pure "ok"

    loadConfigRRs :: MicroDNS.Apex -> FilePath -> IO [DNS.ResourceRecord]
    loadConfigRRs apex zfile = do
        zonecontent <- Megaparsec.parse MicroDNS.zoneFile zfile <$> Text.readFile zfile
        case zonecontent of
            Left err -> error $ show err
            Right zones -> pure $ MicroDNS.collectDirectives apex zones

type Api = DynamicRegistration.Api

data Runtime = Runtime
    { dynamicRegistrationRuntime :: DynamicRegistration.Runtime
    }

initRuntime :: Params -> IO Runtime
initRuntime params = do
    secret <- ByteString.readFile (coerce $ webHmacSecretFile params)
    Runtime
        <$> DynamicRegistration.initRuntime tracePrint secret (MicroDNS.apexFromText $ coerce $ dnsApex params)

serveApi :: Runtime -> Server Api
serveApi Runtime{..} = DynamicRegistration.handleDynamicRegistration dynamicRegistrationRuntime