1 module app; 2 3 import std.stdio; 4 5 struct CmdOptions 6 { 7 string stdoutFile; 8 string stderrFile; 9 string scriptOnRestart; 10 int max=-1; 11 int minUptime=1000; 12 13 public void Parse(ref string[] _args) 14 { 15 import std.getopt:getopt; 16 17 getopt(_args, 18 "log|l", &stdoutFile, 19 "err|e", &stderrFile, 20 "script", &scriptOnRestart, 21 "min-uptime", &minUptime, 22 "max|m", &max); 23 } 24 25 public void print() 26 { 27 if(useStdOutFile) 28 log("stdout file: %s", stdoutFile); 29 if(useStdErrFile) 30 log("stderr file: %s", stderrFile); 31 if(scriptOnRestart.length > 0) 32 log("restart script: %s", scriptOnRestart); 33 if(max > 0) 34 log("max runs: %s", max); 35 } 36 37 @property bool useStdOutFile() const { return stdoutFile.length > 0; } 38 @property bool useStdErrFile() const { return stderrFile.length > 0; } 39 } 40 41 void log(T...)(string _format, T params) 42 { 43 if(_format.length == 0) 44 { 45 writeln(""); 46 } 47 else 48 { 49 import std.string; 50 import std.datetime; 51 52 writefln("-- %s -- " ~ _format, Clock.currTime, params); 53 } 54 } 55 56 enum helpMessage = 57 `forever-d [options] [program] <Arguments...> 58 59 options: 60 -m -max Max runs of [program]. default is 0 (unlimited) 61 -l -log File to print [program] std-out to. By default it's printed to stdout of forever-d 62 -e -err File to print [program] std-err to. By default it's printed to stdout of forever-d 63 -script Script run on process restart. Use [script-env] ENV variables in there. 64 -min-uptime Minimum time in milliseconds program needs to run so it will restart again. (Defaults to 1000) 65 66 script-env: 67 FD_EXITCODE exit code of [program] 68 FD_RESTARTS number of restarts 69 FD_CMDLINE the actual cmd line used for [program]`; 70 71 void main(string[] _args) 72 { 73 import std.process; 74 import core.thread:Thread; 75 import std.array:join; 76 import std.conv:to; 77 import std.datetime:StopWatch; 78 79 log("Starting forever-d"); 80 81 if(_args.length < 2 || _args[1] == "--help") 82 { 83 writeln(helpMessage); 84 return; 85 } 86 87 CmdOptions options; 88 options.Parse(_args); 89 90 options.print(); 91 92 auto cmdline = _args[1..$].join(" "); 93 94 string[string] envVars; 95 int restartCount; 96 StopWatch uptime; 97 bool canRestart = true; 98 99 envVars["FD_CMDLINE"] = cmdline; 100 101 File outStream = stdout; 102 File errStream = stderr; 103 104 if(options.useStdOutFile) 105 outStream = File(options.stdoutFile, "a+"); 106 107 if(options.useStdErrFile) 108 errStream = File(options.stderrFile, "a+"); 109 110 while((options.max == -1 || (options.max-- > 0)) && canRestart) 111 { 112 log("Starting: '%s'", cmdline); 113 uptime.start(); 114 115 auto pid = spawnProcess(cmdline, std.stdio.stdin, outStream, errStream, 116 null, Config.retainStdout); 117 118 auto exitCode = wait(pid); 119 uptime.stop(); 120 if(uptime.peek().msecs < options.minUptime) 121 canRestart = false; 122 uptime.reset(); 123 log(""); 124 log("Process Ended. Exitcode: %s", exitCode); 125 126 restartCount++; 127 128 if(options.scriptOnRestart.length > 0) 129 { 130 envVars["FD_EXITCODE"] = to!string(exitCode); 131 envVars["FD_RESTARTS"] = to!string(restartCount); 132 133 spawnShell(options.scriptOnRestart, envVars); 134 } 135 } 136 }