Port to Python3
Handle bytes vs strings. Fewer logging for interactive mode. Prepare config file: move -f to -g, use -f for config.
This commit is contained in:
parent
d09c0c82ea
commit
71c89f1fe7
1 changed files with 100 additions and 49 deletions
135
ereshkigal.py
Executable file → Normal file
135
ereshkigal.py
Executable file → Normal file
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Ereshkigal is an AutoSSH tunnel monitor
|
# Ereshkigal is an AutoSSH tunnel monitor
|
||||||
|
|
@ -110,10 +110,12 @@ class AutoSSHTunnelMonitor(list):
|
||||||
"""Gather and parse informations from the operating system"""
|
"""Gather and parse informations from the operating system"""
|
||||||
# autossh processes
|
# autossh processes
|
||||||
autosshs = self.get_autossh_instances()
|
autosshs = self.get_autossh_instances()
|
||||||
|
if autosshs:
|
||||||
logging.debug("Autossh processes: %s" % autosshs)
|
logging.debug("Autossh processes: %s" % autosshs)
|
||||||
|
|
||||||
# ssh connections related to a tunnel
|
# ssh connections related to a tunnel
|
||||||
connections = self.get_connections()
|
connections = self.get_connections()
|
||||||
|
if connections:
|
||||||
logging.debug("SSH connections related to a tunnel: %s" % connections)
|
logging.debug("SSH connections related to a tunnel: %s" % connections)
|
||||||
|
|
||||||
# Bind existing connections to autossh processes.
|
# Bind existing connections to autossh processes.
|
||||||
|
|
@ -161,30 +163,33 @@ class AutoSSHTunnelMonitor(list):
|
||||||
|
|
||||||
|
|
||||||
# list of processes with the "autossh" string
|
# list of processes with the "autossh" string
|
||||||
status_list = [ps for ps in status[1].readlines() if "autossh" in ps]
|
status_list = [ps for ps in status[1].readlines() if b"autossh" in ps]
|
||||||
|
if status_list:
|
||||||
logging.debug("Processes containing 'autossh': %s" % status_list)
|
logging.debug("Processes containing 'autossh': %s" % status_list)
|
||||||
|
|
||||||
# split the process line if it contains a "-L"
|
# split the process line if it contains a "-L"
|
||||||
cmds = [i.split() for i in status_list if '-L' in i]
|
cmds = [i.split() for i in status_list if '-L' in i.decode()]
|
||||||
|
|
||||||
autosshs = []
|
autosshs = []
|
||||||
|
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
|
logging.debug("Parse command: %s" % cmd)
|
||||||
|
|
||||||
# split the command in order to obtain arguments to the -L option
|
# split the command in order to obtain arguments to the -L option
|
||||||
args = [i.strip('-').strip('-').strip('L') for i in cmd if '-L' in i][0].split(':')
|
args = [i.strip(b'L-') for i in cmd if '-L' in i.decode()][0].split(b':')
|
||||||
logging.debug("Split around -L: %s" % args)
|
logging.debug("Split around -L: %s" % args)
|
||||||
|
|
||||||
pid = int(cmd[0])
|
pid = int(cmd[0])
|
||||||
local_port = int(args[0])
|
local_port = int(args[0])
|
||||||
target_host = args[1]
|
target_host = args[1].decode()
|
||||||
foreign_port = int(args[2])
|
foreign_port = int(args[2])
|
||||||
|
|
||||||
# find the hostname where the tunnel goes
|
# find the hostname where the tunnel goes
|
||||||
via_host = "unknown"
|
via_host = "unknown"
|
||||||
for i in xrange( len(cmd)-1,0,-1 ):
|
for i in range( len(cmd)-1,0,-1 ):
|
||||||
if cmd[i][0] != '-':
|
if chr(cmd[i][0]) != '-':
|
||||||
via_host = cmd[i]
|
via_host = cmd[i].decode()
|
||||||
|
logging.debug("Via host: %s" % via_host)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -197,12 +202,12 @@ class AutoSSHTunnelMonitor(list):
|
||||||
|
|
||||||
def parse_addr_port(self, addr_port):
|
def parse_addr_port(self, addr_port):
|
||||||
if len(addr_port) == 2: # ipv4
|
if len(addr_port) == 2: # ipv4
|
||||||
addr = addr_port[0]
|
addr = addr_port[0].decode()
|
||||||
logging.debug("IPv4 address: %s" % addr)
|
logging.debug("IPv4 address: %s" % addr)
|
||||||
port = int(addr_port[1])
|
port = int(addr_port[1])
|
||||||
logging.debug("IPv4 port: %s" % port)
|
logging.debug("IPv4 port: %s" % port)
|
||||||
else: # ipv6
|
else: # ipv6
|
||||||
addr = ":".join(addr_port[:-1])
|
addr = b":".join(addr_port[:-1]).decode()
|
||||||
logging.debug("IPv6 address: %s" % addr)
|
logging.debug("IPv6 address: %s" % addr)
|
||||||
port = int(addr_port[-1])
|
port = int(addr_port[-1])
|
||||||
logging.debug("IPv6 port: %s" % port)
|
logging.debug("IPv6 port: %s" % port)
|
||||||
|
|
@ -220,9 +225,9 @@ class AutoSSHTunnelMonitor(list):
|
||||||
status = (p.stdin, p.stdout, p.stderr)
|
status = (p.stdin, p.stdout, p.stderr)
|
||||||
|
|
||||||
status_list = status[1].readlines()
|
status_list = status[1].readlines()
|
||||||
logging.debug("%i active connections" % len(status_list))
|
# logging.debug("%i active connections" % len(status_list))
|
||||||
|
|
||||||
cons = [i.split() for i in status_list if 'ssh' in i]
|
cons = [i.split() for i in status_list if b'ssh' in i]
|
||||||
|
|
||||||
tunnels = []
|
tunnels = []
|
||||||
|
|
||||||
|
|
@ -232,18 +237,18 @@ class AutoSSHTunnelMonitor(list):
|
||||||
# Proto Recv-Q Send-Q Adresse locale Adresse distante Etat PID/Program name
|
# Proto Recv-Q Send-Q Adresse locale Adresse distante Etat PID/Program name
|
||||||
|
|
||||||
# local infos
|
# local infos
|
||||||
local = con[3].split(':')
|
local = con[3].split(b':')
|
||||||
logging.debug("local infos: %s" % local)
|
logging.debug("local infos: %s" % local)
|
||||||
local_addr, local_port = self.parse_addr_port(local)
|
local_addr, local_port = self.parse_addr_port(local)
|
||||||
|
|
||||||
# foreign infos
|
# foreign infos
|
||||||
foreign = con[4].split(':')
|
foreign = con[4].split(b':')
|
||||||
foreign_addr, foreign_port = self.parse_addr_port(foreign)
|
foreign_addr, foreign_port = self.parse_addr_port(foreign)
|
||||||
|
|
||||||
status = con[5]
|
status = con[5]
|
||||||
logging.debug("Connection status: %s" % status)
|
logging.debug("Connection status: %s" % status)
|
||||||
|
|
||||||
sshpid = int( con[6].split('/')[0] )
|
sshpid = int( con[6].split(b'/')[0] )
|
||||||
logging.debug("SSH PID: %s" % sshpid)
|
logging.debug("SSH PID: %s" % sshpid)
|
||||||
|
|
||||||
# ssh cmd line, got from /proc
|
# ssh cmd line, got from /proc
|
||||||
|
|
@ -266,7 +271,7 @@ class AutoSSHTunnelMonitor(list):
|
||||||
f = open( ppidf )
|
f = open( ppidf )
|
||||||
|
|
||||||
# filter the parent pid
|
# filter the parent pid
|
||||||
lpid = [i for i in f.readlines() if 'PPid' in i]
|
lpid = [i for i in f.readlines() if 'PPid' in str(i)]
|
||||||
f.close()
|
f.close()
|
||||||
logging.debug("PPid: %s" % lpid)
|
logging.debug("PPid: %s" % lpid)
|
||||||
|
|
||||||
|
|
@ -282,6 +287,11 @@ class AutoSSHTunnelMonitor(list):
|
||||||
# exclude the port
|
# exclude the port
|
||||||
content = f.readlines()
|
content = f.readlines()
|
||||||
logging.debug("Cmd: %s" % content[0])
|
logging.debug("Cmd: %s" % content[0])
|
||||||
|
if not 'autossh' in content[0]:
|
||||||
|
logging.warning("Tunnel not managed by autossh, ignore.")
|
||||||
|
# FIXME display those hanging tunnels in some way.
|
||||||
|
continue
|
||||||
|
|
||||||
autohost = content[0].split(':')[1]
|
autohost = content[0].split(':')[1]
|
||||||
f.close()
|
f.close()
|
||||||
logging.debug("Parsed cmd without port: %s" % autohost)
|
logging.debug("Parsed cmd without port: %s" % autohost)
|
||||||
|
|
@ -351,6 +361,7 @@ class monitorCurses:
|
||||||
# first update counter
|
# first update counter
|
||||||
last_update = time.clock()
|
last_update = time.clock()
|
||||||
last_state = None
|
last_state = None
|
||||||
|
log_ticks = ""
|
||||||
|
|
||||||
# infinite loop
|
# infinite loop
|
||||||
while(1):
|
while(1):
|
||||||
|
|
@ -367,11 +378,13 @@ class monitorCurses:
|
||||||
|
|
||||||
state = "%s" % self.tm
|
state = "%s" % self.tm
|
||||||
if state != last_state:
|
if state != last_state:
|
||||||
|
logging.debug("Waited: %s" % log_ticks)
|
||||||
|
log_ticks = ""
|
||||||
logging.debug("----- Time of screen update: %s -----" % time.time())
|
logging.debug("----- Time of screen update: %s -----" % time.time())
|
||||||
logging.debug("State of tunnels:\n%s" % self.tm)
|
logging.debug("State of tunnels:\n%s" % self.tm)
|
||||||
last_state = state
|
last_state = state
|
||||||
else:
|
else:
|
||||||
logging.debug('.')
|
log_ticks += "."
|
||||||
|
|
||||||
|
|
||||||
kc = self.scr.getch() # keycode
|
kc = self.scr.getch() # keycode
|
||||||
|
|
@ -387,11 +400,15 @@ class monitorCurses:
|
||||||
|
|
||||||
# Quit
|
# Quit
|
||||||
if ch in 'Qq':
|
if ch in 'Qq':
|
||||||
|
logging.debug("Waited: %s" % log_ticks)
|
||||||
|
log_ticks = ""
|
||||||
logging.debug("Key pushed: Q")
|
logging.debug("Key pushed: Q")
|
||||||
break
|
break
|
||||||
|
|
||||||
# Reload related autossh tunnels
|
# Reload related autossh tunnels
|
||||||
elif ch in 'rR':
|
elif ch in 'rR':
|
||||||
|
logging.debug("Waited: %s" % log_ticks)
|
||||||
|
log_ticks = ""
|
||||||
logging.debug("Key pushed: R")
|
logging.debug("Key pushed: R")
|
||||||
# if a pid is selected
|
# if a pid is selected
|
||||||
if self.cur_pid != -1:
|
if self.cur_pid != -1:
|
||||||
|
|
@ -402,23 +419,32 @@ class monitorCurses:
|
||||||
|
|
||||||
# Kill autossh process
|
# Kill autossh process
|
||||||
elif ch in 'kK':
|
elif ch in 'kK':
|
||||||
|
logging.debug("Waited: %s" % log_ticks)
|
||||||
|
log_ticks = ""
|
||||||
logging.debug("Key pushed: K")
|
logging.debug("Key pushed: K")
|
||||||
if self.cur_pid != -1:
|
if self.cur_pid != -1:
|
||||||
# send a SIGKILL
|
# send a SIGKILL
|
||||||
# the related process is stopped
|
# the related process is stopped
|
||||||
# FIXME SIGTERM or SIGKILL ?
|
# FIXME SIGTERM or SIGKILL ?
|
||||||
logging.debug("SIGKILL on PID: %i" % self.cur_pid)
|
logging.debug("SIGKILL on PID: %i" % self.cur_pid)
|
||||||
|
try:
|
||||||
os.kill( self.cur_pid, signal.SIGKILL )
|
os.kill( self.cur_pid, signal.SIGKILL )
|
||||||
|
except OSError:
|
||||||
|
logging.error("No such process: %i" % self.cur_pid)
|
||||||
|
|
||||||
# Switch to show ssh connections
|
# Switch to show ssh connections
|
||||||
# only available for root
|
# only available for root
|
||||||
elif ch in 'tT' and os.getuid() == 0:
|
elif ch in 'tT' and os.getuid() == 0:
|
||||||
|
logging.debug("Waited: %s" % log_ticks)
|
||||||
|
log_ticks = ""
|
||||||
logging.debug("Key pushed: T")
|
logging.debug("Key pushed: T")
|
||||||
self.show_tunnels = not self.show_tunnels
|
self.show_tunnels = not self.show_tunnels
|
||||||
|
|
||||||
# key pushed
|
# key pushed
|
||||||
elif kc == curses.KEY_DOWN:
|
elif kc == curses.KEY_DOWN:
|
||||||
logging.debug("Key pushed: downed")
|
logging.debug("Waited: %s" % log_ticks)
|
||||||
|
log_ticks = ""
|
||||||
|
logging.debug("Key pushed: down")
|
||||||
# if not the end of the list
|
# if not the end of the list
|
||||||
if self.cur_line < len(self.tm)-1:
|
if self.cur_line < len(self.tm)-1:
|
||||||
self.cur_line += 1
|
self.cur_line += 1
|
||||||
|
|
@ -427,9 +453,12 @@ class monitorCurses:
|
||||||
|
|
||||||
# key up
|
# key up
|
||||||
elif kc == curses.KEY_UP:
|
elif kc == curses.KEY_UP:
|
||||||
|
logging.debug("Waited: %s" % log_ticks)
|
||||||
|
log_ticks = ""
|
||||||
logging.debug("Key pushed: up")
|
logging.debug("Key pushed: up")
|
||||||
if self.cur_line > -1:
|
if self.cur_line > -1:
|
||||||
self.cur_line -= 1
|
self.cur_line -= 1
|
||||||
|
if self.cur_line > 0:
|
||||||
self.cur_pid = int(self.tm[self.cur_line]['pid'])
|
self.cur_pid = int(self.tm[self.cur_line]['pid'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -442,6 +471,8 @@ class monitorCurses:
|
||||||
# force a screen refresh
|
# force a screen refresh
|
||||||
self.scr.refresh()
|
self.scr.refresh()
|
||||||
|
|
||||||
|
# end of the loop
|
||||||
|
|
||||||
|
|
||||||
def display(self):
|
def display(self):
|
||||||
"""Generate the interface screen"""
|
"""Generate the interface screen"""
|
||||||
|
|
@ -458,6 +489,8 @@ class monitorCurses:
|
||||||
# Second line
|
# Second line
|
||||||
self.scr.addstr( "Active AutoSSH instances: ", curses.color_pair(6) )
|
self.scr.addstr( "Active AutoSSH instances: ", curses.color_pair(6) )
|
||||||
self.scr.addstr( str( len(self.tm) ), curses.color_pair(1) )
|
self.scr.addstr( str( len(self.tm) ), curses.color_pair(1) )
|
||||||
|
self.scr.addstr( " / Active connections: ", curses.color_pair(6) )
|
||||||
|
self.scr.addstr( str( sum([len(i['tunnels']) for i in self.tm]) ), curses.color_pair(1) )
|
||||||
self.scr.addstr( '\n', curses.color_pair(1) )
|
self.scr.addstr( '\n', curses.color_pair(1) )
|
||||||
self.scr.clrtoeol()
|
self.scr.clrtoeol()
|
||||||
|
|
||||||
|
|
@ -476,7 +509,7 @@ class monitorCurses:
|
||||||
self.scr.clrtoeol()
|
self.scr.clrtoeol()
|
||||||
|
|
||||||
# for each autossh processes available in the monitor
|
# for each autossh processes available in the monitor
|
||||||
for l in xrange(len(self.tm)):
|
for l in range(len(self.tm)):
|
||||||
# add a line for the l-th autossh process
|
# add a line for the l-th autossh process
|
||||||
self.add_autossh( l )
|
self.add_autossh( l )
|
||||||
|
|
||||||
|
|
@ -494,6 +527,7 @@ class monitorCurses:
|
||||||
|
|
||||||
# for each connections related to te line-th autossh process
|
# for each connections related to te line-th autossh process
|
||||||
for t in self.tm[line]['tunnels']:
|
for t in self.tm[line]['tunnels']:
|
||||||
|
# FIXME fail if the screen's height is too small.
|
||||||
self.scr.addstr( '\n\t* ' )
|
self.scr.addstr( '\n\t* ' )
|
||||||
|
|
||||||
self.scr.addstr( str( t['ssh_pid'] ), curses.color_pair(colors['ssh_pid'] ) )
|
self.scr.addstr( str( t['ssh_pid'] ), curses.color_pair(colors['ssh_pid'] ) )
|
||||||
|
|
@ -511,10 +545,10 @@ class monitorCurses:
|
||||||
color = self.colors_ssh['status']
|
color = self.colors_ssh['status']
|
||||||
# if the connections is established
|
# if the connections is established
|
||||||
# TODO avoid hard-coded constants
|
# TODO avoid hard-coded constants
|
||||||
if t['status'] != 'ESTABLISHED':
|
if t['status'].decode() != 'ESTABLISHED':
|
||||||
color = self.colors_ssh['status_out']
|
color = self.colors_ssh['status_out']
|
||||||
|
|
||||||
self.scr.addstr( str( t['status'] ), curses.color_pair( color ) )
|
self.scr.addstr( t['status'].decode(), curses.color_pair( color ) )
|
||||||
|
|
||||||
self.scr.clrtoeol()
|
self.scr.clrtoeol()
|
||||||
|
|
||||||
|
|
@ -534,7 +568,7 @@ class monitorCurses:
|
||||||
for i in self.tm[line]['tunnels']:
|
for i in self.tm[line]['tunnels']:
|
||||||
# add a vertical bar |
|
# add a vertical bar |
|
||||||
# the color change according to the status of the connection
|
# the color change according to the status of the connection
|
||||||
if i['status'] == 'ESTABLISHED':
|
if i['status'].decode() == 'ESTABLISHED':
|
||||||
self.scr.addstr( '|', curses.color_pair(self.colors_ssh['status']) )
|
self.scr.addstr( '|', curses.color_pair(self.colors_ssh['status']) )
|
||||||
else:
|
else:
|
||||||
self.scr.addstr( '|', curses.color_pair(self.colors_ssh['status_out']) )
|
self.scr.addstr( '|', curses.color_pair(self.colors_ssh['status_out']) )
|
||||||
|
|
@ -569,6 +603,7 @@ class monitorCurses:
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
import configparser
|
||||||
|
|
||||||
usage = """%prog [options]
|
usage = """%prog [options]
|
||||||
A user interface to monitor existing SSH tunnel that are managed with autossh.
|
A user interface to monitor existing SSH tunnel that are managed with autossh.
|
||||||
|
|
@ -596,37 +631,53 @@ if __name__ == "__main__":
|
||||||
parser.add_option('-l', '--log-level', choices=list(LOG_LEVELS), default='error', metavar='LEVEL',
|
parser.add_option('-l', '--log-level', choices=list(LOG_LEVELS), default='error', metavar='LEVEL',
|
||||||
help='Log level (%s), default: %s.' % (", ".join(LOG_LEVELS), 'error') )
|
help='Log level (%s), default: %s.' % (", ".join(LOG_LEVELS), 'error') )
|
||||||
|
|
||||||
parser.add_option('-f', '--log-file', default=None, metavar='FILE',
|
parser.add_option('-g', '--log-file', default=None, metavar='FILE',
|
||||||
help="Log to this file, default to standard output. \
|
help="Log to this file, default to standard output. \
|
||||||
If not set, asking for the curses interface automatically set logging to the \"ereshkigal.log\" file.")
|
If you use the curses interface, you may want to set this to actually see logs.")
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
parser.add_option('-f', '--config-file', default=None, metavar='FILE',
|
||||||
print(options)
|
help="Use this configuration file (default: '~/.ereshkigal.conf')")
|
||||||
|
|
||||||
|
(asked_for, args) = parser.parse_args()
|
||||||
|
|
||||||
logmsg = "----- Started Ereshkigal -----"
|
logmsg = "----- Started Ereshkigal -----"
|
||||||
if options.log_file:
|
|
||||||
logfile = options.log_file
|
if asked_for.log_file:
|
||||||
logging.basicConfig(filename=logfile, level=LOG_LEVELS[options.log_level])
|
logfile = asked_for.log_file
|
||||||
|
logging.basicConfig(filename=logfile, level=LOG_LEVELS[asked_for.log_level])
|
||||||
logging.debug(logmsg)
|
logging.debug(logmsg)
|
||||||
logging.debug("Log in %s" % logfile)
|
logging.debug("Log in %s" % logfile)
|
||||||
else:
|
else:
|
||||||
if options.curses:
|
if asked_for.curses:
|
||||||
logging.basicConfig(filename='ereshkigal.log', level=LOG_LEVELS[options.log_level])
|
logging.warning("It's a bad idea to log to stdout while in the curses interface.")
|
||||||
logging.debug(logmsg)
|
logging.basicConfig(level=LOG_LEVELS[asked_for.log_level])
|
||||||
logging.debug("Log in ereshkigal.log")
|
|
||||||
else:
|
|
||||||
logging.basicConfig(level=LOG_LEVELS[options.log_level])
|
|
||||||
logging.debug(logmsg)
|
logging.debug(logmsg)
|
||||||
logging.debug("Log to stdout")
|
logging.debug("Log to stdout")
|
||||||
|
|
||||||
logging.debug("Asked for: %s" % options)
|
logging.debug("Asked for: %s" % asked_for)
|
||||||
|
|
||||||
# unfortunately, options class has no __len__ method in python 2.4.3 (bug?)
|
# unfortunately, asked_for class has no __len__ method in python 2.4.3 (bug?)
|
||||||
#if len(options) > 1:
|
#if len(asked_for) > 1:
|
||||||
# parser.error("options are mutually exclusive")
|
# parser.error("asked_for are mutually exclusive")
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
if asked_for.config_file:
|
||||||
|
try:
|
||||||
|
config.read(asked_for.config_file)
|
||||||
|
except configparser.MissingSectionHeaderError:
|
||||||
|
logging.error("'%s' contains no known configuration" % asked_for.config_file)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
config.read('~/.ereshkigal.conf')
|
||||||
|
except configparser.MissingSectionHeaderError:
|
||||||
|
logging.error("'%s' contains no known configuration" % asked_for.config_file)
|
||||||
|
|
||||||
|
# Load autossh instances by sections: [expected]
|
||||||
|
# if config['expected']:
|
||||||
|
|
||||||
|
|
||||||
if options.curses:
|
|
||||||
|
if asked_for.curses:
|
||||||
logging.debug("Entering curses mode")
|
logging.debug("Entering curses mode")
|
||||||
import curses
|
import curses
|
||||||
import traceback
|
import traceback
|
||||||
|
|
@ -671,7 +722,7 @@ if __name__ == "__main__":
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
elif options.connections:
|
elif asked_for.connections:
|
||||||
logging.debug("Entering connections mode")
|
logging.debug("Entering connections mode")
|
||||||
tm = AutoSSHTunnelMonitor()
|
tm = AutoSSHTunnelMonitor()
|
||||||
# do not call update() but only get connections
|
# do not call update() but only get connections
|
||||||
|
|
@ -684,7 +735,7 @@ if __name__ == "__main__":
|
||||||
logging.error("Only root can see SSH tunnels connections.")
|
logging.error("Only root can see SSH tunnels connections.")
|
||||||
|
|
||||||
|
|
||||||
elif options.autossh:
|
elif asked_for.autossh:
|
||||||
logging.debug("Entering autossh mode")
|
logging.debug("Entering autossh mode")
|
||||||
tm = AutoSSHTunnelMonitor()
|
tm = AutoSSHTunnelMonitor()
|
||||||
# do not call update() bu only get autossh processes
|
# do not call update() bu only get autossh processes
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue