#!/usr/bin/env python3 # -*- coding: utf-8 -*- # metadata """OctopuSSH.""" __version__ = "1.5.0" __license__ = ' GPLv3+ LGPLv3+ ' __author__ = ' juancarlos ' __email__ = ' juancarlospaco@gmail.com ' __url__ = 'https://github.com/juancarlospaco/octopussh' __source__ = ('https://raw.githubusercontent.com/juancarlospaco/' 'octopussh/master/octopussh') # imports import logging as log import os import signal import sys import time from copy import copy from ctypes import byref, cdll, create_string_buffer from datetime import datetime from getpass import getuser from socket import getfqdn, gethostbyname from subprocess import call from urllib import request from webbrowser import open_new_tab from tempfile import gettempdir from PyQt5.QtCore import QUrl from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import (QApplication, QCheckBox, QCompleter, QDialogButtonBox, QFileDialog, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMainWindow, QMessageBox, QShortcut, QVBoxLayout, QWidget) DESKTOP_FILE = """ [Desktop Entry] Comment={description}, powered by OctopuSSH. Exec={command} || read -n1 -p 'SSH command failed!. Press any key to exit...' GenericName=ssh_to_{name} Icon=Terminal Name=ssh_to_{name} StartupNotify=false Terminal=true Type=Application X-DBUS-ServiceName=ssh_to_{name} X-KDE-StartupNotify=false """ SH_FILE = r"""#!/usr/bin/env bash # -*- coding: utf-8 -*- clear echo -e '\x1b[29;5;7m Connecting via SSH to {name} as {user}... \x1b[0m' {command} || read -n1 -p ' SSH command failed!. Press any key to exit...' """ ############################################################################### class MainWindow(QMainWindow): """Class representing main window.""" def __init__(self, parent=None): """Init main class.""" super(MainWindow, self).__init__() self.setWindowTitle(__doc__.strip().capitalize()) self.setMinimumSize(700, 240) self.setMaximumSize(800, 400) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("preferences-system")) self.center() QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("&File").addAction("Exit", exit) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("FullScreen", lambda: self.showFullScreen()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position()) windowMenu.addSeparator() windowMenu.addAction("Minimum size", lambda: self.resize(self.minimumSize())) windowMenu.addAction("Maximum size", lambda: self.resize(self.maximumSize())) windowMenu.addSeparator() helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("About Qt 5", lambda: QMessageBox.aboutQt(self)) helpMenu.addAction("About Python", about_python) helpMenu.addAction("About" + __doc__, about_self) helpMenu.addSeparator() helpMenu.addAction("View Source Code", view_code) helpMenu.addAction("Report Bugs", report_bug) helpMenu.addAction("Check Updates", lambda: QMessageBox.information( self, __doc__, check_for_updates())) container = QWidget() container_layout = QVBoxLayout(container) self.setCentralWidget(container) group0, group1 = QGroupBox("SSH"), QGroupBox("Options") container_layout.addWidget(group0) container_layout.addWidget(group1) self.user, self.pswd = QLineEdit(getuser().lower()), QLineEdit() self.host, self.path = QLineEdit(), QLineEdit() self.user.setPlaceholderText("root") self.pswd.setPlaceholderText("P@ssw0rD!") self.host.setPlaceholderText(gethostbyname(getfqdn())) self.path.setPlaceholderText(os.path.expanduser("~")) self.user.setToolTip("Remote User to use for SSH connection") self.pswd.setToolTip("Password for remote user (OPTIONAL)") self.host.setToolTip("Remote SSH Server IP address or hostname") self.path.setToolTip("Remote SSH Server full path folder (OPTIONAL)") self.user.setCompleter(QCompleter(("root", getuser(), "guest"))) self.path.setCompleter(QCompleter(("/root", "/tmp", "/home", "/data"))) self.host.setCompleter(QCompleter(( "10.0.0.1", "172.16.0.1", "172.31.0.1", "192.168.0.1", "127.0.0.1" ))) ssh_layout = QHBoxLayout(group0) ssh_layout.addWidget(QLabel("User")) ssh_layout.addWidget(self.user) ssh_layout.addWidget(QLabel("Server")) ssh_layout.addWidget(self.host) ssh_layout.addWidget(QLabel("Password")) ssh_layout.addWidget(self.pswd) ssh_layout.addWidget(QLabel("Folder")) ssh_layout.addWidget(self.path) self.chrt, self.ionice = QCheckBox("Low CPU"), QCheckBox("Low HDD") self.verb, self.comprs = QCheckBox("Verbose"), QCheckBox("Compression") self.ign = QCheckBox("Ignore know_hosts") self.tun = QCheckBox("Tunnel") self.chrt.setChecked(True) self.ionice.setChecked(True) self.verb.setChecked(True) self.comprs.setChecked(True) self.ign.setChecked(True) self.tun.setChecked(True) self.chrt.setToolTip("Use Low CPU speed priority") self.ionice.setToolTip("Use Low HDD speed priority") self.verb.setToolTip("Use Verbose messages, ideal for Troubleshooting") self.comprs.setToolTip("Use Compression of all Data, ideal for Wifi") self.tun.setToolTip("Use full forwarding SSH tunnel, with X and ports") self.ign.setToolTip("Ignore check of {}/.ssh/known_hosts".format( os.path.expanduser("~"))) opt_layout = QHBoxLayout(group1) opt_layout.addWidget(self.chrt) opt_layout.addWidget(self.ionice) opt_layout.addWidget(self.verb) opt_layout.addWidget(self.comprs) opt_layout.addWidget(self.tun) opt_layout.addWidget(self.ign) self.bt = QDialogButtonBox(self) self.bt.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Close) self.bt.rejected.connect(exit) self.bt.accepted.connect(self.run) container_layout.addWidget(self.bt) self.host.setFocus() def run(self): """Run the main method and create bash script.""" if not len(self.user.text()) or not len(self.host.text()): QMessageBox.warning(self, __doc__, "ERROR:User or Server Empty") return conditional = bool(len(self.pswd.text())) script = " ".join(( "ionice --ignore --class 3" if self.ionice.isChecked() else "", "chrt --verbose --idle 0" if self.chrt.isChecked() else "", "sshpass -p '{}'".format(self.pswd.text()) if conditional else "", "ssh", "-vvv" if self.verb.isChecked() else "", "-C" if self.comprs.isChecked() else "", "-g -X" if self.tun.isChecked() else "", "-o StrictHostKeychecking=no" if self.ign.isChecked() else "", "{0}@{1}".format(self.user.text(), self.host.text()))) if bool(len(self.path.text())): script += ":{0}".format(self.path.text()) default_filename = os.path.join( os.path.expanduser("~"), "ssh_to_" + str(self.host.text()).strip().lower().replace(".", "_") + ".sh") extensions = ("Bash Script Executable for Linux .sh (*.sh);;" "Desktop Launcher file for Linux (*.desktop)") file_path = QFileDialog.getSaveFileName( self, __doc__.title() + " - Save SSH Script ! ", default_filename, extensions)[0] log.debug(script) if file_path and os.path.isdir(os.path.dirname(file_path)): if file_path.lower().endswith(".desktop"): self.save_as_desktop(file_path, script) else: self.save_as_sh(file_path, script) def save_as_sh(self, file_path, script): """Save as SH bash script.""" with open(file_path, "w", encoding="utf-8") as sh_file: sh_file.write(SH_FILE.format(name=self.host.text(), command=script, user=self.user.text().upper(),)) os.chmod(file_path, 0o775) def save_as_desktop(self, file_path, script): """Save as Desktop Launcher.""" desc = "Connect to {} as {} using SSH".format(self.host.text().title(), self.user.text().title()) launcher = DESKTOP_FILE.format( name=self.host.text(), command=script.strip(), description=desc) with open(file_path, "w", encoding="utf-8") as file_to_write: file_to_write.write(launcher) os.chmod(file_path, 0o775) def center(self): """Center Window on the Current Screen,with Multi-Monitor support.""" window_geometry = self.frameGeometry() mousepointer_position = QApplication.desktop().cursor().pos() screen = QApplication.desktop().screenNumber(mousepointer_position) centerPoint = QApplication.desktop().screenGeometry(screen).center() window_geometry.moveCenter(centerPoint) self.move(window_geometry.topLeft()) def move_to_mouse_position(self): """Center the Window on the Current Mouse position.""" window_geometry = self.frameGeometry() window_geometry.moveCenter(QApplication.desktop().cursor().pos()) self.move(window_geometry.topLeft()) def make_logger(name=str(os.getpid())): """Build and return a Logging Logger.""" if not sys.platform.startswith("win") and sys.stderr.isatty(): def add_color_emit_ansi(fn): """Add methods we need to the class.""" def new(*args): """Method overload.""" if len(args) == 2: new_args = (args[0], copy(args[1])) else: new_args = (args[0], copy(args[1]), args[2:]) if hasattr(args[0], 'baseFilename'): return fn(*args) levelno = new_args[1].levelno if levelno >= 50: color = '\x1b[31;5;7m\n ' # blinking red with black elif levelno >= 40: color = '\x1b[31m' # red elif levelno >= 30: color = '\x1b[33m' # yellow elif levelno >= 20: color = '\x1b[32m' # green elif levelno >= 10: color = '\x1b[35m' # pink else: color = '\x1b[0m' # normal try: new_args[1].msg = color + str(new_args[1].msg) + ' \x1b[0m' except Exception as reason: print(reason) # Do not use log here. return fn(*new_args) return new log.StreamHandler.emit = add_color_emit_ansi(log.StreamHandler.emit) log_file = os.path.join(gettempdir(), str(name).lower().strip() + ".log") log.basicConfig(level=-1, filemode="w", filename=log_file) log.getLogger().addHandler(log.StreamHandler(sys.stderr)) adrs = "/dev/log" if sys.platform.startswith("lin") else "/var/run/syslog" try: handler = log.handlers.SysLogHandler(address=adrs) except Exception: log.debug("Unix SysLog Server not found, ignored Logging to SysLog.") else: log.getLogger().addHandler(handler) log.debug("Logger created with Log file at: {0}.".format(log_file)) return log def check_for_updates(): """Method to check for updates from Git repo versus this version.""" try: last_version = str(request.urlopen(__source__).read().decode("utf8")) this_version = str(open(__file__).read()) except Exception: log_exception() else: if this_version != last_version: msg = "Theres a new Version!, update the App from: " + __source__ log.warning(msg) else: msg = "No new updates!, You have the latest version of this app." log.info(msg) return msg def about_python(): """Open Python official homepage.""" return open_new_tab('https://python.org') def about_self(): """Open this App homepage.""" return open_new_tab(__url__) def view_code(): """Open this App local Python source code.""" return open_new_tab(__file__) def report_bug(): """Open this App Bug Tracker.""" return open_new_tab(__url__ + "/issues/new") ############################################################################### def main(): """Main Loop.""" make_logger("octopussh") log.info(__doc__ + __version__) signal.signal(signal.SIGINT, signal.SIG_DFL) # CTRL+C work to quit app application = QApplication(sys.argv) application.setApplicationName("octopussh") application.setOrganizationName("octopussh") application.setOrganizationDomain("octopussh") application.setWindowIcon(QIcon.fromTheme("preferences-system")) mainwindow = MainWindow() mainwindow.show() sys.exit(application.exec_()) if __name__ in '__main__': main()