bashhub 3.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bashhub/__init__.py ADDED
File without changes
bashhub/bashhub.py ADDED
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/python
2
+ from time import *
3
+ import click
4
+ import traceback
5
+ import dateutil.parser
6
+ import sys
7
+ import os
8
+ import io
9
+
10
+ from .model import CommandForm
11
+ from . import rest_client
12
+ from . import bashhub_setup
13
+ from . import bashhub_globals
14
+ from .bashhub_globals import BH_FILTER, BH_HOME, BH_SAVE_COMMANDS
15
+ from .bashhub_globals import write_to_config_file
16
+ from .version import version_str
17
+ import shutil
18
+ import requests
19
+ import subprocess
20
+ from . import shell_utils
21
+ import re
22
+ from .view.status import *
23
+
24
+
25
+
26
+ def print_version(ctx, param, value):
27
+ if not value or ctx.resilient_parsing:
28
+ return
29
+ click.echo(version_str)
30
+ ctx.exit()
31
+
32
+
33
+ CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
34
+
35
+
36
+ @click.group(context_settings=CONTEXT_SETTINGS)
37
+ @click.option('-V',
38
+ '--version',
39
+ default=False,
40
+ is_flag=True,
41
+ callback=print_version,
42
+ help='Display version',
43
+ expose_value=False,
44
+ is_eager=True)
45
+ def bashhub():
46
+ """Bashhub command line client"""
47
+ pass
48
+
49
+
50
+ @bashhub.command()
51
+ def version():
52
+ """Display version"""
53
+ click.echo(version_str)
54
+
55
+
56
+ @bashhub.command()
57
+ @click.option("-g",
58
+ "--global",
59
+ "is_global",
60
+ default=False,
61
+ is_flag=True,
62
+ help="Turn off saving commands for all sessions.")
63
+ def off(is_global):
64
+ """Turn off saving commands to Bashhub. Applies for this current session."""
65
+ if is_global:
66
+ write_to_config_file('save_commands', 'False')
67
+ else:
68
+ f = io.open(BH_HOME + '/script.bh', 'w+', encoding='utf-8')
69
+ print(str("export BH_SAVE_COMMANDS='False'"), file=f)
70
+
71
+
72
+ @bashhub.command()
73
+ @click.option('-l',
74
+ "--local",
75
+ help="Turn on saving commands for only this session.",
76
+ is_flag=True)
77
+ def on(local):
78
+ """Turn on saving commands to Bashhub. Applies globally."""
79
+ f = io.open(BH_HOME + '/script.bh', 'w+', encoding='utf-8')
80
+
81
+ if local:
82
+ print(str("export BH_SAVE_COMMANDS='True'"), file=f)
83
+ else:
84
+ print(str("unset BH_SAVE_COMMANDS"), file=f)
85
+ write_to_config_file('save_commands', 'True')
86
+
87
+
88
+ @bashhub.command()
89
+ @click.argument('command', type=str)
90
+ @click.argument('path', type=click.Path(exists=True))
91
+ @click.argument('pid', type=int)
92
+ @click.argument('process_start_time', type=int)
93
+ @click.argument('exit_status', type=int)
94
+ def save(command, path, pid, process_start_time, exit_status):
95
+ """Save a command to Bashhub"""
96
+ pid_start_time = unix_time_to_epoc_millis(process_start_time)
97
+ command = command.strip()
98
+
99
+ # Check if we have commands saving turned on
100
+ if not bashhub_globals.BH_SAVE_COMMANDS:
101
+ return
102
+
103
+ # Check if we should ignore this command.
104
+ if "#ignore" in command:
105
+ return
106
+
107
+ # Check if we should filter this command.
108
+ bh_filter = bashhub_globals.BH_FILTER
109
+ if bh_filter and re.findall(bh_filter, command):
110
+ return
111
+
112
+ # Check that we have an auth token.
113
+ if bashhub_globals.BH_AUTH() == "":
114
+ print("No auth token found. Run 'bashhub setup' to login.")
115
+ return
116
+
117
+ command = CommandForm(command, path, exit_status, pid, pid_start_time)
118
+ rest_client.save_command(command)
119
+
120
+
121
+ @bashhub.command()
122
+ def setup():
123
+ """Run Bashhub user and system setup"""
124
+ bashhub_setup.main()
125
+
126
+
127
+ @bashhub.command()
128
+ def status():
129
+ """Stats for this session and user"""
130
+ # Get our user and session information from our context
131
+ (ppid, start_time) = shell_utils.get_session_information()
132
+ status_view = rest_client.get_status_view(ppid, start_time)
133
+ if status_view:
134
+ click.echo(build_status_view(status_view))
135
+
136
+
137
+ @bashhub.command()
138
+ @click.pass_context
139
+ def help(ctx):
140
+ """Show this message and exit"""
141
+ click.echo(ctx.parent.get_help())
142
+
143
+ # Dynamic help text containing the BH_FILTER variable.
144
+ filtered_text = "BH_FILTER={0}".format(
145
+ BH_FILTER) if BH_FILTER else "BH_FILTER \
146
+ is unset."
147
+
148
+ filter_help_text = """Check if a command is filtered from bashhub. Filtering
149
+ is configured via a regex exported as BH_FILTER.
150
+ \n
151
+ {0}""".format(filtered_text)
152
+
153
+
154
+ @bashhub.command(help=filter_help_text)
155
+ @click.argument('command', type=str)
156
+ @click.option('-r',
157
+ '--regex',
158
+ default=BH_FILTER,
159
+ help='Regex to filter against')
160
+ def filter(command, regex):
161
+
162
+ # Check if the regex we receive is valid
163
+ if not bashhub_globals.is_valid_regex(regex):
164
+ click.secho("Regex {0} is invalid".format(regex), fg='red')
165
+ return
166
+
167
+ v = re.findall(regex, command)
168
+ click.echo(filtered_text)
169
+ if v and regex:
170
+ matched = [str(s) for s in set(v)]
171
+ output = click.style("{0} \nIs Filtered. Matched ".format(command),
172
+ fg='yellow') + click.style(
173
+ str(matched), fg='red')
174
+ click.echo(output)
175
+ else:
176
+ click.echo("{0} \nIs Unfiltered".format(command))
177
+
178
+
179
+ @bashhub.command()
180
+ @click.argument('version', type=str, default='')
181
+ def update(version):
182
+ """Update your Bashhub installation"""
183
+
184
+ if version != '':
185
+ github = "https://github.com/rcaloras/bashhub-client/archive/{0}.tar.gz".format(
186
+ version)
187
+ response = requests.get(github)
188
+ if response.status_code != 200:
189
+ click.echo("Invalid version number {0}".format(version))
190
+ sys.exit(1)
191
+
192
+ query_param = '?version={0}'.format(version) if version else ''
193
+ url = 'https://bashhub.com/setup' + query_param
194
+ response = requests.get(url, stream=True)
195
+ filename = 'update-bashhub.sh'
196
+ with open(filename, 'wb') as out_file:
197
+ shutil.copyfileobj(response.raw, out_file)
198
+
199
+ subprocess.call(["bash", "-e", filename, version])
200
+ os.remove(filename)
201
+
202
+
203
+ @bashhub.group()
204
+ def util():
205
+ """Misc utils used by Bashhub"""
206
+ pass
207
+
208
+
209
+ @util.command()
210
+ def update_system_info():
211
+ """Updates system info for Bashhub"""
212
+ result = bashhub_setup.update_system_info()
213
+ # Exit code based on if our update call was successful
214
+ sys.exit(0) if result != None else sys.exit(1)
215
+
216
+
217
+ @util.command()
218
+ @click.argument('date_string', type=str)
219
+ def parsedate(date_string):
220
+ """date string to seconds since the unix epoch"""
221
+ try:
222
+ date = dateutil.parser.parse(date_string)
223
+ unix_time = int(mktime(date.timetuple()))
224
+ click.echo(unix_time)
225
+ except Exception as e:
226
+ # Should really log an error here
227
+ click.echo(0)
228
+
229
+
230
+ def unix_time_to_epoc_millis(unix_time):
231
+ return int(unix_time) * 1000
232
+
233
+
234
+ def main():
235
+ try:
236
+ bashhub()
237
+ except Exception as e:
238
+ formatted = traceback.format_exc(e)
239
+ click.echo("Oops, looks like an exception occured: " + str(e))
240
+ sys.exit(1)
@@ -0,0 +1,83 @@
1
+ """
2
+ This file should be used for declaring any global variables that need to be
3
+ pulled in from environment variables or are just used across multiple files.
4
+ """
5
+
6
+ import os
7
+ import re
8
+ import time
9
+ import stat
10
+
11
+ import configparser
12
+ from configparser import NoSectionError, NoOptionError
13
+
14
+ # Current time in milleseconds to use across app.
15
+ current_milli_time = lambda: int(round(time.time() * 1000))
16
+
17
+ BH_HOME = '~/.bashhub' if 'HOME' not in list(os.environ.keys()) \
18
+ else os.environ['HOME'] + '/.bashhub'
19
+
20
+
21
+ def write_to_config_file(section, value):
22
+ exists = os.path.exists(BH_HOME)
23
+ file_path = BH_HOME + '/config'
24
+ permissions = stat.S_IRUSR | stat.S_IWUSR
25
+ if exists:
26
+ config = configparser.ConfigParser()
27
+ config.read(BH_HOME + '/config')
28
+ # Add our section if it doesn't exist
29
+ if not config.has_section("bashhub"):
30
+ config.add_section("bashhub")
31
+
32
+ config.set("bashhub", section, value)
33
+ with open(file_path, 'w') as config_file:
34
+ config.write(config_file)
35
+ os.chmod(file_path, permissions)
36
+ return True
37
+ else:
38
+ print("Couldn't find bashhub home directory. Sorry.")
39
+ return False
40
+
41
+
42
+ def get_from_config(key, default=''):
43
+ try:
44
+ config = configparser.ConfigParser()
45
+ config.read(BH_HOME + '/config')
46
+ return config.get('bashhub', key)
47
+ except NoSectionError as error:
48
+ return default
49
+ except NoOptionError as error:
50
+ return default
51
+
52
+ # Optional environment variable to configure for development
53
+ # export BH_URL='http://localhost:9000'
54
+ BH_URL = os.getenv('BH_URL', get_from_config('url', 'https://bashhub.com'))
55
+
56
+ BH_SAVE_COMMANDS = os.getenv('BH_SAVE_COMMANDS', \
57
+ get_from_config('save_commands')).lower() in ('true', 'yes', 't', 'on', '')
58
+
59
+ BH_SYSTEM_NAME = get_from_config("system_name")
60
+
61
+ # Check if debug mode is enabled
62
+ BH_DEBUG = os.getenv('BH_DEBUG', get_from_config("debug"))
63
+
64
+
65
+ # Get our token from the environment if one is present
66
+ # otherwise retrieve it from our config. Needs to
67
+ # be a function since we may change our token during setup
68
+ def BH_AUTH():
69
+ return os.getenv('BH_ACCESS_TOKEN', get_from_config("access_token"))
70
+
71
+
72
+ def is_valid_regex(regex):
73
+ try:
74
+ re.compile(regex)
75
+ return True
76
+ except re.error:
77
+ return False
78
+
79
+ def get_bh_filter():
80
+ filter = os.getenv('BH_FILTER', get_from_config('filter'))
81
+ return filter if is_valid_regex(filter) else '__invalid__'
82
+
83
+ BH_FILTER = get_bh_filter()
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/python
2
+ from time import *
3
+ import jsonpickle
4
+ import json
5
+ import sys
6
+ import requests
7
+ import getpass
8
+ import traceback
9
+ import uuid
10
+ import stat
11
+ import socket
12
+ from . import rest_client
13
+ from .version import __version__
14
+ from .model import *
15
+ from .bashhub_globals import *
16
+ import requests
17
+ from requests import ConnectionError
18
+ from requests import HTTPError
19
+ import collections
20
+ import configparser
21
+
22
+
23
+ def query_yes_no(question, default="yes"):
24
+ """Ask a yes/no question via input() and return their answer.
25
+
26
+ "question" is a string that is presented to the user.
27
+ "default" is the presumed answer if the user just hits <Enter>.
28
+ It must be "yes" (the default), "no" or None (meaning
29
+ an answer is required of the user).
30
+
31
+ The "answer" return value is one of "yes" or "no".
32
+ """
33
+ valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
34
+ if default == None:
35
+ prompt = " [y/n] "
36
+ elif default == "yes":
37
+ prompt = " [Y/n] "
38
+ elif default == "no":
39
+ prompt = " [y/N] "
40
+ else:
41
+ raise ValueError("invalid default answer: '%s'" % default)
42
+
43
+ while True:
44
+ sys.stdout.write(question + prompt)
45
+ choice = input().lower()
46
+ if default is not None and choice == '':
47
+ return valid[default]
48
+ elif choice in valid:
49
+ return valid[choice]
50
+ else:
51
+ sys.stdout.write("Please respond with 'yes' or 'no' "
52
+ "(or 'y' or 'n').\n")
53
+
54
+
55
+ def get_new_user_information():
56
+ email = input("What's your email? ")
57
+ username = input("What username would you like? ")
58
+ password = getpass.getpass("What password? ")
59
+ print("\nEmail: " + email + " Username: " + username)
60
+ all_good = query_yes_no("Are these correct?")
61
+
62
+ if all_good:
63
+ return RegisterUser(email, username, password)
64
+ else:
65
+ return get_new_user_information()
66
+
67
+
68
+ def get_user_information_and_login(username=None, password=None, attempts=0):
69
+ if attempts == 4:
70
+ print("Too many bad attempts.")
71
+ return None
72
+
73
+ # Only collect user information if we don't already have it
74
+ # i.e. if we didn't just register a new user.
75
+ if username == None and password == None:
76
+ print("Please enter your bashhub credentials")
77
+ username = input("Username: ")
78
+ password = getpass.getpass("Password: ")
79
+
80
+ # login once we have all of our information
81
+ access_token = rest_client.login_user(LoginForm(username, password))
82
+
83
+ # Package our result to include our credentials to later login our system.
84
+ if access_token:
85
+ result = (username, password, access_token)
86
+ else:
87
+ result = get_user_information_and_login(attempts=attempts + 1) or (
88
+ None, None, None)
89
+
90
+ return result
91
+
92
+
93
+ def get_mac_address():
94
+ """Get the mac address for our system as a fingerprint. If we can't
95
+ get the mac, use the hash of our hostname as a subtitute"""
96
+
97
+ mac = uuid.getnode()
98
+ # check if getnode fails
99
+ if (mac >> 40) & 1:
100
+ hostname = socket.gethostname()
101
+ print("warning: cannot find MAC. Using hostname (%s) to identify system" % hostname)
102
+ mac = hostname
103
+ else:
104
+ mac = str(mac)
105
+ return mac
106
+
107
+
108
+ # Update our hostname incase it changed.
109
+ def update_system_info():
110
+ mac = get_mac_address()
111
+ hostname = socket.gethostname()
112
+ patch = SystemPatch(hostname=hostname, client_version=__version__)
113
+ return rest_client.patch_system(patch, mac)
114
+
115
+
116
+ def handle_system_information(username, password, attempts=0):
117
+
118
+ mac = get_mac_address()
119
+ system = rest_client.get_system_information(mac)
120
+ system_name = None
121
+ # Register a new System if this one isn't recognized
122
+ if system is None:
123
+ hostname = socket.gethostname()
124
+ name_input = input("What do you want to call this system? " +
125
+ "For example Home, File Server, ect. [%s]: " %
126
+ hostname)
127
+
128
+ name = name_input or hostname
129
+ system_name = rest_client.register_system(RegisterSystem(
130
+ name, mac, hostname, __version__))
131
+ if system_name:
132
+ print("Registered a new system " + name)
133
+ else:
134
+ if attempts < 3:
135
+ print("Looks like registering your system failed. Lets retry.")
136
+ return handle_system_information(username, password, attempts + 1)
137
+ else:
138
+ return (None, None)
139
+
140
+ # Login with this new system
141
+ access_token = rest_client.login_user(LoginForm(username, password, mac))
142
+
143
+ if access_token is None:
144
+ print("Failed to login with system.")
145
+ return (None, None)
146
+
147
+ # If this system is already registered
148
+ if system is not None:
149
+ system_name = system.name
150
+ print("Welcome back! Looks like this box is already registered as " +
151
+ system.name + ".")
152
+
153
+ return (access_token, system_name)
154
+
155
+
156
+ def main():
157
+ try:
158
+
159
+ ascii_art = r"""
160
+ ____ _ _ _
161
+ | _ \ | | | | | |
162
+ | |_) | __ _ ___| |__ | |__ _ _| |__ ___ ___ _ __ ___
163
+ | _ < / _` / __| '_ \| '_ \| | | | '_ \ / __/ _ \| '_ ` _ \
164
+ | |_) | (_| \__ \ | | | | | | |_| | |_) | (_| (_) | | | | | |
165
+ |____/ \__,_|___/_| |_|_| |_|\__,_|_.__(_)___\___/|_| |_| |_|
166
+
167
+ """
168
+
169
+ print(ascii_art)
170
+ print("Welcome to bashhub setup!")
171
+ is_new_user = query_yes_no("Are you a new user?")
172
+
173
+ # Initialize variaous Credentials for logging in.
174
+ username = None
175
+ password = None
176
+ access_token = None
177
+
178
+ # If this is a new user walk them through the registration flow
179
+ if is_new_user:
180
+ register_user = get_new_user_information()
181
+ register_result = rest_client.register_user(register_user)
182
+ if register_result:
183
+ print("Registered new user {0}\n".format(
184
+ register_user.username))
185
+ # Set our credentials to login later
186
+ username = register_user.username
187
+ password = register_user.password
188
+ else:
189
+ print("Sorry, registering a new user failed.")
190
+ print("You can rerun setup using 'bashhub setup' in a new "
191
+ "terminal window.\n")
192
+ sys.exit(0)
193
+
194
+ (username, password, access_token) = get_user_information_and_login(
195
+ username, password)
196
+ if access_token == None:
197
+ print("\nSorry looks like logging in failed.")
198
+ print("If you forgot your password please reset it. "
199
+ "https://bashhub.com/password-reset")
200
+ print("You can rerun setup using 'bashhub setup' in a new "
201
+ "terminal window.\n")
202
+ sys.exit(0)
203
+
204
+ # write out our user scoped access token
205
+ config_write_result = write_to_config_file("access_token",
206
+ access_token)
207
+ if not config_write_result:
208
+ print("Writing your config file failed.")
209
+ sys.exit(1)
210
+
211
+ (access_token, system_name) = handle_system_information(username,
212
+ password)
213
+
214
+ if access_token == None:
215
+ print("Sorry looks like getting your info failed.\
216
+ Exiting...")
217
+ sys.exit(0)
218
+
219
+ # write out our system scoped token and the system name
220
+ write_to_config_file("access_token", access_token)
221
+ write_to_config_file("system_name", system_name)
222
+ update_system_info()
223
+
224
+ sys.exit(0)
225
+
226
+ except Exception as err:
227
+ sys.stderr.write('Setup Error:\n%s\n' % str(err))
228
+ traceback.print_exc()
229
+ sys.exit(1)
230
+ except KeyboardInterrupt:
231
+ # To allow Ctrl+C (^C). Print a new line to drop the prompt.
232
+ print("")
233
+ sys.exit()
234
+
235
+
236
+ if __name__ == "__main__":
237
+ main()
bashhub/bh.py ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/python
2
+
3
+ import click
4
+ import sys
5
+ import io
6
+ import os
7
+ import traceback
8
+ import datetime
9
+ from .bashhub_globals import *
10
+ from . import rest_client
11
+ from .i_search import InteractiveSearch
12
+ from .version import version_str
13
+
14
+ @click.command()
15
+ @click.argument('query', type=str, default='')
16
+ @click.option('-n', '--number', default=None, help='Limit the number of previous commands. Default is 100.', type=int)
17
+ @click.option("-ses",
18
+ "--session",
19
+ help="Filter by specific session id. Default is None.",
20
+ default=None,
21
+ type=str)
22
+ @click.option("-d",
23
+ "--directory",
24
+ help="Search for commands within this directory.",
25
+ default=False,
26
+ is_flag=True)
27
+ @click.option("-sys",
28
+ "--system",
29
+ help="Search for commands created on this system.",
30
+ default=False,
31
+ is_flag=True)
32
+ @click.option("-i",
33
+ "--interactive",
34
+ help="Use interactive search. Allows you to select commands to run.",
35
+ default=False,
36
+ is_flag=True)
37
+ @click.option("-dups",
38
+ "--duplicates",
39
+ help="Include duplicates",
40
+ default=False,
41
+ is_flag=True)
42
+ @click.option("-t",
43
+ "--timestamps",
44
+ help="Include timestamps",
45
+ default=False,
46
+ is_flag=True)
47
+ @click.option("-V",
48
+ "--version",
49
+ help="Print version information",
50
+ default=False,
51
+ is_flag=True)
52
+ def bh(query, number, session, directory, system, interactive, duplicates, timestamps, version):
53
+ """Bashhhub Search
54
+
55
+ QUERY - Like string to search for
56
+ """
57
+ limit = number
58
+ system_name = BH_SYSTEM_NAME if system else None
59
+ path = os.getcwd() if directory else None
60
+ session_id = session
61
+
62
+ # By default show unique on the client.
63
+ unique = not duplicates
64
+
65
+ use_timestamps = timestamps
66
+
67
+ # If we're interactive, make sure we have a query
68
+ if interactive and query == '':
69
+ query = input("(bashhub-i-search): ")
70
+
71
+ if version and query == '':
72
+ print(version_str)
73
+ sys.exit()
74
+
75
+ # Call our rest api to search for commands
76
+ commands = rest_client.search(limit=limit,
77
+ path=path,
78
+ query=query,
79
+ system_name=system_name,
80
+ unique=unique,
81
+ session_id=session_id)
82
+
83
+ if interactive:
84
+ run_interactive(commands)
85
+ else:
86
+ print_commands(commands, use_timestamps)
87
+
88
+
89
+ def print_commands(commands, use_timestamps):
90
+ for command in reversed(commands):
91
+ if use_timestamps:
92
+ timestamp = unix_milliseconds_timestamp_to_datetime(command.created)
93
+ print('%s\t%s' % (timestamp, command.command))
94
+ else:
95
+ print(command.command)
96
+
97
+
98
+ def run_interactive(commands):
99
+ i_search = InteractiveSearch(commands, rest_client)
100
+ i_search.run()
101
+ # numpy bullshit since it doesn't return anything.
102
+ # Consider submitting a patchset for it.
103
+ command = i_search.return_value
104
+ if command is not None:
105
+ f = io.open(BH_HOME + '/response.bh', 'w+', encoding='utf-8')
106
+ print(command.command, file=f)
107
+
108
+ def unix_milliseconds_timestamp_to_datetime(timestamp):
109
+ return datetime.datetime.fromtimestamp(int(timestamp) / 1000) \
110
+ .strftime('%Y-%m-%d %H:%M:%S')
111
+
112
+ def main():
113
+ try:
114
+ bh()
115
+ except Exception as e:
116
+ if BH_DEBUG:
117
+ traceback.print_exc()
118
+ click.echo("Oops, look like an exception occured: " + str(e))
119
+ sys.exit(1)
120
+ except KeyboardInterrupt:
121
+ # To allow Ctrl+C (^C). Print a new line to drop the prompt.
122
+ click.echo()
123
+ sys.exit()
124
+
125
+ main()