mirror of
https://github.com/snoyberg/keter.git
synced 2024-12-15 01:23:09 +03:00
File watching: it all works
This commit is contained in:
parent
1686e0938b
commit
1554bb9196
@ -15,7 +15,6 @@ import qualified Codec.Archive.Tar as Tar
|
|||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import Codec.Compression.GZip (decompress)
|
import Codec.Compression.GZip (decompress)
|
||||||
import qualified System.FilePath as F
|
import qualified System.FilePath as F
|
||||||
import Data.Text (pack)
|
|
||||||
import Data.Yaml
|
import Data.Yaml
|
||||||
import Control.Applicative ((<$>), (<*>))
|
import Control.Applicative ((<$>), (<*>))
|
||||||
import System.PosixCompat.Files
|
import System.PosixCompat.Files
|
||||||
@ -71,14 +70,14 @@ unpackBundle tf bundle appname = tryM $ do
|
|||||||
|
|
||||||
start :: TempFolder
|
start :: TempFolder
|
||||||
-> Nginx
|
-> Nginx
|
||||||
|
-> Appname
|
||||||
-> FilePath -- ^ app bundle
|
-> FilePath -- ^ app bundle
|
||||||
-> IO () -- ^ action to perform to remove this App from list of actives
|
-> IO () -- ^ action to perform to remove this App from list of actives
|
||||||
-> IO (App, IO ())
|
-> IO (App, IO ())
|
||||||
start tf nginx bundle removeFromList = do
|
start tf nginx appname bundle removeFromList = do
|
||||||
chan <- C.newChan
|
chan <- C.newChan
|
||||||
return (App $ C.writeChan chan, rest chan)
|
return (App $ C.writeChan chan, rest chan)
|
||||||
where
|
where
|
||||||
appname = pack $ F.takeBaseName bundle
|
|
||||||
void f = f >> return ()
|
void f = f >> return ()
|
||||||
|
|
||||||
runApp port dir config = do
|
runApp port dir config = do
|
||||||
@ -113,6 +112,7 @@ start tf nginx bundle removeFromList = do
|
|||||||
Terminate -> do
|
Terminate -> do
|
||||||
removeFromList
|
removeFromList
|
||||||
removeEntry nginx $ configHost configOld
|
removeEntry nginx $ configHost configOld
|
||||||
|
putStrLn $ "Received terminate signal for app: " ++ show appname
|
||||||
terminateOld
|
terminateOld
|
||||||
Reload -> do
|
Reload -> do
|
||||||
mres <- unpackBundle tf bundle appname
|
mres <- unpackBundle tf bundle appname
|
||||||
@ -129,6 +129,7 @@ start tf nginx bundle removeFromList = do
|
|||||||
addEntry nginx (configHost config) $ AppEntry port
|
addEntry nginx (configHost config) $ AppEntry port
|
||||||
when (configHost config /= configHost configOld) $
|
when (configHost config /= configHost configOld) $
|
||||||
removeEntry nginx $ configHost configOld
|
removeEntry nginx $ configHost configOld
|
||||||
|
putStrLn $ "Finished reloading: " ++ show appname
|
||||||
terminateOld
|
terminateOld
|
||||||
loop chan dir process port config
|
loop chan dir process port config
|
||||||
else do
|
else do
|
||||||
|
66
Keter/Main.hs
Normal file
66
Keter/Main.hs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
module Keter.Main
|
||||||
|
( keter
|
||||||
|
) where
|
||||||
|
|
||||||
|
import qualified Keter.Nginx as Nginx
|
||||||
|
import qualified Keter.TempFolder as TempFolder
|
||||||
|
import qualified Keter.App as App
|
||||||
|
|
||||||
|
import Data.Default (def)
|
||||||
|
import System.FilePath ((</>), takeBaseName)
|
||||||
|
import qualified Control.Concurrent.MVar as M
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import Data.Text (pack)
|
||||||
|
import System.Directory (getDirectoryContents)
|
||||||
|
import Control.Concurrent (threadDelay)
|
||||||
|
import qualified System.INotify as I
|
||||||
|
import Control.Monad (forever, when)
|
||||||
|
import Data.List (isSuffixOf)
|
||||||
|
|
||||||
|
keter :: FilePath -- ^ root directory, with incoming, temp, and etc folders
|
||||||
|
-> IO ()
|
||||||
|
keter dir = do
|
||||||
|
nginx <- Nginx.start def
|
||||||
|
tf <- TempFolder.setup $ dir </> "temp"
|
||||||
|
|
||||||
|
mappMap <- M.newMVar Map.empty
|
||||||
|
let removeApp appname = M.modifyMVar_ mappMap $ return . Map.delete appname
|
||||||
|
addApp bundle = do
|
||||||
|
let appname = getAppname bundle
|
||||||
|
rest <- M.modifyMVar mappMap $ \appMap ->
|
||||||
|
case Map.lookup appname appMap of
|
||||||
|
Just app -> do
|
||||||
|
App.reload app
|
||||||
|
return (appMap, return ())
|
||||||
|
Nothing -> do
|
||||||
|
(app, rest) <- App.start tf nginx appname bundle $ removeApp appname
|
||||||
|
let appMap' = Map.insert appname app appMap
|
||||||
|
return (appMap', rest)
|
||||||
|
rest
|
||||||
|
terminateApp appname = do
|
||||||
|
appMap <- M.readMVar mappMap
|
||||||
|
case Map.lookup appname appMap of
|
||||||
|
Nothing -> return ()
|
||||||
|
Just app -> App.terminate app
|
||||||
|
|
||||||
|
let incoming = dir </> "incoming"
|
||||||
|
let hidden ('.':_) = True
|
||||||
|
hidden _ = False
|
||||||
|
isKeter = isSuffixOf ".keter"
|
||||||
|
bundles <- fmap (map (incoming </>) . filter isKeter . filter (not . hidden))
|
||||||
|
$ getDirectoryContents incoming
|
||||||
|
mapM_ addApp bundles
|
||||||
|
|
||||||
|
let events = [I.MoveIn, I.MoveOut, I.Delete, I.CloseWrite]
|
||||||
|
i <- I.initINotify
|
||||||
|
_ <- I.addWatch i events incoming $ \e ->
|
||||||
|
case e of
|
||||||
|
I.Deleted _ fp -> when (isKeter fp) $ terminateApp $ getAppname fp
|
||||||
|
I.MovedOut _ fp _ -> when (isKeter fp) $ terminateApp $ getAppname fp
|
||||||
|
I.Closed _ (Just fp) _ -> when (isKeter fp) $ addApp $ incoming </> fp
|
||||||
|
I.MovedIn _ fp _ -> when (isKeter fp) $ addApp $ incoming </> fp
|
||||||
|
_ -> print e
|
||||||
|
|
||||||
|
forever $ threadDelay $ 60 * 1000 * 1000
|
||||||
|
where
|
||||||
|
getAppname = pack . takeBaseName
|
@ -30,7 +30,7 @@ import Control.Concurrent (forkIO)
|
|||||||
import qualified Control.Monad.Trans.State as S
|
import qualified Control.Monad.Trans.State as S
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import Control.Monad (forever)
|
import Control.Monad (forever, unless)
|
||||||
import qualified Data.ByteString.Lazy as L
|
import qualified Data.ByteString.Lazy as L
|
||||||
import Blaze.ByteString.Builder (copyByteString, toLazyByteString)
|
import Blaze.ByteString.Builder (copyByteString, toLazyByteString)
|
||||||
import Blaze.ByteString.Builder.Char.Utf8 (fromString, fromShow)
|
import Blaze.ByteString.Builder.Char.Utf8 (fromString, fromShow)
|
||||||
@ -39,6 +39,8 @@ import Data.ByteString.Char8 ()
|
|||||||
import System.Directory (renameFile)
|
import System.Directory (renameFile)
|
||||||
import qualified Network
|
import qualified Network
|
||||||
import Control.Exception (SomeException, try)
|
import Control.Exception (SomeException, try)
|
||||||
|
import qualified Data.ByteString as S
|
||||||
|
import System.Exit (ExitCode (ExitSuccess))
|
||||||
|
|
||||||
(<>) :: Monoid m => m -> m -> m
|
(<>) :: Monoid m => m -> m -> m
|
||||||
(<>) = mappend
|
(<>) = mappend
|
||||||
@ -79,15 +81,25 @@ data Settings = Settings
|
|||||||
instance Default Settings where
|
instance Default Settings where
|
||||||
def = Settings
|
def = Settings
|
||||||
{ configFile = "/etc/nginx/sites-enabled/keter"
|
{ configFile = "/etc/nginx/sites-enabled/keter"
|
||||||
, reloadAction = rawSystem "/etc/init.d/nginx" ["reload"] >> return ()
|
, reloadAction = rawSystem' "/etc/init.d/nginx" ["reload"]
|
||||||
, startAction = rawSystem "/etc/init.d/nginx" ["start"] >> return ()
|
, startAction = rawSystem' "/etc/init.d/nginx" ["start"]
|
||||||
, portRange = [4000..4999]
|
, portRange = [4000..4999]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rawSystem' :: FilePath -> [String] -> IO ()
|
||||||
|
rawSystem' fp args = do
|
||||||
|
ec <- rawSystem fp args
|
||||||
|
unless (ec == ExitSuccess) $ error $ "Received exit failure when running: " ++ show (fp:args)
|
||||||
|
|
||||||
-- | Start running a separate thread which will accept commands and modify
|
-- | Start running a separate thread which will accept commands and modify
|
||||||
-- Nginx's behavior accordingly.
|
-- Nginx's behavior accordingly.
|
||||||
start :: Settings -> IO Nginx
|
start :: Settings -> IO Nginx
|
||||||
start Settings{..} = do
|
start Settings{..} = do
|
||||||
|
-- Start off by ensuring we can read and write the config file and reload
|
||||||
|
config0 <- S.readFile configFile
|
||||||
|
S.writeFile configFile config0
|
||||||
|
reloadAction
|
||||||
|
|
||||||
chan <- C.newChan
|
chan <- C.newChan
|
||||||
_ <- forkIO $ flip S.evalStateT (NState portRange [] Map.empty) $ forever $ do
|
_ <- forkIO $ flip S.evalStateT (NState portRange [] Map.empty) $ forever $ do
|
||||||
command <- lift $ C.readChan chan
|
command <- lift $ C.readChan chan
|
||||||
|
@ -4,10 +4,15 @@ import qualified Data.ByteString.Lazy.Char8 as L8
|
|||||||
import Network.Wai
|
import Network.Wai
|
||||||
import Network.Wai.Handler.Warp
|
import Network.Wai.Handler.Warp
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
|
import System.Directory
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
|
fp <- canonicalizePath "."
|
||||||
[msg] <- getArgs
|
[msg] <- getArgs
|
||||||
portS <- getEnv "PORT"
|
portS <- getEnv "PORT"
|
||||||
let port = read portS
|
let port = read portS
|
||||||
run port $ const $ return $ responseLBS status200 [("content-type", "text/plain")] $ L8.pack msg
|
run port $ const $ return $ responseLBS status200 [("content-type", "text/plain")] $ L8.pack $ unlines
|
||||||
|
[ "Message: " ++ msg
|
||||||
|
, "Path: " ++ fp
|
||||||
|
]
|
||||||
|
@ -28,17 +28,20 @@ Library
|
|||||||
, blaze-builder >= 0.3 && < 0.4
|
, blaze-builder >= 0.3 && < 0.4
|
||||||
, yaml >= 0.7 && < 0.8
|
, yaml >= 0.7 && < 0.8
|
||||||
, unix-compat >= 0.3 && < 0.4
|
, unix-compat >= 0.3 && < 0.4
|
||||||
|
, hinotify >= 0.3 && < 0.4
|
||||||
Exposed-Modules: Keter.Nginx
|
Exposed-Modules: Keter.Nginx
|
||||||
Keter.Process
|
Keter.Process
|
||||||
Keter.Postgres
|
Keter.Postgres
|
||||||
Keter.TempFolder
|
Keter.TempFolder
|
||||||
Keter.App
|
Keter.App
|
||||||
|
Keter.Main
|
||||||
ghc-options: -Wall -Werror
|
ghc-options: -Wall -Werror
|
||||||
|
|
||||||
Executable keter
|
Executable keter
|
||||||
Main-is: ../keter.hs
|
Main-is: ../keter.hs
|
||||||
hs-source-dirs: dist
|
hs-source-dirs: dist
|
||||||
Build-depends: base, keter
|
Build-depends: base, keter
|
||||||
|
ghc-options: -threaded -Wall -Werror
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
type: git
|
type: git
|
||||||
|
11
keter.hs
11
keter.hs
@ -1,4 +1,11 @@
|
|||||||
import Keter.Nginx ()
|
import System.Environment (getArgs, getProgName)
|
||||||
|
import Keter.Main (keter)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = putStrLn "Nothing here yet"
|
main = do
|
||||||
|
args <- getArgs
|
||||||
|
case args of
|
||||||
|
[dir] -> keter dir
|
||||||
|
_ -> do
|
||||||
|
pn <- getProgName
|
||||||
|
error $ "Usage: " ++ pn ++ " <root folder>"
|
||||||
|
Loading…
Reference in New Issue
Block a user