跳板机管理平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 lines
9.2 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #!/usr/bin/env python3
  2. # -*- coding: UTF-8 -*-
  3. import paramiko
  4. import os
  5. import sys
  6. import curses
  7. import termios
  8. import tty
  9. import socket
  10. import signal
  11. import getpass
  12. import platform
  13. import sqlite3
  14. KEYS_ENTER = (curses.KEY_ENTER, ord('\n'), ord('\r'))
  15. KEYS_UP = (curses.KEY_UP, ord('k'))
  16. KEYS_DOWN = (curses.KEY_DOWN, ord('j'))
  17. KEYS_SELECT = (curses.KEY_RIGHT, ord(' '))
  18. # 数据库文件 default
  19. gSqlite3File = "/usr/local/jumpserver/jumpserver.db"
  20. # ssh_private_path ssh私钥路径
  21. system_type = platform.system()
  22. if system_type == "Darwin":
  23. ssh_private_path = "/Users/%s/.ssh/id_rsa"
  24. elif system_type == "Linux":
  25. ssh_private_path = "/home/%s/.ssh/id_rsa"
  26. else:
  27. print("Don't support your system(%s)" % system_type)
  28. exit(1)
  29. def init():
  30. global gSqlite3File
  31. jumpDb = os.environ.get("JUMPDB")
  32. if jumpDb is not None:
  33. gSqlite3File = jumpDb
  34. else:
  35. print("ERROR: Hadn't set environ var 'JUMPDB' for jump database! mod 755。Default use %s" % gSqlite3File)
  36. # exit(1)
  37. class Picker(object):
  38. def __init__(self, options, title=None, indicator='*', default_index=0, multiselect=False, multi_select=False,
  39. min_selection_count=0, options_map_func=None):
  40. if len(options) == 0:
  41. raise ValueError('options should not be an empty list')
  42. self.options = options
  43. self.title = title
  44. self.indicator = indicator
  45. self.multiselect = multiselect or multi_select
  46. self.min_selection_count = min_selection_count
  47. self.options_map_func = options_map_func
  48. self.all_selected = []
  49. if default_index >= len(options):
  50. raise ValueError('default_index should be less than the length of options')
  51. if multiselect and min_selection_count > len(options):
  52. raise ValueError(
  53. 'min_selection_count is bigger than the available options, you will not be able to make any selection')
  54. if options_map_func is not None and not callable(options_map_func):
  55. raise ValueError('options_map_func must be a callable function')
  56. self.index = default_index
  57. self.custom_handlers = {}
  58. def register_custom_handler(self, key, func):
  59. self.custom_handlers[key] = func
  60. def move_up(self):
  61. self.index -= 1
  62. if self.index < 0:
  63. self.index = len(self.options) - 1
  64. def move_down(self):
  65. self.index += 1
  66. if self.index >= len(self.options):
  67. self.index = 0
  68. def mark_index(self):
  69. if self.multiselect:
  70. if self.index in self.all_selected:
  71. self.all_selected.remove(self.index)
  72. else:
  73. self.all_selected.append(self.index)
  74. def get_selected(self):
  75. """return the current selected option as a tuple: (option, index)
  76. or as a list of tuples (in case multiselect==True)
  77. """
  78. if self.multiselect:
  79. return_tuples = []
  80. for selected in self.all_selected:
  81. return_tuples.append((self.options[selected], selected))
  82. return return_tuples
  83. else:
  84. return self.options[self.index], self.index
  85. def get_title_lines(self):
  86. if self.title:
  87. return self.title.split('\n') + ['']
  88. return []
  89. def get_option_lines(self):
  90. lines = []
  91. for index, option in enumerate(self.options):
  92. # pass the option through the options map of one was passed in
  93. if self.options_map_func:
  94. option = self.options_map_func(option)
  95. if index == self.index:
  96. prefix = self.indicator
  97. else:
  98. prefix = len(self.indicator) * ' '
  99. if self.multiselect and index in self.all_selected:
  100. format = curses.color_pair(1)
  101. line = ('{0} {1}'.format(prefix, option), format)
  102. else:
  103. line = '{0} {1}'.format(prefix, option)
  104. lines.append(line)
  105. return lines
  106. def get_lines(self):
  107. title_lines = self.get_title_lines()
  108. option_lines = self.get_option_lines()
  109. lines = title_lines + option_lines
  110. current_line = self.index + len(title_lines) + 1
  111. return lines, current_line
  112. def draw(self):
  113. """draw the curses ui on the screen, handle scroll if needed"""
  114. self.screen.clear()
  115. x, y = 1, 1 # start point
  116. max_y, max_x = self.screen.getmaxyx()
  117. max_rows = max_y - y # the max rows we can draw
  118. lines, current_line = self.get_lines()
  119. # calculate how many lines we should scroll, relative to the top
  120. scroll_top = getattr(self, 'scroll_top', 0)
  121. if current_line <= scroll_top:
  122. scroll_top = 0
  123. elif current_line - scroll_top > max_rows:
  124. scroll_top = current_line - max_rows
  125. self.scroll_top = scroll_top
  126. lines_to_draw = lines[scroll_top:scroll_top + max_rows]
  127. for line in lines_to_draw:
  128. if type(line) is tuple:
  129. self.screen.addnstr(y, x, line[0], max_x - 2, line[1])
  130. else:
  131. self.screen.addnstr(y, x, line, max_x - 2)
  132. y += 1
  133. self.screen.refresh()
  134. def run_loop(self):
  135. while True:
  136. self.draw()
  137. c = self.screen.getch()
  138. if c in KEYS_UP:
  139. self.move_up()
  140. elif c in KEYS_DOWN:
  141. self.move_down()
  142. elif c in KEYS_ENTER:
  143. if self.multiselect and len(self.all_selected) < self.min_selection_count:
  144. continue
  145. return self.get_selected()
  146. elif c in KEYS_SELECT and self.multiselect:
  147. self.mark_index()
  148. elif c in self.custom_handlers:
  149. ret = self.custom_handlers[c](self)
  150. if ret:
  151. return ret
  152. elif c == ord('q'):
  153. exit(0)
  154. def config_curses(self):
  155. try:
  156. # use the default colors of the terminal
  157. curses.use_default_colors()
  158. # hide the cursor
  159. curses.curs_set(0)
  160. # add some color for multi_select
  161. # @todo make colors configurable
  162. curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_WHITE)
  163. except:
  164. # Curses failed to initialize color support, eg. when TERM=vt100
  165. curses.initscr()
  166. def _start(self, screen):
  167. self.screen = screen
  168. self.config_curses()
  169. return self.run_loop()
  170. def start(self):
  171. return curses.wrapper(self._start)
  172. def updateWindowHandler(signum):
  173. if signum == signal.SIGWINCH:
  174. width, height = os.get_terminal_size()
  175. os.terminal_size((width, height))
  176. def posix_shell(chan):
  177. import select
  178. oldtty = termios.tcgetattr(sys.stdin)
  179. signal.signal(signal.SIGWINCH, updateWindowHandler)
  180. try:
  181. tty.setraw(sys.stdin.fileno(), termios.TCIOFLUSH)
  182. # tty.setcbreak(sys.stdin.fileno())
  183. chan.settimeout(0.0)
  184. while True:
  185. r, w, e = select.select([chan, sys.stdin], [], [])
  186. if chan in r:
  187. try:
  188. data = str(chan.recv(1024), encoding='utf-8')
  189. if len(data) == 0:
  190. break
  191. sys.stdout.write(data)
  192. sys.stdout.flush()
  193. except socket.timeout:
  194. pass
  195. if sys.stdin in r:
  196. ch = sys.stdin.read(1)
  197. if len(ch) == 0:
  198. break
  199. chan.send(ch)
  200. finally:
  201. termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
  202. def NewTerminal(host, port, user):
  203. # 建立ssh连接
  204. ssh = paramiko.SSHClient()
  205. ssh.load_system_host_keys()
  206. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  207. ssh.connect(
  208. host,
  209. port=port,
  210. username=user,
  211. key_filename=ssh_private_path % user,
  212. compress=False)
  213. # 建立交互式shell连接
  214. width, height = os.get_terminal_size()
  215. channel = ssh.invoke_shell("xterm-256color", width, height)
  216. # 建立交互式管道
  217. posix_shell(channel)
  218. # 关闭连接
  219. channel.close()
  220. ssh.close()
  221. # 初始化表
  222. def connect_db():
  223. # 连接数据库
  224. conn = sqlite3.connect(gSqlite3File)
  225. if conn == None:
  226. print("sqlite3.connect " + gSqlite3File + "failed!")
  227. exit(1)
  228. return conn
  229. def get_hosts(user):
  230. db = connect_db()
  231. hosts = db.execute("select name,ip,port from hosts where isdelete=0 and name in (select hostname from hostuser where username='%s') order by id" % user).fetchall()
  232. resp = []
  233. for host in hosts:
  234. resp.append("%s:%s:%s" % (host[0], host[1], host[2]))
  235. if len(resp) == 0:
  236. print(user + " no valid hosts")
  237. exit(1)
  238. return resp
  239. def main():
  240. # 初始化检测
  241. init()
  242. user = getpass.getuser()
  243. print("current user: " + user)
  244. title = "ssh hosts select:"
  245. menu = get_hosts(user)
  246. while True:
  247. option, index = Picker(menu, title).start()
  248. arr = option.split(":")
  249. if len(arr) == 3:
  250. host = arr[1]
  251. port = int(arr[2])
  252. NewTerminal(host, port, user)
  253. if __name__ == '__main__':
  254. main()