-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMain.hs
More file actions
241 lines (209 loc) · 7.48 KB
/
Main.hs
File metadata and controls
241 lines (209 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
module Main where
import System.IO
import System.Environment
import System.Process.Internals
import System.Directory
import Control.Monad
import Control.Exception
import Text.Read
import Text.Regex
import Data.List
import qualified System.Process as P
import qualified Data.ByteString.Lazy as B
import qualified System.Posix.Types as T
import qualified Data.Map as Map
-- type declarations ----------------------
type Options = Map.Map String String
type Config = Map.Map String String
empty :: Options
empty = Map.fromList []
defconf :: Config
defconf = Map.fromList []
-------------------------------------------
main = do
putStrLn "spawn v0.9.10"
args <- getArgs
case args of
["--help"] -> showHelp
(cmd:rest) -> runCommand cmd $ consumeOpts rest
otherwise -> cError
-- function definitions -------------------
runCommand :: String -> Options -> IO ()
runCommand "init" opts = cInit opts
runCommand "start" opts = checkConfig opts cStart
runCommand "stop" opts = checkConfig opts cStop
runCommand "reload" opts = checkConfig opts cReload
runCommand "status" opts = checkConfig opts cStatus
runCommand "clean" opts = checkConfig opts cClean
runCommand c o = cError
consumeOpts' :: Options -> [String] -> Options
consumeOpts' opt [] = opt
consumeOpts' opt [_] = opt
consumeOpts' opt ("-f":v:os) = consumeOpts' (Map.insert "proc" v opt) os
consumeOpts' opt ("-p":v:os) = consumeOpts' (Map.insert "port" v opt) os
consumeOpts' opt ("-d":v:os) = consumeOpts' (Map.insert "dir" v opt) os
consumeOpts' opt ("--onstart":v:os) = consumeOpts' (Map.insert "start" v opt) os
consumeOpts' opt ("--onstop":v:os) = consumeOpts' (Map.insert "stop" v opt) os
consumeOpts' opt (_:_:os) = consumeOpts' opt os
consumeOpts :: [String] -> Options
consumeOpts = consumeOpts' empty
checkConfig :: Options -> (Options -> IO ()) -> IO ()
checkConfig opts continue = do
let dir = getDir $ opts # "dir"
let confPath = (dir ++ "/" ++ ".spawn")
confExists <- doesFileExist confPath
if confExists
then continue opts
else putStrLn $ "not a spawn config" ++ "\nrun spawn init first"
readConfig :: String -> IO (Config)
readConfig dir = do
contents <- fmap lines $ readFile (dir ++ "/" ++ ".spawn")
let keys = ["proc","port","start","stop"]
return (Map.fromList $ zip keys contents)
(#) :: Map.Map String String -> String -> Maybe String
k # m = if value=="$invalid" then Nothing
else Just value
where value = extract $ Map.lookup m k
extract :: Maybe String -> String
extract Nothing = "$invalid"
extract (Just x) = x
-- |fallback to current directory if no -d flag passed
getDir :: Maybe String -> String
getDir Nothing = "."
getDir (Just x) = x
-- |read process id from spawn-fcgi output
getPid :: String -> Maybe String
getPid output = fmap (!! 0) $ matchRegex (mkRegex "spawn-fcgi: child spawned successfully: PID: ([0-9]+)") output
-- |retrieve process id of application
processState :: String -> IO (Maybe String)
processState dir = do
let pidPath = dir ++ "/.pid"
pidfExists <- doesFileExist pidPath
if pidfExists
then do
oPid <- readFile pidPath
procExists <- doesDirectoryExist ("/proc/" ++ oPid)
if procExists
then return (Just oPid)
else do
removeFile pidPath
return Nothing
else return Nothing
-- |initialize a spawn template
cInit :: Options -> IO ()
cInit opts = do
putStr "creating spawn config: "
let proc = opts # "proc"
let port = opts # "port"
if proc == Nothing || port == Nothing
then putStrLn $ "invalid options\n" ++ "usage: spawn init -p <port> -f <process> [--onstart <command>] [--onstop <command>]"
else do
let start = opts # "start"
let stop = opts # "stop"
let config = extract proc ++ "\n" ++ extract port ++ "\n" ++ extract start ++ "\n" ++ extract stop
writeFile ".spawn" config
putStrLn "ok."
cStart :: Options -> IO ()
cStart opts = do
putStr "spawning process: "
let dir = getDir $ opts # "dir"
config <- readConfig dir
let exec = "./" ++ (extract $ config # "proc")
let start = config # "start"
mPid <- processState dir
case mPid of
Just _ -> putStrLn "already running!"
Nothing -> do
ph <- if dir /= "."
then P.readCreateProcess (P.shell $ intercalate " " ["spawn-fcgi -d" , dir, " -f", exec, "-p", (extract $ config # "port")]) ""
else P.readCreateProcess (P.shell $ intercalate " " ["spawn-fcgi -f", exec, "-p", (extract $ config # "port")]) ""
let mPid = getPid ph
case mPid of
Nothing -> putStrLn "error"
Just p -> do
let pidPath = dir ++ "/.pid"
writeFile pidPath p
putStrLn p
if start /= Nothing
then do
P.spawnCommand $ extract start
return ()
else return ()
cStop :: Options -> IO ()
cStop opts = do
let dir = getDir $ opts # "dir"
config <- readConfig dir
putStr "terminating process: "
mPid <- processState dir
case mPid of
Just pid -> do
handle <- mkProcessHandle (T.CPid $ read pid) False
P.terminateProcess handle
P.callCommand $ "sleep 0.5"
let stop = config # "stop"
putStrLn "ok."
if stop /= Nothing
then do
P.spawnCommand $ extract stop
else return ()
Nothing -> putStrLn "cannot attach to process."
let pidPath = dir ++ "/.pid"
pidExists <- doesFileExist pidPath
if pidExists
then removeFile pidPath
else return ()
cReload :: Options -> IO ()
cReload opts = do
(cStop opts)
P.callCommand $ "sleep 2"
(cStart opts)
cStatus :: Options -> IO ()
cStatus opts = do
let dir = getDir $ opts # "dir"
config <- readConfig dir
let onstart = config # "start"
let onstop = config # "stop"
let exec = (extract $ config # "proc")
let port = (extract $ config # "port")
mPid <- processState dir
putStrLn "configuration:"
putStrLn $ "status: " ++ case mPid of Just p -> "running (" ++ p ++ ")"
Nothing -> "stopped"
putStrLn $ "process: " ++ exec
putStrLn $ "port: " ++ port
if onstart/=Nothing
then putStrLn $ "onstart: " ++ extract onstart
else return ()
if onstop/=Nothing
then putStrLn $ "onstop: " ++ extract onstop
else return ()
cClean :: Options -> IO ()
cClean opts = do
spconf <- doesFileExist ".spawn"
if spconf
then do
cStop opts
putStr "removing configuration: "
removeFile ".spawn"
putStrLn "ok."
else putStrLn "error: spawn template not found."
cError :: IO ()
cError = do
putStrLn "unknown command!"
putStrLn "usage: spawn [init|start|stop|reload|status|clean] [options]"
showHelp :: IO ()
showHelp = do
putStrLn "usage: spawn [init|start|stop|reload|status|clean] [options]"
putStrLn $ "init: initialize new spawn template\n" ++
" -f: (required) fcgi application file\n" ++
" -p: (required) port number\n" ++
" --onstart: (optional) command to run after application has started\n" ++
" --onstop: (optional) command to run after application has terminated"
putStrLn $ "start: start the spawn process\n" ++
" -d: (optional) path to spawn directory"
putStrLn $ "stop: terminate the spawn process\n" ++
" -d: (optional) path to spawn directory"
putStrLn $ "reload: restart the spawn process\n" ++
" -d: (optional) path to spawn directory"
putStrLn $ "status: print process status and configuration"
putStrLn $ "clean: stop the process and remove configuration file"