keter/Keter/Main.hs

168 lines
6.2 KiB
Haskell
Raw Normal View History

2012-05-14 12:18:09 +04:00
{-# LANGUAGE OverloadedStrings #-}
2013-06-03 15:15:13 +04:00
{-# LANGUAGE ViewPatterns #-}
2012-05-15 12:38:54 +04:00
{-# LANGUAGE NoImplicitPrelude #-}
2012-05-17 10:32:11 +04:00
{-# LANGUAGE TemplateHaskell #-}
2012-05-29 06:30:59 +04:00
{-# LANGUAGE RecordWildCards #-}
2012-10-24 18:31:18 +04:00
{-# LANGUAGE ScopedTypeVariables #-}
2012-05-11 12:29:25 +04:00
module Keter.Main
( keter
) where
2012-05-17 11:59:17 +04:00
import Keter.Prelude hiding (getCurrentTime)
2013-07-10 15:15:18 +04:00
import qualified Codec.Archive.TempTarball as TempFolder
2012-05-11 12:29:25 +04:00
import qualified Keter.App as App
2013-07-26 11:09:11 +04:00
import Keter.App (AppStartConfig (..))
2013-07-10 14:26:37 +04:00
import Keter.Types
2013-07-25 14:07:48 +04:00
import qualified Keter.HostManager as HostMan
import qualified Keter.PortPool as PortPool
import qualified Keter.Proxy as Proxy
2013-07-25 15:18:32 +04:00
import qualified Network.HTTP.ReverseProxy.Rewrite as Rewrite
2013-05-29 10:43:52 +04:00
import System.Posix.Files (modificationTime, getFileStatus)
import System.Posix.Signals (sigHUP, installHandler, Handler (Catch))
import qualified Data.Conduit.LogFile as LogFile
2013-07-25 18:35:16 +04:00
import qualified Keter.AppManager as AppMan
import Data.Monoid (mempty)
import Control.Monad (unless)
import qualified Data.Vector as V
2012-05-11 12:29:25 +04:00
2013-07-10 10:57:38 +04:00
import Data.Yaml.FilePath
2012-05-11 12:29:25 +04:00
import qualified Control.Concurrent.MVar as M
import Control.Concurrent (forkIO)
2012-05-11 12:29:25 +04:00
import qualified Data.Map as Map
2013-06-03 14:34:47 +04:00
import qualified System.FSNotify as FSN
2013-07-10 10:57:38 +04:00
import Control.Monad (forever, forM)
2012-05-14 11:15:50 +04:00
import qualified Filesystem.Path.CurrentOS as F
2012-05-29 06:30:59 +04:00
import qualified Filesystem as F
2012-10-24 18:31:18 +04:00
import Control.Exception (throwIO, try)
2012-05-15 12:38:54 +04:00
import qualified Prelude as P
2012-05-17 08:15:25 +04:00
import Data.Text.Encoding (encodeUtf8)
import Data.Time (getCurrentTime)
import qualified Data.Text as T
2013-05-29 10:43:52 +04:00
import Data.Maybe (fromMaybe, catMaybes)
2013-07-14 16:55:05 +04:00
import Control.Applicative ((<$>))
2012-10-24 18:31:18 +04:00
import System.Posix.User (userID, userGroupID, getUserEntryForName, getUserEntryForID, userName)
import qualified Data.Text.Read
import qualified Data.Set as Set
import qualified Network.HTTP.Conduit as HTTP (newManager)
2013-06-03 15:15:13 +04:00
import qualified Network.Wai.Handler.Warp as Warp
import Data.Conduit.Process.Unix (initProcessTracker)
2012-05-11 12:29:25 +04:00
2012-05-29 06:30:59 +04:00
keter :: P.FilePath -- ^ root directory or config file
2013-07-10 14:26:37 +04:00
-> [F.FilePath -> KIO (Either SomeException Plugin)]
2012-05-15 12:38:54 +04:00
-> P.IO ()
2013-07-10 14:26:37 +04:00
keter (F.decodeString -> input) mkPlugins = do
2012-05-29 06:30:59 +04:00
exists <- F.isFile input
2013-07-14 16:30:55 +04:00
KeterConfig{..} <-
2012-05-29 06:30:59 +04:00
if exists
2013-07-10 10:57:38 +04:00
then decodeFileRelative input >>= either
(\e -> P.error $ "Invalid config file: " ++ P.show e)
return
2013-07-14 16:30:55 +04:00
else return def { kconfigDir = input }
2012-05-29 06:30:59 +04:00
2012-10-24 18:31:18 +04:00
muid <-
2013-07-14 16:30:55 +04:00
case kconfigSetuid of
2012-10-24 18:31:18 +04:00
Nothing -> return Nothing
Just t -> do
x <- try $
case Data.Text.Read.decimal t of
Right (i, "") -> getUserEntryForID i
_ -> getUserEntryForName $ T.unpack t
case x of
Left (_ :: SomeException) -> P.error $ T.unpack $ "Invalid user ID: " ++ t
Right ue -> return $ Just (T.pack $ userName ue, (userID ue, userGroupID ue))
processTracker <- initProcessTracker
hostman <- HostMan.start
portpool <- PortPool.start kconfigPortPool
2013-07-14 16:30:55 +04:00
tf <- runThrow $ liftIO $ TempFolder.setup $ kconfigDir </> "temp"
plugins <- runThrow $ loadPlugins $ map ($ kconfigDir) mkPlugins
mainlog <- runThrow $ liftIO $ LogFile.openRotatingLog
2013-07-14 16:30:55 +04:00
(F.encodeString $ kconfigDir </> "log" </> "keter")
LogFile.defaultMaxTotal
2012-05-17 08:15:25 +04:00
let runKIO' = runKIO $ \ml -> do
now <- getCurrentTime
let bs = encodeUtf8 $ T.concat
2012-05-17 10:32:11 +04:00
[ T.take 22 $ show now
2012-05-17 08:15:25 +04:00
, ": "
, show ml
, "\n"
]
LogFile.addChunk mainlog bs
2012-05-17 08:15:25 +04:00
runKIOPrint = runKIO P.print
2012-05-11 12:29:25 +04:00
2013-06-03 15:15:13 +04:00
manager <- HTTP.newManager def
2013-07-25 20:58:54 +04:00
V.forM_ kconfigListeners
$ forkIO
. Proxy.reverseProxy
2013-07-14 16:30:55 +04:00
kconfigIpFromHeader
2013-06-03 15:15:13 +04:00
manager
2013-07-25 20:58:54 +04:00
(runKIOPrint . HostMan.lookupAction hostman)
2013-07-26 11:09:11 +04:00
let appStartConfig = AppStartConfig
{ ascTempFolder = tf
, ascSetuid = muid
, ascProcessTracker = processTracker
, ascHostManager = hostman
, ascPortPool = portpool
, ascPlugins = plugins
}
appMan <- AppMan.initialize (AppMan.RunKIO runKIO') appStartConfig
let addApp bundle = do
etime <- modificationTime <$> getFileStatus (F.encodeString bundle)
AppMan.perform
appMan
(AppMan.AINamed $ getAppname bundle)
(AppMan.Reload $ AppMan.AIBundle bundle etime)
2013-07-25 18:35:16 +04:00
terminateApp appname = AppMan.perform appMan (AppMan.AINamed appname) AppMan.Terminate
2012-05-11 12:29:25 +04:00
2013-07-14 16:30:55 +04:00
let incoming = kconfigDir </> "incoming"
2012-05-15 12:38:54 +04:00
isKeter fp = hasExtension fp "keter"
2012-05-17 12:47:54 +04:00
createTree incoming
2013-05-29 10:43:52 +04:00
bundles0 <- fmap (filter isKeter) $ listDirectory incoming
2013-07-25 18:35:16 +04:00
mapM_ addApp bundles0
2012-05-11 12:29:25 +04:00
2013-07-25 18:35:16 +04:00
unless (V.null kconfigBuiltinStanzas) $ AppMan.perform
appMan
AppMan.AIBuiltin
(AppMan.Reload $ AppMan.AIData $ BundleConfig kconfigBuiltinStanzas mempty)
2013-07-10 14:26:37 +04:00
2013-06-03 14:34:47 +04:00
-- File system watching
wm <- FSN.startManager
FSN.watchDir wm incoming (P.const True) $ \e ->
let e' =
case e of
FSN.Removed fp _ -> Left fp
FSN.Added fp _ -> Right fp
FSN.Modified fp _ -> Right fp
in case e' of
Left fp -> when (isKeter fp) $ terminateApp $ getAppname fp
2013-07-25 18:35:16 +04:00
Right fp -> when (isKeter fp) $ addApp $ incoming </> fp
2012-05-11 12:29:25 +04:00
2013-05-29 10:43:52 +04:00
-- Install HUP handler for cases when inotify cannot be used.
_ <- flip (installHandler sigHUP) Nothing $ Catch $ do
2013-07-26 11:09:11 +04:00
bundles <- fmap (filter isKeter) $ F.listDirectory incoming
newMap <- fmap Map.fromList $ forM bundles $ \bundle -> do
time <- modificationTime <$> getFileStatus (F.encodeString bundle)
return (getAppname bundle, (bundle, time))
AppMan.reloadAppList appMan newMap
2013-05-29 10:43:52 +04:00
2012-05-15 12:38:54 +04:00
runKIO' $ forever $ threadDelay $ 60 * 1000 * 1000
2012-05-11 12:29:25 +04:00
where
2012-05-15 12:38:54 +04:00
getAppname = either id id . toText . basename
getAppname' = getAppname . F.decodeString
2012-05-17 08:15:25 +04:00
runThrow f = runKIO P.print f >>= either throwIO return
2013-07-10 14:26:37 +04:00
loadPlugins :: [KIO (Either SomeException Plugin)]
-> KIO (Either SomeException Plugins)
loadPlugins =
loop id
where
loop front [] = return $ Right $ front []
loop front (x:xs) = do
eres <- x
case eres of
Left e -> return $ Left e
Right p -> loop (front . (p:)) xs