diff --git a/commit.sh b/commit.sh index 7d17715..3256f25 100755 --- a/commit.sh +++ b/commit.sh @@ -1,4 +1,4 @@ #!/bin/sh -git add -f extra lib/sshyp.py lib/sshync.py port-jobs share LICENSE README.md package.sh commit.sh .gitignore +git add -f extra lib/sshyp.py lib/sshync.py lib/stweak.py port-jobs share LICENSE README.md package.sh commit.sh .gitignore git commit -m "$1" git push diff --git a/lib/sshync.py b/lib/sshync.py old mode 100755 new mode 100644 diff --git a/lib/sshyp.py b/lib/sshyp.py index 036298b..c65e920 100755 --- a/lib/sshyp.py +++ b/lib/sshyp.py @@ -3,8 +3,8 @@ from os import chmod, environ, listdir, remove, walk from os.path import exists, expanduser, isdir, isfile, realpath from pathlib import Path from random import randint -from shutil import get_terminal_size, move, rmtree -from sshync import delete as offline_delete, run_profile, make_profile, get_profile +from shutil import move, rmtree +from sshync import delete as offline_delete, run_profile, get_profile from subprocess import CalledProcessError, DEVNULL, PIPE, run from sys import argv, exit as s_exit # PORT START UNAME-IMPORT @@ -14,8 +14,10 @@ home = expanduser("~") # UTILITY FUNCTIONS + # generates and prints full entry list def entry_list_gen(_directory=f"{home}/.local/share/sshyp/"): + from shutil import get_terminal_size from textwrap import fill _ran = False print("\nfor a list of usable commands, run 'sshyp help'\n\n\u001b[38;5;0;48;5;15msshyp entries:\u001b[0m\n") @@ -214,206 +216,7 @@ def copy_id_check(_port, _username_ssh, _ip, _client_device_id): return False -# creates a radio selection between the provided options -def settings_radio(_stdscr, _options, _pretext): - curses.curs_set(0) - _selected = 0 - while True: - _stdscr.clear() - _stdscr.addstr(0, 0, _pretext, curses.A_BOLD) - for _i, _option in enumerate(_options): - _y = _i + 2 - if _i == _selected: - _stdscr.addstr(_y, 0, "[*] " + _option, curses.A_REVERSE) - else: - _stdscr.addstr(_y, 0, "[ ] " + _option) - _stdscr.refresh() - _key = _stdscr.getch() - # update _selected based on user input - if _key == curses.KEY_UP: - _selected = (_selected - 1) % len(_options) - elif _key == curses.KEY_DOWN: - _selected = (_selected + 1) % len(_options) - elif _key == ord('\n'): - break - _stdscr.refresh() - curses.curs_set(1) - return _selected - - -# creates a text-box input -def settings_text(_stdscr, _pretext): - _stdscr.clear() - _stdscr.addstr(0, 0, _pretext) - _term_columns = get_terminal_size()[0] - _editwin = curses.newwin(1, _term_columns-2, 3, 1) - rectangle(_stdscr, 2, 0, 4, _term_columns-1) - _stdscr.refresh() - _box = Textbox(_editwin) - # let the user edit until ctrl+g/enter is struck - _box.edit() - # return resulting contents - return _box.gather().strip() - - -# cleanly terminates curses -def settings_terminate(_stdscr): - curses.nocbreak() - _stdscr.keypad(False) # TODO Needed?? - curses.echo() - curses.endwin() - - # ARGUMENT-SPECIFIC FUNCTIONS -# runs configuration wizard -def settings(): - # set to avoid PEP8 warnings - _sshyp_data = None - # config directory creation - Path(f"{home}/.config/sshyp/devices").mkdir(mode=0o700, parents=True, exist_ok=True) - - # temporary file symlink creation - if not exists(f"{home}/.config/sshyp/tmp"): - from os import symlink - # PORT START UNAME-TMP - if uname()[0] in ('Haiku', 'FreeBSD', 'Darwin'): - symlink('/tmp', f"{home}/.config/sshyp/tmp") - elif exists('/data/data/com.termux'): - symlink('/data/data/com.termux/files/usr/tmp', f"{home}/.config/sshyp/tmp") - else: - symlink('/dev/shm', f"{home}/.config/sshyp/tmp") - # PORT END UNAME-TMP - - # curses initialization - _stdscr = curses.initscr() - curses.noecho() - curses.cbreak() - _stdscr.keypad(True) - - # curses menu tree - try: - # device+sync type selection - # PORT START TWEAK-DEVTYPE - _install_type = settings_radio(_stdscr, ('server', 'client (ssh-synchronized)', 'client (offline)'), - 'device + sync type configuration') - if _install_type == 0: - _sshyp_data = ['server'] - Path(f"{home}/.config/sshyp/deleted").mkdir(mode=0o700, exist_ok=True) - Path(f"{home}/.config/sshyp/whitelist").mkdir(mode=0o700, exist_ok=True) - settings_terminate(_stdscr) - print(f"\nmake sure the ssh service is running and properly configured") - else: - _offline_mode = False - if _install_type == 2: - _offline_mode = True - _sshyp_data = ['client'] - Path(f"{home}/.local/share/sshyp").mkdir(mode=0o700, parents=True, exist_ok=True) - # PORT END TWEAK-DEVTYPE - - # gpg key selection - _uid_list = [_item for _item in run(['gpg', '-k', '--with-colons'], - stdout=PIPE, text=True).stdout.splitlines() if _item.startswith('uid')] - _clean_uid_list = [] - for _uid in _uid_list: - _clean_uid_list.append(sub(r':+', ':', _uid).split(':')[4]) - _clean_uid_list.append('auto-generate') - _gpg_id_sel = settings_radio(_stdscr, _clean_uid_list, 'gpg key selection') - _gpg_id = _clean_uid_list[_gpg_id_sel] - if _gpg_id == 'auto-generate': - print('\na unique gpg key is being generated for you...') - if not isfile(f"{home}/.config/sshyp/gpg-gen"): - open(f"{home}/.config/sshyp/gpg-gen", 'w').writelines([ - 'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n', - 'Name-Comment: gpg-sshyp\n', 'Name-Email: https://github.com/rwinkhart/sshyp\n', - 'Expire-Date: 0']) - run(['gpg', '--batch', '--generate-key', f"{home}/.config/sshyp/gpg-gen"]) - remove(f"{home}/.config/sshyp/gpg-gen") - _sshyp_data.append(run(['gpg', '-k'], stdout=PIPE, text=True).stdout.splitlines()[-3].strip()) - else: - _sshyp_data.append(_gpg_id) - - # text editor configuration - _sshyp_data.append(settings_text(_stdscr, 'enter the name of your preferred text editor:\n\n\n\n\n' - '(ctrl+g/enter to confirm)\n\nexample input: vim')) - # lock file generation - if isfile(f"{home}/.config/sshyp/lock.gpg"): - remove(f"{home}/.config/sshyp/lock.gpg") - open(f"{home}/.config/sshyp/lock", 'w') - run(['gpg', '-qr', str(_sshyp_data[1]), '-e', f"{home}/.config/sshyp/lock"]) - remove(f"{home}/.config/sshyp/lock") - - # ssh configuration - if not _offline_mode: - _uiport = settings_text(_stdscr, 'enter the username, ip, and ssh port of your sshyp server:\n\n\n\n\n(' - 'ctrl+g/enter to confirm)\n\nexample inputs:\n\n ipv4: user@10.10.10.' - '10:22\n ipv6: user@[2000:2000:2000:2000:2000:2000:2000:2000]:22\n ' - 'domain: user@mydomain.com:22').lstrip('[').replace(']', '') - _uiport_split = _uiport.split('@') - _username_ssh = _uiport_split[0] - _iport = _uiport_split[1].rsplit(':', 1) - - # sshync profile generation - make_profile(f"{home}/.config/sshyp/sshyp.sshync", - f"{home}/.local/share/sshyp/", f"/home/{_username_ssh}/.local/share/sshyp/", - f"{home}/.ssh/sshyp", _iport[0], _iport[1], _username_ssh) - - # device id configuration - # remove existing device id - for _id in listdir(f"{home}/.config/sshyp/devices"): - remove(f"{home}/.config/sshyp/devices/{_id}") - _device_id_prefix = settings_text(_stdscr, 'name this device:\n\n\n\n\n(ctrl+g/enter to confirm)\n\n' - 'important:\u001b[0m this id \u001b[4;1mmust\u001b[0m be ' - 'unique amongst your client devices\n\nthis is used to keep ' - 'track of database syncing and quick-unlock permissions\n') - _device_id_suffix = string_gen('f', randint(24, 48)) - _device_id = _device_id_prefix + '-' + _device_id_suffix - open(f"{home}/.config/sshyp/devices/{_device_id}", 'w') - - # quick-unlock configuration - _quick_unlock_sel = settings_radio(_stdscr, ('yes', 'no'), 'enable quick-unlock?') - if _quick_unlock_sel == 0: - _sshyp_data.append('yes') - else: - _sshyp_data.append('no') - - settings_terminate(_stdscr) - - # test server connection and attempt to register device id - copy_id_check(_iport[1], _username_ssh, _iport[0], _device_id) - - else: - if isfile(f"{home}/.config/sshyp/sshyp.sshync"): - remove(f"{home}/.config/sshyp/sshyp.sshync") - settings_terminate(_stdscr) - - # PORT START CLIPTOOL - # check for clipboard tool and display warning if missing - if uname()[0] in ('Linux', 'FreeBSD'): - if 'WAYLAND_DISPLAY' in environ: - _display_server, _clipboard_tool, _clipboard_package = 'Wayland', 'wl-copy', 'wl-clipboard' - else: - _display_server, _clipboard_tool, _clipboard_package = 'X11', 'xclip', 'xclip' - from shutil import which - if which(_clipboard_tool) is None: - print(f'\n\u001b[38;5;9mwarning: you are using {_display_server} and "{_clipboard_tool}" is not ' - f'present - \ncopying entry fields will not function until ' - f'"{_clipboard_package}" is installed\u001b[0m') - # PORT END CLIPTOOL - - except KeyboardInterrupt: - settings_terminate(_stdscr) - - # write main config file (sshyp-data) - with open(f"{home}/.config/sshyp/sshyp-data", 'w') as _config_file: - _lines = 0 - for _item in _sshyp_data: - _lines += 1 - _config_file.write(str(_item) + '\n') - while _lines < 4: - _lines += 1 - _config_file.write('n') - print('\nconfiguration complete\n') - # prints help text based on argument def print_info(): @@ -945,10 +748,8 @@ if __name__ == "__main__": print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n') s_exit(1) else: - import curses - from curses.textpad import Textbox, rectangle - from re import sub - settings() + import stweak + stweak.settings() s_exit() # run function based on arguments diff --git a/lib/stweak.py b/lib/stweak.py new file mode 100644 index 0000000..b0d8d95 --- /dev/null +++ b/lib/stweak.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +from curses import curs_set, newwin, A_BOLD, A_REVERSE, KEY_UP, KEY_DOWN, noecho, nocbreak, endwin, initscr, cbreak +from curses.textpad import rectangle, Textbox +from os import environ, listdir, remove, symlink +from pathlib import Path +from random import randint +from re import sub +from shutil import get_terminal_size, which +from sshync import make_profile +from sshyp import copy_id_check, string_gen +from subprocess import run, PIPE +# PORT START UNAME-IMPORT +from os import uname +from os.path import exists, expanduser, isfile +# PORT END UNAME-IMPORT +home = expanduser("~") + + +# creates a radio selection between the provided options +def settings_radio(_stdscr, _options, _pretext): + curs_set(0) + _selected = 0 + while True: + _stdscr.clear() + _stdscr.addstr(0, 0, _pretext, A_BOLD) + for _i, _option in enumerate(_options): + _y = _i + 2 + if _i == _selected: + _stdscr.addstr(_y, 0, "[*] " + _option, A_REVERSE) + else: + _stdscr.addstr(_y, 0, "[ ] " + _option) + _stdscr.refresh() + _key = _stdscr.getch() + # update _selected based on user input + if _key == KEY_UP: + _selected = (_selected - 1) % len(_options) + elif _key == KEY_DOWN: + _selected = (_selected + 1) % len(_options) + elif _key == ord('\n'): + break + _stdscr.refresh() + curs_set(1) + return _selected + + +# creates a text-box input +def settings_text(_stdscr, _pretext): + _stdscr.clear() + _stdscr.addstr(0, 0, _pretext) + _term_columns = get_terminal_size()[0] + _editwin = newwin(1, _term_columns - 2, 3, 1) + rectangle(_stdscr, 2, 0, 4, _term_columns - 1) + _stdscr.refresh() + _box = Textbox(_editwin) + # let the user edit until ctrl+g/enter is struck + _box.edit() + # return resulting contents + return _box.gather().strip() + + +# cleanly terminates curses +def settings_terminate(_stdscr): + nocbreak() + _stdscr.keypad(False) # TODO Needed?? + noecho() + endwin() + + +# ARGUMENT-SPECIFIC FUNCTIONS +# runs configuration wizard +def settings(): + # set to avoid PEP8 warnings + _sshyp_data = None + # config directory creation + Path(f"{home}/.config/sshyp/devices").mkdir(mode=0o700, parents=True, exist_ok=True) + + # temporary file symlink creation + if not exists(f"{home}/.config/sshyp/tmp"): + # PORT START UNAME-TMP + if uname()[0] in ('Haiku', 'FreeBSD', 'Darwin'): + symlink('/tmp', f"{home}/.config/sshyp/tmp") + elif exists('/data/data/com.termux'): + symlink('/data/data/com.termux/files/usr/tmp', f"{home}/.config/sshyp/tmp") + else: + symlink('/dev/shm', f"{home}/.config/sshyp/tmp") + # PORT END UNAME-TMP + + # curses initialization + _stdscr = initscr() + noecho() + cbreak() + _stdscr.keypad(True) + + # curses menu tree + try: + # device+sync type selection + # PORT START TWEAK-DEVTYPE + _install_type = settings_radio(_stdscr, ('server', 'client (ssh-synchronized)', 'client (offline)'), + 'device + sync type configuration') + if _install_type == 0: + _sshyp_data = ['server'] + Path(f"{home}/.config/sshyp/deleted").mkdir(mode=0o700, exist_ok=True) + Path(f"{home}/.config/sshyp/whitelist").mkdir(mode=0o700, exist_ok=True) + settings_terminate(_stdscr) + print(f"\nmake sure the ssh service is running and properly configured") + else: + _offline_mode = False + if _install_type == 2: + _offline_mode = True + _sshyp_data = ['client'] + Path(f"{home}/.local/share/sshyp").mkdir(mode=0o700, parents=True, exist_ok=True) + # PORT END TWEAK-DEVTYPE + + # gpg key selection + _uid_list = [_item for _item in run(['gpg', '-k', '--with-colons'], + stdout=PIPE, text=True).stdout.splitlines() if _item.startswith('uid')] + _clean_uid_list = [] + for _uid in _uid_list: + _clean_uid_list.append(sub(r':+', ':', _uid).split(':')[4]) + _clean_uid_list.append('auto-generate') + _gpg_id_sel = settings_radio(_stdscr, _clean_uid_list, 'gpg key selection') + _gpg_id = _clean_uid_list[_gpg_id_sel] + if _gpg_id == 'auto-generate': + print('\na unique gpg key is being generated for you...') + if not isfile(f"{home}/.config/sshyp/gpg-gen"): + open(f"{home}/.config/sshyp/gpg-gen", 'w').writelines([ + 'Key-Type: 1\n', 'Key-Length: 4096\n', 'Key-Usage: sign encrypt\n', 'Name-Real: sshyp\n', + 'Name-Comment: gpg-sshyp\n', 'Name-Email: https://github.com/rwinkhart/sshyp\n', + 'Expire-Date: 0']) + run(['gpg', '--batch', '--generate-key', f"{home}/.config/sshyp/gpg-gen"]) + remove(f"{home}/.config/sshyp/gpg-gen") + _sshyp_data.append(run(['gpg', '-k'], stdout=PIPE, text=True).stdout.splitlines()[-3].strip()) + else: + _sshyp_data.append(_gpg_id) + + # text editor configuration + _sshyp_data.append(settings_text(_stdscr, 'enter the name of your preferred text editor:\n\n\n\n\n' + '(ctrl+g/enter to confirm)\n\nexample input: vim')) + # lock file generation + if isfile(f"{home}/.config/sshyp/lock.gpg"): + remove(f"{home}/.config/sshyp/lock.gpg") + open(f"{home}/.config/sshyp/lock", 'w') + run(['gpg', '-qr', str(_sshyp_data[1]), '-e', f"{home}/.config/sshyp/lock"]) + remove(f"{home}/.config/sshyp/lock") + + # ssh configuration + if not _offline_mode: + _uiport = settings_text(_stdscr, 'enter the username, ip, and ssh port of your sshyp server:\n\n\n\n\n(' + 'ctrl+g/enter to confirm)\n\nexample inputs:\n\n ipv4: user@10.10.10.' + '10:22\n ipv6: user@[2000:2000:2000:2000:2000:2000:2000:2000]:22\n ' + 'domain: user@mydomain.com:22').lstrip('[').replace(']', '') + _uiport_split = _uiport.split('@') + _username_ssh = _uiport_split[0] + _iport = _uiport_split[1].rsplit(':', 1) + + # sshync profile generation + make_profile(f"{home}/.config/sshyp/sshyp.sshync", + f"{home}/.local/share/sshyp/", f"/home/{_username_ssh}/.local/share/sshyp/", + f"{home}/.ssh/sshyp", _iport[0], _iport[1], _username_ssh) + + # device id configuration + # remove existing device id + for _id in listdir(f"{home}/.config/sshyp/devices"): + remove(f"{home}/.config/sshyp/devices/{_id}") + _device_id_prefix = settings_text(_stdscr, 'name this device:\n\n\n\n\n(ctrl+g/enter to confirm)\n\n' + 'important:\u001b[0m this id \u001b[4;1mmust\u001b[0m be ' + 'unique amongst your client devices\n\nthis is used to keep ' + 'track of database syncing and quick-unlock permissions\n') + _device_id_suffix = string_gen('f', randint(24, 48)) + _device_id = _device_id_prefix + '-' + _device_id_suffix + open(f"{home}/.config/sshyp/devices/{_device_id}", 'w') + + # quick-unlock configuration + _quick_unlock_sel = settings_radio(_stdscr, ('yes', 'no'), 'enable quick-unlock?') + if _quick_unlock_sel == 0: + _sshyp_data.append('yes') + else: + _sshyp_data.append('no') + + settings_terminate(_stdscr) + + # test server connection and attempt to register device id + copy_id_check(_iport[1], _username_ssh, _iport[0], _device_id) + + else: + if isfile(f"{home}/.config/sshyp/sshyp.sshync"): + remove(f"{home}/.config/sshyp/sshyp.sshync") + settings_terminate(_stdscr) + + # PORT START CLIPTOOL + # check for clipboard tool and display warning if missing + if uname()[0] in ('Linux', 'FreeBSD'): + if 'WAYLAND_DISPLAY' in environ: + _display_server, _clipboard_tool, _clipboard_package = 'Wayland', 'wl-copy', 'wl-clipboard' + else: + _display_server, _clipboard_tool, _clipboard_package = 'X11', 'xclip', 'xclip' + if which(_clipboard_tool) is None: + print(f'\n\u001b[38;5;9mwarning: you are using {_display_server} and "{_clipboard_tool}" is not ' + f'present - \ncopying entry fields will not function until ' + f'"{_clipboard_package}" is installed\u001b[0m') + # PORT END CLIPTOOL + + except KeyboardInterrupt: + settings_terminate(_stdscr) + + # write main config file (sshyp-data) + with open(f"{home}/.config/sshyp/sshyp-data", 'w') as _config_file: + _lines = 0 + for _item in _sshyp_data: + _lines += 1 + _config_file.write(str(_item) + '\n') + while _lines < 4: + _lines += 1 + _config_file.write('n') + print('\nconfiguration complete\n')