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