# HG changeset patch
# User jfp <jf.pieronne@laposte.net>
# Date 1683634910 -7200
#      Tue May 09 14:21:50 2023 +0200
# Node ID d46a90f35a22ff36f562e1a7a7d7bbbb342b8cc7
# Parent  8fe8267c9b59bdf3de5ede2b55dcc8c00b83752c
Add optional password for supervisorctl, return a reply for each command

diff --git a/supervisorctl.py b/supervisorctl.py
--- a/supervisorctl.py
+++ b/supervisorctl.py
@@ -1,10 +1,12 @@
+import argparse
 import cmd
 import json
-import sys
-from typing import Tuple
+from typing import Tuple, Any
 
 from ovms import cmbdef, iodef, mbxqio, psldef, starlet
 
+supervisorctl_pwd = ''
+
 
 def pstat2str(s: int) -> str:
     dstate = {
@@ -36,6 +38,15 @@
         self.mcmd_r = mcmd_r
         cmd.Cmd.__init__(self, completekey, stdin, stdout)
 
+    def write_cmd(self, cmd: dict) -> Any:
+        global supervisorctl_pwd
+        cmd['pwd'] = supervisorctl_pwd
+        jscmd = json.dumps(cmd)
+        self.mcmd.write(jscmd.encode('ascii'), iodef.IO_M_READERCHECK)
+        return json.loads(
+            self.mcmd_r.read(4096, iodef.IO_M_WRITERCHECK)  # type: ignore
+        )
+
     def do_EOF(self, line):
         """To quit, type ^D or use the quit command"""
         return True
@@ -50,21 +61,36 @@
         if len(lst) != 0:
             print('*** invalid number of arguments')
             return
-        jscmd = json.dumps({'cmd': 'shutdown'})
-        self.mcmd.write(jscmd.encode('ascii'), iodef.IO_M_READERCHECK)
+        res = self.write_cmd({'cmd': 'shutdown'})
+        print(res)
 
     def do_status(self, arg):
         """Display status of programs"""
         lst = arg.split()
-        jscmd = json.dumps({'cmd': 'status', 'programs': lst})
-        self.mcmd.write(jscmd.encode('ascii'), iodef.IO_M_READERCHECK)
-        res: bytes
-        res = self.mcmd_r.read(4096, iodef.IO_M_WRITERCHECK)   # type: ignore
-        jscmd = json.loads(res)
-        for pinfo in jscmd['reply']:
+        jscmd = self.write_cmd({'cmd': 'status', 'programs': lst})
+        if 'error' in jscmd:
+            print(jscmd['error'])
+            return
+        for pinfo in jscmd['result']:
             pinfo[3] = pstat2str(pinfo[3])
             print(pinfo)
 
+    def do_start(self, arg):
+        lst = arg.split()
+        if len(lst) < 1:
+            print('*** invalid number of arguments')
+            return
+        res = self.write_cmd({'cmd': 'start', 'programs': lst})
+        print(res)
+
+    def do_stop(self, arg):
+        lst = arg.split()
+        if len(lst) < 1:
+            print('*** invalid number of arguments')
+            return
+        res = self.write_cmd({'cmd': 'stop', 'programs': lst})
+        print(res)
+
     def help_status(self):
         print('status <name>\t\tGet status for a single process')
         print('status <gname>:*\tGet status for all ' 'processes in a group')
@@ -73,28 +99,12 @@
         )
         print('status\t\t\tGet all process status info')
 
-    def do_start(self, arg):
-        lst = arg.split()
-        if len(lst) < 1:
-            print('*** invalid number of arguments')
-            return
-        jscmd = json.dumps({'cmd': 'start', 'programs': lst})
-        self.mcmd.write(jscmd.encode('ascii'), iodef.IO_M_READERCHECK)
-
     def help_start(self):
         print('start <name>\t\tStart a process')
         print('start <gname>:*\t\tStart all processes in a group')
         print('start <name> <name>\tStart multiple processes or groups')
         print('start all\t\tStart all processes')
 
-    def do_stop(self, arg):
-        lst = arg.split()
-        if len(lst) < 1:
-            print('*** invalid number of arguments')
-            return
-        jscmd = json.dumps({'cmd': 'stop', 'programs': lst})
-        self.mcmd.write(jscmd.encode('ascii'), iodef.IO_M_READERCHECK)
-
     def help_stop(self):
         print('stop <name>\t\tStop a process')
         print('stop <gname>:*\t\tStop all processes in a group')
@@ -126,14 +136,28 @@
 
 
 def main():
+    global supervisorctl_pwd
+    parser = argparse.ArgumentParser(description='supervisorctl')
+    parser.add_argument(
+        '-p',
+        '--password',
+        required=False,
+        #        type=str,
+        default='',
+        help='password for supervisord',
+    )
+    # args = parser.parse_args()
+    # print('-->', args)
+    args, unknownargs = parser.parse_known_args()
+    supervisorctl_pwd = args.password
     chan, chan_r = mbx_init()
     with (
         mbxqio.MBXQIO(channel=chan) as mcmd,
         mbxqio.MBXQIO(channel=chan_r) as mcmd_r,
     ):
         c = Controller(mcmd, mcmd_r)
-        if len(sys.argv) > 1:
-            c.onecmd(' '.join(sys.argv[1:]))
+        if len(unknownargs) > 0:
+            c.onecmd(' '.join(unknownargs))
         else:
             c.cmdloop()
 
diff --git a/supervisord.conf_template b/supervisord.conf_template
--- a/supervisord.conf_template
+++ b/supervisord.conf_template
@@ -1,3 +1,5 @@
+[openvms]
+password=xxxxxx
 [program:firstpgm]
 command=dev:[dir]PGM1.COM
 process_name=PGM1D
diff --git a/supervisord.py b/supervisord.py
--- a/supervisord.py
+++ b/supervisord.py
@@ -1,6 +1,7 @@
 #
 # Required  privileges: SYSNAM, PRMMBX, IMPERSONATE, WORLD
 #
+import argparse
 import configparser
 import ctypes
 import datetime
@@ -38,6 +39,7 @@
 current_tick: int = 0
 timer_queue: queue.PriorityQueue['PrioritizedItem'] = queue.PriorityQueue()
 
+supervisorctl_pwd = ''
 
 class TimerItemType(IntEnum):
     UNDEF = 0
@@ -420,11 +422,29 @@
     return astctxt
 
 
+def send_cmd_reply(fcmd_r: mbxqio.MBXQIO, cmdreply: dict):
+    jscmd = json.dumps(cmdreply)
+    try:
+        fcmd_r.write(
+            jscmd.encode('ascii'),
+            iodef.IO_M_READERCHECK | iodef.IO_M_NOW,
+        )
+    except OSError:
+        pass
+
 def dispatch_cmd(res: bytes, fcmd_r: mbxqio.MBXQIO):
+    global supervisorctl_pwd
     jscmd = json.loads(res)
-    logging.info(f'Receive cmd: {jscmd}')
+    pwd = jscmd['pwd']
+    jscmd['pwd'] = '*****'
+    if pwd != supervisorctl_pwd:
+        logging.info('Invalid password')
+        send_cmd_reply(fcmd_r, {'error': 'Invalid password'})
+
+        return
     match jscmd['cmd'].lower():
         case 'shutdown':
+            send_cmd_reply(fcmd_r, {'result': None})
             exit(1)
         case 'start':
             pgmnames = set([name.upper() for name in jscmd['programs']])
@@ -441,6 +461,7 @@
                             pgm.process.state = ProcessStates.STOPPED
                             pgm.remain_startretries = pgm.startretries + 1
                         pgm.create_process(False)
+                send_cmd_reply(fcmd_r, {'result': None})
         case 'stop':
             pgmnames = set([name.upper() for name in jscmd['programs']])
             if 'ALL' in pgmnames:
@@ -450,6 +471,7 @@
                 for pgm in Program.programs.values():
                     if pgm.name in pgmnames:
                         pgm.kill()
+                send_cmd_reply(fcmd_r, {'result': None})
         case 'status':
             pgmnames = set([name.upper() for name in jscmd['programs']])
             lst = []
@@ -466,21 +488,14 @@
                 )
                 if pgm.process.state in (
                     ProcessStates.BACKOFF,
-                   ProcessStates.STOPPED,
+                    ProcessStates.STOPPED,
                     ProcessStates.EXITED,
                     ProcessStates.FATAL,
                 ):
                     lst[-1].append(str(pgm.process.end_time)[:19])
                     lst[-1].append(pgm.process.finalsts)
 
-            jscmd = json.dumps({'reply': lst})
-            try:
-                fcmd_r.write(
-                    jscmd.encode('ascii'),
-                    iodef.IO_M_READERCHECK | iodef.IO_M_NOW,
-                )
-            except OSError:
-                pass
+            send_cmd_reply(fcmd_r, {'result': lst})
 
 
 def dispatch_ast(astparam: AstParam, fcmd_r: mbxqio.MBXQIO):
@@ -579,7 +594,7 @@
 
 
 def main():
-    global logger
+    global logger, supervisorctl_pwd
 
     logging_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
     logging.basicConfig(
@@ -603,8 +618,22 @@
         | prvdef.PRV_M_WORLD,
     )
 
+    parser = argparse.ArgumentParser(description='supervisord')
+    parser.add_argument(
+        '-c',
+        '--configuration',
+        type=argparse.FileType('r', encoding='latin1'),
+        default='./supervisord.conf',
+        help='Configuration file path (default ./supervisord.conf)',
+    )
+    args = parser.parse_args()
+
     config = configparser.ConfigParser()
-    config.read('supervisord.conf')
+    config.read_file(args.configuration)
+    args.configuration.close()
+
+    supervisorctl_pwd = config['openvms'].get('password', '')
+
     for sn in config.sections():
         if sn.startswith('program:'):
             name = sn.split(':')[-1].upper()