subservice.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. package subservice
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "net/url"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "runtime"
  13. "sort"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "imuslab.com/arozos/mod/info/logger"
  18. modules "imuslab.com/arozos/mod/modules"
  19. "imuslab.com/arozos/mod/network/reverseproxy"
  20. "imuslab.com/arozos/mod/network/websocketproxy"
  21. user "imuslab.com/arozos/mod/user"
  22. )
  23. /*
  24. ArOZ Online System - Dynamic Subsystem loading services
  25. author: tobychui
  26. This module load in ArOZ Online Subservice using authorized reverse proxy channel.
  27. Please see the demo subservice module for more information on implementing a subservice module.
  28. */
  29. type SubService struct {
  30. Port int //Port that this subservice use
  31. ServiceDir string //The directory where the service is located
  32. Path string //Path that this subservice is located
  33. RpEndpoint string //Reverse Proxy Endpoint
  34. ProxyHandler *reverseproxy.ReverseProxy //Reverse Proxy Object
  35. Info modules.ModuleInfo //Module information for this subservice
  36. Process *exec.Cmd //The CMD runtime object of the process
  37. }
  38. type SubServiceRouter struct {
  39. ReservePaths []string
  40. RunningSubService []SubService
  41. BasePort int
  42. listenPort int
  43. userHandler *user.UserHandler
  44. moduleHandler *modules.ModuleHandler
  45. logger *logger.Logger
  46. }
  47. func NewSubServiceRouter(ReservePaths []string, basePort int, userHandler *user.UserHandler, moduleHandler *modules.ModuleHandler, parentPort int) *SubServiceRouter {
  48. //Create a service logger
  49. thisLogger, _ := logger.NewLogger("system", "system/logs/subservice", true)
  50. return &SubServiceRouter{
  51. ReservePaths: ReservePaths,
  52. RunningSubService: []SubService{},
  53. BasePort: basePort,
  54. listenPort: parentPort,
  55. userHandler: userHandler,
  56. moduleHandler: moduleHandler,
  57. logger: thisLogger,
  58. }
  59. }
  60. // Load and start all the subservices inside this rootpath
  61. func (sr *SubServiceRouter) LoadSubservicesFromRootPath(rootpath string) {
  62. scanningPath := filepath.ToSlash(filepath.Clean(rootpath)) + "/*"
  63. subservices, _ := filepath.Glob(scanningPath)
  64. for _, servicePath := range subservices {
  65. if !fileExists(servicePath + "/.disabled") {
  66. //Only enable module with no suspended config file
  67. err := sr.Launch(servicePath, true)
  68. if err != nil {
  69. sr.logger.PrintAndLog("Subservice", "Failed to start subservice: "+filepath.Base(servicePath)+" "+err.Error(), err)
  70. //log.Println(err)
  71. }
  72. }
  73. }
  74. }
  75. func (sr *SubServiceRouter) Launch(servicePath string, startupMode bool) error {
  76. //Get the executable name from its path
  77. servicePath = filepath.ToSlash(servicePath)
  78. binaryname := filepath.Base(servicePath)
  79. serviceRoot := filepath.Base(servicePath)
  80. binaryExecPath := filepath.ToSlash(binaryname)
  81. if runtime.GOOS == "windows" {
  82. binaryExecPath = binaryExecPath + ".exe"
  83. } else {
  84. binaryExecPath = binaryExecPath + "_" + runtime.GOOS + "_" + runtime.GOARCH
  85. }
  86. //Check if startscript exists. If no, try to launch the binaries
  87. if fileExists(servicePath + "/.startscript") {
  88. //Launch from start.bat or start.sh
  89. if !(fileExists(servicePath+"/start.sh") || fileExists(servicePath+"/start.bat")) {
  90. //log.Println("Failed to load subservice: " + serviceRoot + ", .startscript flag is TRUE but no start script found")
  91. return errors.New(".startscript flag is TRUE but no start script found")
  92. }
  93. startScriptName := "start.sh"
  94. if runtime.GOOS == "windows" {
  95. startScriptName = "start.bat"
  96. }
  97. binaryExecPath = startScriptName
  98. } else {
  99. //No startscript defined. Start from binary files if exists
  100. if runtime.GOOS == "windows" && !fileExists(servicePath+"/"+binaryExecPath) {
  101. if startupMode {
  102. //log.Println("Failed to load subservice: " + serviceRoot)
  103. return errors.New("Subservice executable not exists " + servicePath + "/" + binaryExecPath + ". Skipping this service")
  104. } else {
  105. return errors.New("Subservice executable " + servicePath + "/" + binaryExecPath + ". Skipping this service")
  106. }
  107. } else if runtime.GOOS == "linux" {
  108. //Check if service installed using which
  109. cmd := exec.Command("which", serviceRoot)
  110. searchResults, _ := cmd.CombinedOutput()
  111. if len(strings.TrimSpace(string(searchResults))) == 0 {
  112. //This is not installed. Check if it exists as a binary (aka ./myservice)
  113. if !fileExists(servicePath + "/" + binaryExecPath) {
  114. if startupMode {
  115. //log.Println("Package not installed. " + serviceRoot)
  116. return errors.New("Package not installed")
  117. } else {
  118. return errors.New("Package not installed")
  119. }
  120. }
  121. }
  122. } else if runtime.GOOS == "darwin" {
  123. //Skip the whereis approach that linux use
  124. if !fileExists(servicePath + "/" + binaryExecPath) {
  125. return errors.New("Subservice executable not exists " + servicePath + "/" + binaryExecPath + ". Skipping this service")
  126. }
  127. }
  128. }
  129. //Check if the suspend file exists. If yes, clear it
  130. if fileExists(servicePath + "/.disabled") {
  131. os.Remove(servicePath + "/.disabled")
  132. }
  133. //Check if there are config files that replace the -info tag. If yes, use it instead.
  134. out := []byte{}
  135. if fileExists(servicePath + "/moduleInfo.json") {
  136. launchConfig, err := ioutil.ReadFile(servicePath + "/moduleInfo.json")
  137. if err != nil {
  138. if startupMode {
  139. log.Fatal("Failed to read moduleInfo.json: "+binaryname, err)
  140. } else {
  141. return errors.New("Failed to read moduleInfo.json: " + binaryname)
  142. }
  143. }
  144. out = launchConfig
  145. } else {
  146. infocmd := exec.Command(servicePath+"/"+binaryExecPath, "-info")
  147. launchConfig, err := infocmd.CombinedOutput()
  148. if err != nil {
  149. sr.logger.PrintAndLog("Subservice", "Missing module startup info for "+servicePath, errors.New("Startup flag -info return no JSON string and moduleInfo.json does not exists for "+servicePath))
  150. if startupMode {
  151. log.Fatal("Unable to start service: "+binaryname, err)
  152. } else {
  153. return errors.New("Unable to start service: " + binaryname)
  154. }
  155. }
  156. out = launchConfig
  157. }
  158. //Clean the module info and append it into the module list
  159. serviceLaunchInfo := strings.TrimSpace(string(out))
  160. thisModuleInfo := modules.ModuleInfo{}
  161. err := json.Unmarshal([]byte(serviceLaunchInfo), &thisModuleInfo)
  162. if err != nil {
  163. if startupMode {
  164. log.Fatal("Failed to load subservice: "+serviceRoot+"\n", err.Error())
  165. } else {
  166. return errors.New("Failed to load subservice: " + serviceRoot)
  167. }
  168. }
  169. var thisSubService SubService
  170. if fileExists(servicePath + "/.noproxy") {
  171. //Adaptive mode. This is designed for modules that do not designed with ArOZ Online in mind.
  172. //Ignore proxy setup and startup the application
  173. absolutePath, _ := filepath.Abs(servicePath + "/" + binaryExecPath)
  174. if fileExists(servicePath + "/.startscript") {
  175. initPath := servicePath + "/start.sh"
  176. if runtime.GOOS == "windows" {
  177. initPath = servicePath + "/start.bat"
  178. }
  179. if !fileExists(initPath) {
  180. if startupMode {
  181. log.Fatal("start.sh not found. Unable to startup service " + serviceRoot)
  182. } else {
  183. return errors.New("start.sh not found. Unable to startup service " + serviceRoot)
  184. }
  185. }
  186. absolutePath, _ = filepath.Abs(initPath)
  187. }
  188. cmd := exec.Command(absolutePath)
  189. cmd.Stdout = os.Stdout
  190. cmd.Stderr = os.Stderr
  191. cmd.Dir = filepath.ToSlash(servicePath + "/")
  192. //Spawn a new go routine to run this subservice
  193. go func(cmdObject *exec.Cmd) {
  194. if err := cmd.Start(); err != nil {
  195. panic(err)
  196. }
  197. }(cmd)
  198. //Create the servie object
  199. thisSubService = SubService{
  200. Path: binaryExecPath,
  201. Info: thisModuleInfo,
  202. ServiceDir: serviceRoot,
  203. Process: cmd,
  204. }
  205. sr.logger.PrintAndLog("Subservice", "Starting service "+serviceRoot+" in compatibility mode", nil)
  206. } else {
  207. //Create a proxy for this service
  208. //Get proxy endpoint from startDir dir
  209. rProxyEndpoint := filepath.Dir(thisModuleInfo.StartDir)
  210. //Check if this path is reversed
  211. if stringInSlice(rProxyEndpoint, sr.ReservePaths) || rProxyEndpoint == "" {
  212. if startupMode {
  213. log.Fatal(serviceRoot + " service try to request system reserve path as Reverse Proxy endpoint.")
  214. } else {
  215. return errors.New(serviceRoot + " service try to request system reserve path as Reverse Proxy endpoint.")
  216. }
  217. }
  218. //Assign a port for this subservice
  219. thisServicePort := sr.GetNextUsablePort()
  220. //Run the subservice with the given port
  221. absolutePath, _ := filepath.Abs(servicePath + "/" + binaryExecPath)
  222. if fileExists(servicePath + "/.startscript") {
  223. initPath := servicePath + "/start.sh"
  224. if runtime.GOOS == "windows" {
  225. initPath = servicePath + "/start.bat"
  226. }
  227. if !fileExists(initPath) {
  228. if startupMode {
  229. log.Fatal("start.sh not found. Unable to startup service " + serviceRoot)
  230. } else {
  231. return errors.New(serviceRoot + "start.sh not found. Unable to startup service " + serviceRoot)
  232. }
  233. }
  234. absolutePath, _ = filepath.Abs(initPath)
  235. }
  236. servicePort := ":" + intToString(thisServicePort)
  237. if fileExists(filepath.Join(servicePath, "/.intport")) {
  238. servicePort = intToString(thisServicePort)
  239. }
  240. cmd := exec.Command(absolutePath, "-port", servicePort, "-rpt", "http://localhost:"+intToString(sr.listenPort)+"/api/ajgi/interface")
  241. cmd.Stdout = os.Stdout
  242. cmd.Stderr = os.Stderr
  243. cmd.Dir = filepath.ToSlash(servicePath + "/")
  244. //log.Println(cmd.Dir,binaryExecPath)
  245. //Spawn a new go routine to run this subservice
  246. go func(cmd *exec.Cmd) {
  247. if err := cmd.Start(); err != nil {
  248. panic(err)
  249. }
  250. }(cmd)
  251. //Create a subservice object for this subservice
  252. thisSubService = SubService{
  253. Port: thisServicePort,
  254. Path: binaryExecPath,
  255. ServiceDir: serviceRoot,
  256. RpEndpoint: rProxyEndpoint,
  257. Info: thisModuleInfo,
  258. Process: cmd,
  259. }
  260. sr.logger.PrintAndLog("Subservice", "Subservice Registered: "+thisModuleInfo.Name, nil)
  261. //Create a new proxy object
  262. path, _ := url.Parse("http://localhost:" + intToString(thisServicePort))
  263. proxy := reverseproxy.NewReverseProxy(path)
  264. thisSubService.ProxyHandler = proxy
  265. }
  266. //Append this subservice into the list
  267. sr.RunningSubService = append(sr.RunningSubService, thisSubService)
  268. //Append this module into the loaded module list
  269. sr.moduleHandler.LoadedModule = append(sr.moduleHandler.LoadedModule, &thisModuleInfo)
  270. return nil
  271. }
  272. func (sr *SubServiceRouter) HandleListing(w http.ResponseWriter, r *http.Request) {
  273. //List all subservice running in the background
  274. type visableInfo struct {
  275. Port int
  276. ServiceDir string
  277. Path string
  278. RpEndpoint string
  279. ProcessID int
  280. Info modules.ModuleInfo
  281. }
  282. type disabledServiceInfo struct {
  283. ServiceDir string
  284. Path string
  285. }
  286. enabled := []visableInfo{}
  287. disabled := []disabledServiceInfo{}
  288. for _, thisSubservice := range sr.RunningSubService {
  289. enabled = append(enabled, visableInfo{
  290. Port: thisSubservice.Port,
  291. Path: thisSubservice.Path,
  292. ServiceDir: thisSubservice.ServiceDir,
  293. RpEndpoint: thisSubservice.RpEndpoint,
  294. ProcessID: thisSubservice.Process.Process.Pid,
  295. Info: thisSubservice.Info,
  296. })
  297. }
  298. disabledModules, _ := filepath.Glob("subservice/*/.disabled")
  299. for _, modFile := range disabledModules {
  300. thisdsi := new(disabledServiceInfo)
  301. thisdsi.ServiceDir = filepath.Base(filepath.Dir(modFile))
  302. thisdsi.Path = filepath.Base(filepath.Dir(modFile))
  303. if runtime.GOOS == "windows" {
  304. thisdsi.Path = thisdsi.Path + ".exe"
  305. }
  306. disabled = append(disabled, *thisdsi)
  307. }
  308. jsonString, err := json.Marshal(struct {
  309. Enabled []visableInfo
  310. Disabled []disabledServiceInfo
  311. }{
  312. Enabled: enabled,
  313. Disabled: disabled,
  314. })
  315. if err != nil {
  316. sr.logger.PrintAndLog("Subservice", "Unable to list subservice folder", err)
  317. }
  318. sendJSONResponse(w, string(jsonString))
  319. }
  320. // Kill the subservice that is currently running
  321. func (sr *SubServiceRouter) HandleKillSubService(w http.ResponseWriter, r *http.Request) {
  322. userinfo, _ := sr.userHandler.GetUserInfoFromRequest(w, r)
  323. //Require admin permission
  324. if !userinfo.IsAdmin() {
  325. sendErrorResponse(w, "Permission denied")
  326. return
  327. }
  328. //OK. Get paramters
  329. serviceDir, _ := mv(r, "serviceDir", true)
  330. //moduleName, _ := mv(r, "moduleName", true)
  331. err := sr.KillSubService(serviceDir)
  332. if err != nil {
  333. sendErrorResponse(w, err.Error())
  334. } else {
  335. sendOK(w)
  336. }
  337. }
  338. func (sr *SubServiceRouter) HandleStartSubService(w http.ResponseWriter, r *http.Request) {
  339. userinfo, _ := sr.userHandler.GetUserInfoFromRequest(w, r)
  340. //Require admin permission
  341. if !userinfo.IsAdmin() {
  342. sendErrorResponse(w, "Permission denied")
  343. return
  344. }
  345. //OK. Get which dir to start
  346. serviceDir, _ := mv(r, "serviceDir", true)
  347. err := sr.StartSubService(serviceDir)
  348. if err != nil {
  349. sendErrorResponse(w, err.Error())
  350. } else {
  351. sendOK(w)
  352. }
  353. }
  354. // Check if the user has permission to access such proxy module
  355. func (sr *SubServiceRouter) CheckUserPermissionOnSubservice(ss *SubService, u *user.User) bool {
  356. moduleName := ss.Info.Name
  357. return u.GetModuleAccessPermission(moduleName)
  358. }
  359. // Check if the target is reverse proxy. If yes, return the proxy handler and the rewritten url in string
  360. func (sr *SubServiceRouter) CheckIfReverseProxyPath(r *http.Request) (bool, *reverseproxy.ReverseProxy, string, *SubService) {
  361. requestURL := r.URL.Path
  362. for _, subservice := range sr.RunningSubService {
  363. thisServiceProxyEP := subservice.RpEndpoint
  364. if thisServiceProxyEP != "" {
  365. if len(requestURL) > len(thisServiceProxyEP)+1 && requestURL[1:len(thisServiceProxyEP)+1] == thisServiceProxyEP {
  366. //This is a proxy path. Generate the rewrite URL
  367. //Get all GET paramters from URL
  368. values := r.URL.Query()
  369. counter := 0
  370. parsedGetTail := ""
  371. for k, v := range values {
  372. if counter == 0 {
  373. parsedGetTail = "?" + k + "=" + url.QueryEscape(v[0])
  374. } else {
  375. parsedGetTail = parsedGetTail + "&" + k + "=" + url.QueryEscape(v[0])
  376. }
  377. counter++
  378. }
  379. return true, subservice.ProxyHandler, requestURL[len(thisServiceProxyEP)+1:] + parsedGetTail, &subservice
  380. }
  381. }
  382. }
  383. return false, nil, "", &SubService{}
  384. }
  385. func (sr *SubServiceRouter) Close() {
  386. //Handle shutdown of subprocesses. Kill all of them
  387. for _, subservice := range sr.RunningSubService {
  388. cmd := subservice.Process
  389. if cmd != nil {
  390. if runtime.GOOS == "windows" {
  391. //Force kill with the power of CMD
  392. kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
  393. //kill.Stderr = os.Stderr
  394. //kill.Stdout = os.Stdout
  395. kill.Run()
  396. } else {
  397. //Send sigkill to process
  398. cmd.Process.Kill()
  399. }
  400. }
  401. }
  402. }
  403. func (sr *SubServiceRouter) KillSubService(serviceDir string) error {
  404. //Remove them from the system
  405. ssi := -1
  406. moduleName := ""
  407. for i, ss := range sr.RunningSubService {
  408. if ss.ServiceDir == serviceDir {
  409. ssi = i
  410. moduleName = ss.Info.Name
  411. //Kill the module cmd
  412. cmd := ss.Process
  413. if cmd != nil {
  414. if runtime.GOOS == "windows" {
  415. //Force kill with the power of CMD
  416. kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
  417. kill.Run()
  418. } else {
  419. err := cmd.Process.Kill()
  420. if err != nil {
  421. return err
  422. }
  423. }
  424. }
  425. //Write a suspended file into the module
  426. ioutil.WriteFile("subservice/"+ss.ServiceDir+"/.disabled", []byte(""), 0755)
  427. }
  428. }
  429. //Pop this service from running Subservice
  430. if ssi != -1 {
  431. i := ssi
  432. copy(sr.RunningSubService[i:], sr.RunningSubService[i+1:])
  433. sr.RunningSubService = sr.RunningSubService[:len(sr.RunningSubService)-1]
  434. }
  435. //Pop the related module from the loadedModule list
  436. mi := -1
  437. for i, m := range sr.moduleHandler.LoadedModule {
  438. if m.Name == moduleName {
  439. mi = i
  440. }
  441. }
  442. if mi != -1 {
  443. i := mi
  444. copy(sr.moduleHandler.LoadedModule[i:], sr.moduleHandler.LoadedModule[i+1:])
  445. sr.moduleHandler.LoadedModule = sr.moduleHandler.LoadedModule[:len(sr.moduleHandler.LoadedModule)-1]
  446. }
  447. return nil
  448. }
  449. func (sr *SubServiceRouter) StartSubService(serviceDir string) error {
  450. if fileExists("subservice/" + serviceDir) {
  451. err := sr.Launch("subservice/"+serviceDir, false)
  452. if err != nil {
  453. return err
  454. }
  455. } else {
  456. return errors.New("Subservice directory not exists.")
  457. }
  458. //Sort the list
  459. sort.Slice(sr.moduleHandler.LoadedModule, func(i, j int) bool {
  460. return sr.moduleHandler.LoadedModule[i].Name < sr.moduleHandler.LoadedModule[j].Name
  461. })
  462. sort.Slice(sr.RunningSubService, func(i, j int) bool {
  463. return sr.RunningSubService[i].Info.Name < sr.RunningSubService[j].Info.Name
  464. })
  465. return nil
  466. }
  467. // Get a list of subservice roots in realpath
  468. func (sr *SubServiceRouter) GetSubserviceRoot() []string {
  469. subserviceRoots := []string{}
  470. for _, subService := range sr.RunningSubService {
  471. subserviceRoots = append(subserviceRoots, subService.Path)
  472. }
  473. return subserviceRoots
  474. }
  475. // Scan and get the next avaible port for subservice from its basePort
  476. func (sr *SubServiceRouter) GetNextUsablePort() int {
  477. basePort := sr.BasePort
  478. for sr.CheckIfPortInUse(basePort) {
  479. basePort++
  480. }
  481. return basePort
  482. }
  483. func (sr *SubServiceRouter) CheckIfPortInUse(port int) bool {
  484. for _, service := range sr.RunningSubService {
  485. if service.Port == port {
  486. return true
  487. }
  488. }
  489. return false
  490. }
  491. func (sr *SubServiceRouter) HandleRoutingRequest(w http.ResponseWriter, r *http.Request, proxy *reverseproxy.ReverseProxy, subserviceObject *SubService, rewriteURL string) {
  492. u, _ := sr.userHandler.GetUserInfoFromRequest(w, r)
  493. if !sr.CheckUserPermissionOnSubservice(subserviceObject, u) {
  494. //Permission denied
  495. http.NotFound(w, r)
  496. return
  497. }
  498. //Perform reverse proxy serving
  499. r.URL, _ = url.Parse(rewriteURL)
  500. token, _ := sr.userHandler.GetAuthAgent().NewTokenFromRequest(w, r)
  501. r.Header.Set("aouser", u.Username)
  502. r.Header.Set("aotoken", token)
  503. r.Header.Set("X-Forwarded-Host", r.Host)
  504. if r.Header["Upgrade"] != nil && r.Header["Upgrade"][0] == "websocket" {
  505. //Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
  506. r.Header.Set("A-Upgrade", "websocket")
  507. u, _ := url.Parse("ws://localhost:" + strconv.Itoa(subserviceObject.Port) + r.URL.String())
  508. wspHandler := websocketproxy.NewProxy(u)
  509. wspHandler.ServeHTTP(w, r)
  510. return
  511. }
  512. r.Host = r.URL.Host
  513. err := proxy.ServeHTTP(w, r)
  514. if err != nil {
  515. //Check if it is cancelling events.
  516. if !strings.Contains(err.Error(), "cancel") {
  517. sr.logger.PrintAndLog("Subservice", subserviceObject.Info.Name+" IS NOT RESPONDING!", err)
  518. sr.RestartSubService(subserviceObject)
  519. }
  520. }
  521. }
  522. // Handle fail start over when the remote target is not responding
  523. func (sr *SubServiceRouter) RestartSubService(ss *SubService) {
  524. go func(ss *SubService) {
  525. //Kill the original subservice
  526. sr.KillSubService(ss.ServiceDir)
  527. sr.logger.PrintAndLog("Subservice", "RESTARTING SUBSERVICE "+ss.Info.Name+" IN 10 SECOUNDS", nil)
  528. time.Sleep(10000 * time.Millisecond)
  529. sr.StartSubService(ss.ServiceDir)
  530. sr.logger.PrintAndLog("Subservice", "SUBSERVICE "+ss.Info.Name+" RESTARTED", nil)
  531. }(ss)
  532. }