From 555a661e92338675713bf7d46ea64ae2283ded79 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 18 Sep 2018 16:14:46 -0600 Subject: [PATCH 01/20] Added connet_to_db() function to hw-diags.py * Uses new main.py variables * Only runs if non-quick drive test(s) are selected --- .bin/Scripts/functions/hw_diags.py | 32 ++++++++++++++++++++++++++---- .bin/Scripts/settings/main.py | 5 +++++ .linux_items/packages/live_add | 2 ++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 3b8bd354..cc10ae40 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -2,9 +2,14 @@ import json import time +import mysql.connector as mariadb from functions.common import * +# osTicket Database +db_connection = None +db_cursor = None + # STATIC VARIABLES ATTRIBUTES = { 'NVMe': { @@ -73,6 +78,17 @@ TESTS = { }, } +def connect_to_db(): + """Connect to osTicket database.""" + try: + db_connection = mariadb.connect( + user=DB_USER, password=DB_PASS, database=DB_NAME, host=DB_HOST) + db_cursor = db_connection.cursor() + except: + db_connection = None + db_cursor = None + raise + def generate_horizontal_graph(rates): """Generate two-line horizontal graph from rates, returns str.""" line_top = '' @@ -640,8 +656,10 @@ def run_nvme_smart(): def run_tests(tests): """Run selected hardware test(s).""" - print_log('Starting Hardware Diagnostics') - print_log('\nRunning tests: {}'.format(', '.join(tests))) + clear_screen() + print_standard('Starting Hardware Diagnostics') + print_standard(' ') + print_standard('Running tests: {}'.format(', '.join(tests))) # Enable selected tests for t in ['Prime95', 'NVMe/SMART', 'badblocks', 'iobenchmark']: TESTS[t]['Enabled'] = t in tests @@ -649,10 +667,17 @@ def run_tests(tests): # Initialize if TESTS['NVMe/SMART']['Enabled'] or TESTS['badblocks']['Enabled'] or TESTS['iobenchmark']['Enabled']: + if not TESTS['NVMe/SMART']['Quick']: + print_standard(' ') + try_and_print( + message='Connecting to osTicket database...', + function=connect_to_db, + width=40) print_standard(' ') - print_standard('Scanning disks...') scan_disks() update_progress() + pause() + exit_script() # Run mprime_aborted = False @@ -683,7 +708,6 @@ def run_tests(tests): def scan_disks(full_paths=False, only_path=None): """Scan for disks eligible for hardware testing.""" - clear_screen() # Get eligible disk list cmd = ['lsblk', '-J', '-O'] diff --git a/.bin/Scripts/settings/main.py b/.bin/Scripts/settings/main.py index c49da96e..4e6d4083 100644 --- a/.bin/Scripts/settings/main.py +++ b/.bin/Scripts/settings/main.py @@ -11,6 +11,11 @@ ARCHIVE_PASSWORD='Abracadabra' KIT_NAME_FULL='Wizard Kit' KIT_NAME_SHORT='WK' SUPPORT_MESSAGE='Please let 2Shirt know by opening an issue on GitHub' +# osTicket +DB_HOST='127.0.0.1' +DB_NAME='osticket' +DB_USER='wizardkit' +DB_PASS='Abracadabra' # Live Linux MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags ROOT_PASSWORD='Abracadabra' diff --git a/.linux_items/packages/live_add b/.linux_items/packages/live_add index ea784699..57782fe9 100644 --- a/.linux_items/packages/live_add +++ b/.linux_items/packages/live_add @@ -43,6 +43,7 @@ libinput linux-firmware lm_sensors lzip +mariadb-clients mdadm mediainfo mesa-demos @@ -62,6 +63,7 @@ p7zip papirus-icon-theme progsreiserfs python +python-mysql-connector python-psutil python-requests qemu-guest-agent From 58dead2382fecb82583c1be81a7c8e9bee0c4c23 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 18 Sep 2018 19:55:36 -0600 Subject: [PATCH 02/20] Added osTicket DB functions * osticket_needs_attention() * osticket_reply() * osticket_set_drive_result() --- .bin/Scripts/functions/hw_diags.py | 95 ++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index cc10ae40..cd6e91b4 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -6,9 +6,12 @@ import mysql.connector as mariadb from functions.common import * -# osTicket Database -db_connection = None -db_cursor = None +# Database connection +ost_db = { + 'Connection': None, + 'Cursor': None, + 'Errors': False + } # STATIC VARIABLES ATTRIBUTES = { @@ -56,6 +59,16 @@ IO_VARS = { '███▏', '███▎', '███▍', '███▌', '███▋', '███▊', '███▉', '████'), } +OST_STAFF_ID = '23' +OST_STAFF_NAME = 'Wizard Kit' +OST_SQL_SET_HOLD = "UPDATE `{db_name}`.`ost_ticket` SET `hold` = '{hold_type}' WHERE `ost_ticket`.`ticket_id` = {ticket_id};" +OST_SQL_SET_FLAG = "UPDATE `{db_name}`.`ost_ticket` SET `{flag}` = '{value}' WHERE `ost_ticket`.`ticket_id` = {ticket_id};" +OST_SQL_POST_REPLY = ("INSERT INTO `{db_name}`.`ost_ticket_response` (ticket_id, staff_id, staff_name, response, created) " + "VALUES ('{ticket_id}', '{staff_id}', '{staff_name}', '{response}', '{created}');") +OST_DRIVE_FLAG = 'zHDTune' +OST_DRIVE_PASSED = 1 +OST_DRIVE_FAILED = 2 +OST_NEEDS_ATTENTION = 4 TESTS = { 'Prime95': { 'Enabled': False, @@ -80,14 +93,10 @@ TESTS = { def connect_to_db(): """Connect to osTicket database.""" - try: - db_connection = mariadb.connect( - user=DB_USER, password=DB_PASS, database=DB_NAME, host=DB_HOST) - db_cursor = db_connection.cursor() - except: - db_connection = None - db_cursor = None - raise + ost_db['Connection'] = mariadb.connect( + user=DB_USER, password=DB_PASS, database=DB_NAME, host=DB_HOST) + ost_db['Cursor'] = ost_db['Connection'].cursor() + ost_db['Errors'] = False def generate_horizontal_graph(rates): """Generate two-line horizontal graph from rates, returns str.""" @@ -246,6 +255,70 @@ def menu_diags(*args): elif selection == 'Q': break +def osticket_needs_attention(ticket_id): + """Marks the ticket as "NEEDS ATTENTION" in osTicket.""" + if not ticket_id: + raise GenericError + if not ost_db['Cursor']: + # Skip section + return + + # Set command + sql_cmd = OST_SQL_SET_HOLD.format( + db_name=DB_NAME, + hold_type=OST_NEEDS_ATTENTION, + ticket_id=ticket_id) + + # Run command + try: + ost_db['Cursor'].execute(sql_cmd) + except: + sql_errors = True + +def osticket_reply(ticket_id, response): + """Post a reply to a ticket in osTicket.""" + if not ticket_id: + raise GenericError + if not ost_db['Cursor']: + # Skip section + return + + # Set command + sql_cmd = OST_SQL_POST_REPLY.format( + db_name=DB_NAME, + ticket_id=ticket_id, + staff_id=OST_STAFF_ID, + staff_name=OST_STAFF_NAME, + response=response, + created=time.strftime("%Y-%m-%d %H:%M:%S")) + + # Run command + try: + ost_db['Cursor'].execute(sql_cmd) + except: + sql_errors = True + +def osticket_set_drive_result(ticket_id, passed): + """Marks the pass/fail box for the drive(s) in osTicket.""" + if not ticket_id: + raise GenericError + if not ost_db['Cursor']: + # Skip section + return + + # Set command + sql_cmd = OST_SQL_SET_FLAG.format( + db_name=DB_NAME, + flag=OST_DRIVE_FLAG, + value=OST_DRIVE_PASSED if passed else OST_DRIVE_FAILED, + ticket_id=ticket_id) + + # Run command + try: + ost_db['Cursor'].execute(sql_cmd) + except: + sql_errors = True + def run_badblocks(): """Run a read-only test for all detected disks.""" aborted = False From 5edde45f0eb87a62ab543466ebd2c90b8b98ff10 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 18 Sep 2018 20:33:33 -0600 Subject: [PATCH 03/20] Get osTicket number and verify with name in ticket --- .bin/Scripts/functions/hw_diags.py | 65 +++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index cd6e91b4..089e51a7 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -137,6 +137,22 @@ def get_graph_step(rate, scale=16): break return step +def get_osticket_number(): + """Get ticket number and confirm with name from osTicket DB.""" + ticket_number = None + while ticket_number is None: + print_standard(' ') + _input = input('Enter ticket number: ') + if not re.match(r'^([0-9]+)$', _input): + continue + _name = osticket_get_ticket_name(_input) + if _name: + print_standard('You have selected ticket #{} ({})'.format( + _input, _name)) + if ask('Is this correct?'): + ticket_number = _input + return ticket_number + def get_read_rate(s): """Get read rate in bytes/s from dd progress output.""" real_rate = None @@ -226,10 +242,11 @@ def menu_diags(*args): if selection.isnumeric(): if diag_modes[int(selection)-1]['Name'] != 'Quick drive test': # Save log for non-quick tests - ticket_number = get_ticket_number() - global_vars['LogDir'] = '{}/Logs/{}'.format( + ticket_number = get_osticket_number() + global_vars['LogDir'] = '{}/Logs/{}_{}'.format( global_vars['Env']['HOME'], - ticket_number if ticket_number else global_vars['Date-Time']) + ticket_number, + global_vars['Date-Time']) os.makedirs(global_vars['LogDir'], exist_ok=True) global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format( global_vars['LogDir']) @@ -255,6 +272,28 @@ def menu_diags(*args): elif selection == 'Q': break +def osticket_get_ticket_name(ticket_id): + """Lookup ticket and return name as str.""" + ticket_name = 'Unknown' + if not ticket_id: + raise GenericError + if not ost_db['Cursor']: + # Skip section + return + + # Set command + sql_cmd = "SELECT name FROM `ost_ticket` WHERE `ticket_id` = {}".format( + ticket_id) + + # Run command + try: + ost_db['Cursor'].execute(sql_cmd) + for name in ost_db['Cursor']: + ticket_name = name[0] + return ticket_name + except: + ost_db['Errors'] = True + def osticket_needs_attention(ticket_id): """Marks the ticket as "NEEDS ATTENTION" in osTicket.""" if not ticket_id: @@ -273,7 +312,7 @@ def osticket_needs_attention(ticket_id): try: ost_db['Cursor'].execute(sql_cmd) except: - sql_errors = True + ost_db['Errors'] = True def osticket_reply(ticket_id, response): """Post a reply to a ticket in osTicket.""" @@ -296,7 +335,7 @@ def osticket_reply(ticket_id, response): try: ost_db['Cursor'].execute(sql_cmd) except: - sql_errors = True + ost_db['Errors'] = True def osticket_set_drive_result(ticket_id, passed): """Marks the pass/fail box for the drive(s) in osTicket.""" @@ -317,7 +356,7 @@ def osticket_set_drive_result(ticket_id, passed): try: ost_db['Cursor'].execute(sql_cmd) except: - sql_errors = True + ost_db['Errors'] = True def run_badblocks(): """Run a read-only test for all detected disks.""" @@ -739,18 +778,16 @@ def run_tests(tests): TESTS['NVMe/SMART']['Quick'] = 'Quick' in tests # Initialize + if not TESTS['NVMe/SMART']['Quick']: + print_standard(' ') + try_and_print( + message='Connecting to osTicket database...', + function=connect_to_db, + width=40) if TESTS['NVMe/SMART']['Enabled'] or TESTS['badblocks']['Enabled'] or TESTS['iobenchmark']['Enabled']: - if not TESTS['NVMe/SMART']['Quick']: - print_standard(' ') - try_and_print( - message='Connecting to osTicket database...', - function=connect_to_db, - width=40) print_standard(' ') scan_disks() update_progress() - pause() - exit_script() # Run mprime_aborted = False From 1d88c57be8a27de4af9bd63a2f1a2f6c836de90f Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 18 Sep 2018 20:43:09 -0600 Subject: [PATCH 04/20] Adjust Prime95 countown message --- .bin/Scripts/functions/hw_diags.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 089e51a7..e9e8109a 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -93,6 +93,7 @@ TESTS = { def connect_to_db(): """Connect to osTicket database.""" + #TODO# Open SSH tunnel to DB server ost_db['Connection'] = mariadb.connect( user=DB_USER, password=DB_PASS, database=DB_NAME, host=DB_HOST) ost_db['Cursor'] = ost_db['Connection'].cursor() @@ -597,8 +598,10 @@ def run_mprime(): try: for i in range(int(MPRIME_LIMIT)): clear_screen() - print_standard('Running Prime95 ({} minutes left)'.format( - int(MPRIME_LIMIT)-i)) + min_left = int(MPRIME_LIMIT)-i) + print_standard('Running Prime95 ({} minute{} left)'.format( + min_left, + 's' if min_left != 1 else '') print_warning('If running too hot, press CTRL+c to abort the test') sleep(60) except KeyboardInterrupt: From 580d1de9158607431741cca90301396ea654027e Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 18 Sep 2018 21:48:15 -0600 Subject: [PATCH 05/20] Finished Prime95 osTicket reply section * NOTE: A reply is not posted for Aborted tests or Unknown results --- .bin/Scripts/functions/hw_diags.py | 76 ++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index e9e8109a..627ffa6d 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -141,6 +141,9 @@ def get_graph_step(rate, scale=16): def get_osticket_number(): """Get ticket number and confirm with name from osTicket DB.""" ticket_number = None + if not ost_db['Cursor']: + # No DB access, just set it to 0 + return 0 while ticket_number is None: print_standard(' ') _input = input('Enter ticket number: ') @@ -242,6 +245,12 @@ def menu_diags(*args): spacer = '──────────────────────────') if selection.isnumeric(): if diag_modes[int(selection)-1]['Name'] != 'Quick drive test': + clear_screen() + print_standard(' ') + try_and_print( + message='Connecting to osTicket database...', + function=connect_to_db, + width=40) # Save log for non-quick tests ticket_number = get_osticket_number() global_vars['LogDir'] = '{}/Logs/{}_{}'.format( @@ -251,7 +260,7 @@ def menu_diags(*args): os.makedirs(global_vars['LogDir'], exist_ok=True) global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format( global_vars['LogDir']) - run_tests(diag_modes[int(selection)-1]['Tests']) + run_tests(diag_modes[int(selection)-1]['Tests'], ticket_number) elif selection == 'A': run_program(['hw-diags-audio'], check=False, pipe=False) pause('Press Enter to return to main menu... ') @@ -315,7 +324,7 @@ def osticket_needs_attention(ticket_id): except: ost_db['Errors'] = True -def osticket_reply(ticket_id, response): +def osticket_post_reply(ticket_id, response): """Post a reply to a ticket in osTicket.""" if not ticket_id: raise GenericError @@ -359,7 +368,7 @@ def osticket_set_drive_result(ticket_id, passed): except: ost_db['Errors'] = True -def run_badblocks(): +def run_badblocks(ticket_number): """Run a read-only test for all detected disks.""" aborted = False clear_screen() @@ -421,7 +430,7 @@ def run_badblocks(): run_program('tmux kill-pane -a'.split(), check=False) pass -def run_iobenchmark(): +def run_iobenchmark(ticket_number): """Run a read-only test for all detected disks.""" aborted = False clear_screen() @@ -578,7 +587,7 @@ def run_iobenchmark(): run_program('tmux kill-pane -a'.split(), check=False) pass -def run_mprime(): +def run_mprime(ticket_number): """Run Prime95 for MPRIME_LIMIT minutes while showing the temps.""" aborted = False print_log('\nStart Prime95 test') @@ -598,10 +607,10 @@ def run_mprime(): try: for i in range(int(MPRIME_LIMIT)): clear_screen() - min_left = int(MPRIME_LIMIT)-i) + min_left = int(MPRIME_LIMIT) - i print_standard('Running Prime95 ({} minute{} left)'.format( min_left, - 's' if min_left != 1 else '') + 's' if min_left != 1 else '')) print_warning('If running too hot, press CTRL+c to abort the test') sleep(60) except KeyboardInterrupt: @@ -671,10 +680,45 @@ def run_mprime(): TESTS['Prime95']['Status'] = 'Unknown' update_progress() + # Build osTicket report + if TESTS['Prime95']['Status'] not in ['Unknown', 'Aborted']: + report = ['System {} Prime95 testing.'.format( + 'FAILED' if TESTS['Prime95']['NS'] else 'passed')] + report.append('') + report.append('Prime95 log:') + log_path = '{}/prime.log'.format(global_vars['LogDir']) + try: + with open(log_path, 'r') as f: + for line in f.readlines(): + line = line.strip() + r = re.search('(completed \d+ tests.*)', line, re.IGNORECASE) + if r: + report.append(r.group(1)) + except: + report.append(' ERROR: Failed to read log.') + report.append('') + report.append('Final temps:') + log_path = '{}/Final Temps.log'.format(global_vars['LogDir']) + try: + with open(log_path, 'r') as f: + for line in f.readlines(): + line = line.strip() + if not line: + # Stop after CPU temp(s) + break + report.append(line) + except: + report.append(' ERROR: Failed to read log.') + + # Upload osTicket report + osticket_post_reply( + ticket_id=ticket_number, + response='\n'.join(report)) + # Done run_program('tmux kill-pane -a'.split()) -def run_nvme_smart(): +def run_nvme_smart(ticket_number): """Run the built-in NVMe or SMART test for all detected disks.""" aborted = False clear_screen() @@ -769,7 +813,7 @@ def run_nvme_smart(): # Done run_program('tmux kill-pane -a'.split(), check=False) -def run_tests(tests): +def run_tests(tests, ticket_number=None): """Run selected hardware test(s).""" clear_screen() print_standard('Starting Hardware Diagnostics') @@ -781,12 +825,6 @@ def run_tests(tests): TESTS['NVMe/SMART']['Quick'] = 'Quick' in tests # Initialize - if not TESTS['NVMe/SMART']['Quick']: - print_standard(' ') - try_and_print( - message='Connecting to osTicket database...', - function=connect_to_db, - width=40) if TESTS['NVMe/SMART']['Enabled'] or TESTS['badblocks']['Enabled'] or TESTS['iobenchmark']['Enabled']: print_standard(' ') scan_disks() @@ -796,16 +834,16 @@ def run_tests(tests): mprime_aborted = False if TESTS['Prime95']['Enabled']: try: - run_mprime() + run_mprime(ticket_number) except GenericError: mprime_aborted = True if not mprime_aborted: if TESTS['NVMe/SMART']['Enabled']: - run_nvme_smart() + run_nvme_smart(ticket_number) if TESTS['badblocks']['Enabled']: - run_badblocks() + run_badblocks(ticket_number) if TESTS['iobenchmark']['Enabled']: - run_iobenchmark() + run_iobenchmark(ticket_number) # Show results show_results() From 9698cfbf6d7d52518b7a3e8f4a371da8030a4140 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 20 Sep 2018 00:35:05 -0600 Subject: [PATCH 06/20] Initial osTicket drive report section * Formatting is off, need to remove ASCII color escapes --- .bin/Scripts/functions/hw_diags.py | 156 ++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 3 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 627ffa6d..2deecbd0 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -368,6 +368,155 @@ def osticket_set_drive_result(ticket_id, passed): except: ost_db['Errors'] = True +def post_drive_results(ticket_number): + """Post drive test results to osTicket.""" + tested = False + + # Check if test(s) were run + for t in ['NVMe/SMART', 'badblocks', 'iobenchmark']: + tested |= TESTS[t]['Enabled'] + if not tested or TESTS['NVMe/SMART']['Quick']: + # No tests were run so no post necessary + return + + # Build reports for all tested devices + for name, dev in sorted(TESTS['NVMe/SMART']['Devices'].items()): + dev_failed = False + dev_passed = True + dev_unknown = False + report = [] + + # Check all test results for dev + for t in ['NVMe/SMART', 'badblocks', 'iobenchmark']: + if not TESTS[t]['Enabled']: + continue + status = TESTS[t]['Status'].get(name, 'Unknown') + dev_failed |= status == 'NS' + dev_passed &= status == 'CS' + dev_unknown |= status in ('Working', 'Unknown') + if not dev_failed and not dev_passed and not dev_unknown: + # Assuming drive was skipped so no reply is needed + continue + + # Start drive report + if dev_failed: + report.append('Drive hardware diagnostics tests: FAILED') + elif dev_unknown: + report.append('Drive hardware diagnostics tests: UNKNOWN') + elif dev_passed: + report.append('Drive hardware diagnostics tests: Passed') + report.append('') + + # Drive description + report.append('{size} ({tran}) {model} {serial}'.format( + size=dev['lsblk'].get('size', '???b'), + tran=dev['lsblk'].get('tran', '???'), + model=dev['lsblk'].get('model', 'Unknown Model'), + serial=dev['lsblk'].get('serial', 'Unknown Serial'), + )) + report.append('') + + # Warnings (if any) + if dev.get('NVMe Disk', False): + if dev['Quick Health Ok']: + report.append('WARNING: NVMe support is still experimental') + else: + report.append('ERROR: NVMe disk is reporting critical warnings') + report.append('') + elif not dev['SMART Support']: + report.append('ERROR: Unable to retrieve SMART data') + report.append('') + elif not dev['SMART Pass']: + report.append('ERROR: SMART overall-health assessment result: FAILED') + report.append('') + + # NVMe/SMART Attributes + if dev.get('NVMe Disk', False): + report.append('NVMe Attributes:') + for attrib in sorted(ATTRIBUTES['NVMe'].keys()): + if attrib in dev['nvme-cli']: + report.append('{attrib:30}{value}'.format( + attrib=attrib, + value=dev['nvme-cli'][attrib], + )) + report[-1] = report[-1].strip().replace(' ', '.') + report[-1] = report[-1].replace('_', ' ') + elif dev['smartctl'].get('ata_smart_attributes', None): + report.append('SMART Attributes:') + s_table = dev['smartctl'].get('ata_smart_attributes', {}).get( + 'table', {}) + s_table = {a.get('id', 'Unknown'): a for a in s_table} + for attrib in sorted(ATTRIBUTES['SMART'].keys()): + if attrib in s_table: + report.append('{:0>2}/{:24}{} ({})'.format( + str(hex(int(attrib))).upper()[2:], + attrib, + s_table[attrib]['raw']['string'], + s_table[attrib]['name'], + )) + report[-1] = report[-1].strip().replace(' ', '.') + report[-1] = report[-1].replace('_', ' ') + report.append('') + + # badblocks + bb_status = TESTS['badblocks']['Status'].get(name, None) + if TESTS['badblocks']['Enabled'] and bb_status not in ['Denied', 'Skipped']: + report.append('badblocks:') + bb_result = TESTS['badblocks']['Results'].get( + name, + 'ERROR: Failed to read log.') + for line in bb_result.splitlines(): + line = line.strip() + if not line: + continue + if re.search('Pass completed', line, re.IGNORECASE): + line = re.sub( + r'Pass completed,?\s+', + r'', + line, + re.IGNORECASE) + report.append(line) + report.append('') + + # I/O Benchmark + io_status = TESTS['iobenchmark']['Status'].get(name, None) + if TESTS['iobenchmark']['Enabled'] and io_status not in ['Denied', 'Skipped']: + report.append('I/O Benchmark:') + io_result = TESTS['iobenchmark']['Results'].get( + name, + 'ERROR: Failed to read log.') + for line in io_result.splitlines(): + line = line.strip() + if not line: + continue + report.append(line) + report.append('') + + # TODO-REMOVE TESTING + with open('/home/twoshirt/__ost_report_{}.txt'.format(name), 'w') as f: + for line in report: + f.write('{}\n'.format(line.strip())) + + # Post reply for drive + osticket_post_reply( + ticket_id=ticket_number, + response='\n'.join(report)) + + # Mark ticket HDD/SSD pass/fail checkbox (as needed) + if dev_failed: + osticket_set_drive_result( + ticket_id=ticket_number, + passed=False) + elif dev_unknown: + pass + elif dev_passed: + osticket_set_drive_result( + ticket_id=ticket_number, + passed=True) + + # Mark ticket as NEEDS ATTENTION + osticket_needs_attention(ticket_id=ticket_number) + def run_badblocks(ticket_number): """Run a read-only test for all detected disks.""" aborted = False @@ -695,7 +844,7 @@ def run_mprime(ticket_number): if r: report.append(r.group(1)) except: - report.append(' ERROR: Failed to read log.') + report.append('ERROR: Failed to read log.') report.append('') report.append('Final temps:') log_path = '{}/Final Temps.log'.format(global_vars['LogDir']) @@ -708,7 +857,7 @@ def run_mprime(ticket_number): break report.append(line) except: - report.append(' ERROR: Failed to read log.') + report.append('ERROR: Failed to read log.') # Upload osTicket report osticket_post_reply( @@ -846,6 +995,7 @@ def run_tests(tests, ticket_number=None): run_iobenchmark(ticket_number) # Show results + post_drive_results(ticket_number) show_results() # Open log @@ -947,7 +1097,7 @@ def scan_disks(full_paths=False, only_path=None): if ask('Run tests on this device anyway?'): TESTS['NVMe/SMART']['Status'][dev_name] = 'OVERRIDE' else: - TESTS['NVMe/SMART']['Status'][dev_name] = 'NS' + TESTS['NVMe/SMART']['Status'][dev_name] = 'Skipped' TESTS['badblocks']['Status'][dev_name] = 'Denied' TESTS['iobenchmark']['Status'][dev_name] = 'Denied' print_standard(' ') # In case there's more than one "OVERRIDE" disk From dd13f7bd2437b76dc968a3b5e0b95b14dc49f6aa Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 20 Sep 2018 00:50:32 -0600 Subject: [PATCH 07/20] Adjusted osTicket drive report formatting --- .bin/Scripts/functions/hw_diags.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 2deecbd0..45cb8575 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -448,10 +448,20 @@ def post_drive_results(ticket_number): s_table = {a.get('id', 'Unknown'): a for a in s_table} for attrib in sorted(ATTRIBUTES['SMART'].keys()): if attrib in s_table: - report.append('{:0>2}/{:24}{} ({})'.format( - str(hex(int(attrib))).upper()[2:], - attrib, + # Pad attributewith dots for osTicket + hex_str = '{:>2}'.format(str(hex(int(attrib))).upper()[2:]) + hex_str = hex_str.replace(' ', '..') + if '.' in hex_str: + hex_str = '.' + hex_str + dec_str = '{:>3}'.format(attrib) + dec_str = dec_str.replace(' ', '..') + if '.' in dec_str: + dec_str = '.' + dec_str + report.append('{:>2}/{:>3}: {} {} ({})'.format( + hex_str, + dec_str, s_table[attrib]['raw']['string'], + '..'*24+'.', s_table[attrib]['name'], )) report[-1] = report[-1].strip().replace(' ', '.') @@ -489,6 +499,9 @@ def post_drive_results(ticket_number): line = line.strip() if not line: continue + # Strip colors from line + for c in COLORS.values(): + line = line.replace(c, '') report.append(line) report.append('') From aae7c1d54320f40d459afcda387a1877f84232bc Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 20 Sep 2018 00:58:21 -0600 Subject: [PATCH 08/20] Allow diabling osTicket integration per run --- .bin/Scripts/functions/hw_diags.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 45cb8575..0653203b 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -142,11 +142,16 @@ def get_osticket_number(): """Get ticket number and confirm with name from osTicket DB.""" ticket_number = None if not ost_db['Cursor']: - # No DB access, just set it to 0 - return 0 + # No DB access, return None to disable integration + return None while ticket_number is None: print_standard(' ') - _input = input('Enter ticket number: ') + _input = input('Enter ticket number (or leave blank to disable): ') + if re.match(r'^\s*$', _input): + if ask('Disable osTicket integration for this run?'): + return None + else: + continue if not re.match(r'^([0-9]+)$', _input): continue _name = osticket_get_ticket_name(_input) From 146244d3fd5132293119506ae743a06b644b6890 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 20 Sep 2018 12:02:08 -0600 Subject: [PATCH 09/20] Adjusted osTicket post formatting --- .bin/Scripts/functions/hw_diags.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 0653203b..d56ab469 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -373,6 +373,16 @@ def osticket_set_drive_result(ticket_id, passed): except: ost_db['Errors'] = True +def pad_with_dots(s, left_pad=True): + """Replace ' ' padding with '..' for osTicket posts.""" + s = str(s).replace(' ', '..') + if '.' in s: + if left_pad: + s = '.' + s + else: + s = s + '.' + return s + def post_drive_results(ticket_number): """Post drive test results to osTicket.""" tested = False @@ -454,22 +464,17 @@ def post_drive_results(ticket_number): for attrib in sorted(ATTRIBUTES['SMART'].keys()): if attrib in s_table: # Pad attributewith dots for osTicket - hex_str = '{:>2}'.format(str(hex(int(attrib))).upper()[2:]) - hex_str = hex_str.replace(' ', '..') - if '.' in hex_str: - hex_str = '.' + hex_str - dec_str = '{:>3}'.format(attrib) - dec_str = dec_str.replace(' ', '..') - if '.' in dec_str: - dec_str = '.' + dec_str - report.append('{:>2}/{:>3}: {} {} ({})'.format( + hex_str = str(hex(int(attrib))).upper()[2:] + hex_str = pad_with_dots('{:>2}'.format(hex_str)) + dec_str = pad_with_dots('{:>3}'.format(attrib)) + val_str = '{:<20}'.format(s_table[attrib]['raw']['string']) + val_str = pad_with_dots(val_str, left_pad=False) + report.append('{:>2}/{:>3}: {} ({})'.format( hex_str, dec_str, - s_table[attrib]['raw']['string'], - '..'*24+'.', + val_str, s_table[attrib]['name'], )) - report[-1] = report[-1].strip().replace(' ', '.') report[-1] = report[-1].replace('_', ' ') report.append('') From 462a87b1ce46b0ec72c3ea5eee61b130c7d49ec7 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 20 Sep 2018 12:50:13 -0600 Subject: [PATCH 10/20] Adjusted ticket selection confirmation --- .bin/Scripts/functions/hw_diags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index d56ab469..d42f3a31 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -156,7 +156,7 @@ def get_osticket_number(): continue _name = osticket_get_ticket_name(_input) if _name: - print_standard('You have selected ticket #{} ({})'.format( + print_standard('You have selected ticket #{} {}'.format( _input, _name)) if ask('Is this correct?'): ticket_number = _input From d46ae180459d07221e3073090fdf56ff3bbe3161 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 20 Sep 2018 14:53:17 -0600 Subject: [PATCH 11/20] Establish SSH tunnel before connecting to SQL DB * Also added disconnect_from_db() function. --- .bin/Scripts/functions/hw_diags.py | 40 ++++++++++++++++++++++++++---- .bin/Scripts/settings/main.py | 1 + 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index d42f3a31..467d3563 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -10,7 +10,8 @@ from functions.common import * ost_db = { 'Connection': None, 'Cursor': None, - 'Errors': False + 'Errors': False, + 'Tunnel': None, } # STATIC VARIABLES @@ -92,13 +93,39 @@ TESTS = { } def connect_to_db(): - """Connect to osTicket database.""" - #TODO# Open SSH tunnel to DB server - ost_db['Connection'] = mariadb.connect( - user=DB_USER, password=DB_PASS, database=DB_NAME, host=DB_HOST) + """Connect to osTicket database via SSH tunnel.""" + cmd = [ + 'ssh', '-N', '-p', '2222', '-L3306:127.0.0.1:3306', + '{user}@{host}'.format(user=SSH_USER, host=DB_HOST), + ] + + # Establish SSH tunnel unless one already exists + if not ost_db['Tunnel']: + ost_db['Tunnel'] = popen_program(cmd) + + # Establish SQL connection (try a few times in case SSH is slow) + for x in range(5): + sleep(3) + try: + ost_db['Connection'] = mariadb.connect( + user=DB_USER, password=DB_PASS, database=DB_NAME) + except: + # Just try again + pass + else: + break ost_db['Cursor'] = ost_db['Connection'].cursor() ost_db['Errors'] = False +def disconnect_from_db(): + """Disconnect from SQL DB and close SSH tunnel.""" + if ost_db['Cursor']: + ost_db['Cursor'].close() + if ost_db['Connection']: + ost_db['Connection'].close() + if ost_db['Tunnel']: + ost_db['Tunnel'].kill() + def generate_horizontal_graph(rates): """Generate two-line horizontal graph from rates, returns str.""" line_top = '' @@ -287,6 +314,9 @@ def menu_diags(*args): elif selection == 'Q': break + # Done + disconnect_from_db() + def osticket_get_ticket_name(ticket_id): """Lookup ticket and return name as str.""" ticket_name = 'Unknown' diff --git a/.bin/Scripts/settings/main.py b/.bin/Scripts/settings/main.py index 4e6d4083..0d5944a2 100644 --- a/.bin/Scripts/settings/main.py +++ b/.bin/Scripts/settings/main.py @@ -16,6 +16,7 @@ DB_HOST='127.0.0.1' DB_NAME='osticket' DB_USER='wizardkit' DB_PASS='Abracadabra' +SSH_USER='sql_tunnel' # Live Linux MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags ROOT_PASSWORD='Abracadabra' From ba06b7d63502e5356b11d9ddd81b2d7d90736cc5 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 20 Sep 2018 15:57:53 -0600 Subject: [PATCH 12/20] Increased height of horizontal I/O graph * Allows for 32 steps of accuracy * Adjusted curve to max out around 750 Mb/s --- .bin/Scripts/functions/hw_diags.py | 49 +++++++++++++++++++++--------- .bin/Scripts/settings/main.py | 1 + 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 467d3563..018d54c8 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -43,8 +43,8 @@ IO_VARS = { 'Minimum Test Size': 10*1024**3, 'Alt Test Size Factor': 0.01, 'Progress Refresh Rate': 5, - 'Scale 16': [2**(0.6*x)+(16*x) for x in range(1,17)], - 'Scale 32': [2**(0.6*x/2)+(16*x/2) for x in range(1,33)], + 'Scale 16': [2**(0.56*x)+(16*x) for x in range(1,17)], + 'Scale 32': [2**(0.56*x/2)+(16*x/2) for x in range(1,33)], 'Threshold Fail': 65*1024**2, 'Threshold Warn': 135*1024**2, 'Threshold Great': 750*1024**2, @@ -95,7 +95,7 @@ TESTS = { def connect_to_db(): """Connect to osTicket database via SSH tunnel.""" cmd = [ - 'ssh', '-N', '-p', '2222', '-L3306:127.0.0.1:3306', + 'ssh', '-N', '-p', SSH_PORT, '-L3306:127.0.0.1:3306', '{user}@{host}'.format(user=SSH_USER, host=DB_HOST), ] @@ -128,10 +128,12 @@ def disconnect_from_db(): def generate_horizontal_graph(rates): """Generate two-line horizontal graph from rates, returns str.""" - line_top = '' - line_bottom = '' + line_1 = '' + line_2 = '' + line_3 = '' + line_4 = '' for r in rates: - step = get_graph_step(r, scale=16) + step = get_graph_step(r, scale=32) # Set color r_color = COLORS['CLEAR'] @@ -143,15 +145,32 @@ def generate_horizontal_graph(rates): r_color = COLORS['GREEN'] # Build graph - if step < 8: - line_top += ' ' - line_bottom += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step]) + full_block = '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][-1]) + if step >= 24: + line_1 += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step-24]) + line_2 += full_block + line_3 += full_block + line_4 += full_block + elif step >= 16: + line_1 += ' ' + line_2 += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step-16]) + line_3 += full_block + line_4 += full_block + elif step >= 8: + line_1 += ' ' + line_2 += ' ' + line_3 += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step-8]) + line_4 += full_block else: - line_top += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step-8]) - line_bottom += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][-1]) - line_top += COLORS['CLEAR'] - line_bottom += COLORS['CLEAR'] - return '{}\n{}'.format(line_top, line_bottom) + line_1 += ' ' + line_2 += ' ' + line_3 += ' ' + line_4 += '{}{}'.format(r_color, IO_VARS['Graph Horizontal'][step]) + line_1 += COLORS['CLEAR'] + line_2 += COLORS['CLEAR'] + line_3 += COLORS['CLEAR'] + line_4 += COLORS['CLEAR'] + return '\n'.join([line_1, line_2, line_3, line_4]) def get_graph_step(rate, scale=16): """Get graph step based on rate and scale, returns int.""" @@ -763,7 +782,7 @@ def run_iobenchmark(ticket_number): h_graph_rates.append(sum(read_rates[pos:pos+width])/width) pos += width report = generate_horizontal_graph(h_graph_rates) - report += '\nRead speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format( + report += '\nAverage read speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format( sum(read_rates)/len(read_rates)/(1024**2), min(read_rates)/(1024**2), max(read_rates)/(1024**2)) diff --git a/.bin/Scripts/settings/main.py b/.bin/Scripts/settings/main.py index 0d5944a2..349723d1 100644 --- a/.bin/Scripts/settings/main.py +++ b/.bin/Scripts/settings/main.py @@ -16,6 +16,7 @@ DB_HOST='127.0.0.1' DB_NAME='osticket' DB_USER='wizardkit' DB_PASS='Abracadabra' +SSH_PORT='22' SSH_USER='sql_tunnel' # Live Linux MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags From aec3e8208c9866fb37a38562ca7f3df92a13623c Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 24 Sep 2018 02:27:02 -0600 Subject: [PATCH 13/20] New osTicket report layout * Block character graph has been reduced to one line * A PNG graph is exported using gnuplot * The graph is uploaded to imgur and a link is included in the report * The graph is also uploaded to the BENCHMARK_SERVER for redundancy --- .bin/Scripts/functions/hw_diags.py | 186 ++++++++++++++++++++++++----- .bin/Scripts/settings/main.py | 9 ++ 2 files changed, 167 insertions(+), 28 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 018d54c8..0525d323 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -1,10 +1,14 @@ # Wizard Kit: Functions - HW Diagnostics +import base64 +import Gnuplot import json +import math import time import mysql.connector as mariadb from functions.common import * +from numpy import * # Database connection ost_db = { @@ -43,8 +47,9 @@ IO_VARS = { 'Minimum Test Size': 10*1024**3, 'Alt Test Size Factor': 0.01, 'Progress Refresh Rate': 5, - 'Scale 16': [2**(0.56*x)+(16*x) for x in range(1,17)], - 'Scale 32': [2**(0.56*x/2)+(16*x/2) for x in range(1,33)], + 'Scale 8': [2**(0.56*(x+1))+(16*(x+1)) for x in range(8)], + 'Scale 16': [2**(0.56*(x+1))+(16*(x+1)) for x in range(16)], + 'Scale 32': [2**(0.56*(x+1)/2)+(16*(x+1)/2) for x in range(32)], 'Threshold Fail': 65*1024**2, 'Threshold Warn': 135*1024**2, 'Threshold Great': 750*1024**2, @@ -87,6 +92,7 @@ TESTS = { }, 'iobenchmark': { 'Enabled': False, + 'Rates': {}, 'Results': {}, 'Status': {}, }, @@ -126,7 +132,37 @@ def disconnect_from_db(): if ost_db['Tunnel']: ost_db['Tunnel'].kill() -def generate_horizontal_graph(rates): +def export_png_graph(name, dev): + """Exports PNG graph using gnuplot, returns file path as str.""" + max_rate = max(800, max(rates)) + out_path = '{}/iobenchmark.png'.format(global_vars['TmpDir']) + plot_data = '{}/iobenchmark-{}-raw.log'.format(global_vars['LogDir'], name) + + # Adjust Y-axis range to either 1000 or roughly max rate + 200 + y_range = math.ceil(max_rate/100)*100 + 200 + + # Run gnuplot commands + g = Gnuplot.Gnuplot() + g('set output "{}"'.format(out_path)) + g('set terminal png large size 660,300 truecolor font "Noto Sans,11"') + g('set title "I/O Benchmark"') + g('set yrange [0:{}]'.format(y_range)) + g('set style data lines') + g('plot "{data}" title "{size} ({tran}) {model} {serial}"'.format( + data=plot_data, + size=dev['lsblk'].get('size', '???b'), + tran=dev['lsblk'].get('tran', '???'), + model=dev['lsblk'].get('model', 'Unknown Model'), + serial=dev['lsblk'].get('serial', 'Unknown Serial'), + )) + + # Cleanup + g.close() + del(g) + + return out_path + +def generate_horizontal_graph(rates, oneline=False): """Generate two-line horizontal graph from rates, returns str.""" line_1 = '' line_2 = '' @@ -134,6 +170,8 @@ def generate_horizontal_graph(rates): line_4 = '' for r in rates: step = get_graph_step(r, scale=32) + if oneline: + step = get_graph_step(r, scale=8) # Set color r_color = COLORS['CLEAR'] @@ -170,7 +208,10 @@ def generate_horizontal_graph(rates): line_2 += COLORS['CLEAR'] line_3 += COLORS['CLEAR'] line_4 += COLORS['CLEAR'] - return '\n'.join([line_1, line_2, line_3, line_4]) + if oneline: + return line_4 + else: + return '\n'.join([line_1, line_2, line_3, line_4]) def get_graph_step(rate, scale=16): """Get graph step based on rate and scale, returns int.""" @@ -550,19 +591,39 @@ def post_drive_results(ticket_number): # I/O Benchmark io_status = TESTS['iobenchmark']['Status'].get(name, None) if TESTS['iobenchmark']['Enabled'] and io_status not in ['Denied', 'Skipped']: + one_line_graph = generate_horizontal_graph( + rates=TESTS['iobenchmark']['Data'][name]['Read Rates'], + oneline=True) + for c in COLORS.values(): + one_line_graph = one_line_graph.replace(c, '') report.append('I/O Benchmark:') - io_result = TESTS['iobenchmark']['Results'].get( - name, - 'ERROR: Failed to read log.') - for line in io_result.splitlines(): - line = line.strip() - if not line: - continue - # Strip colors from line - for c in COLORS.values(): - line = line.replace(c, '') - report.append(line) - report.append('') + report.append(one_line_graph) + report.append('{}'.format( + TESTS['iobenchmark']['Data'][name]['Avg/Min/Max'])) + + # Export PNG + try: + png_path = export_png_graph(name, dev) + except: + png_path = None + + # imgur + try: + url = upload_to_imgur(image_path) + except: + # Oh well + pass + else: + report.append('Imgur: {}'.format(url)) + + # Nextcloud + try: + url = upload_to_nextcloud(image_path, ticket_number, name) + except: + # Oh well + pass + else: + report.append('Nextcloud: {}'.format(url)) # TODO-REMOVE TESTING with open('/home/twoshirt/__ost_report_{}.txt'.format(name), 'w') as f: @@ -744,7 +805,9 @@ def run_iobenchmark(ticket_number): # Run dd read tests offset = 0 - read_rates = [] + TESTS['iobenchmark']['Data'][name] = { + 'Graph': [], + 'Read Rates': []} for i in range(test_chunks): i += 1 s = skip_count @@ -759,12 +822,18 @@ def run_iobenchmark(ticket_number): o='/dev/null') result = run_program(cmd.split()) result_str = result.stderr.decode().replace('\n', '') - read_rates.append(get_read_rate(result_str)) + cur_rate = get_read_rate(result_str) + TESTS['iobenchmark']['Data'][name]['Read Rates'].append( + cur_rate) + TESTS['iobenchmark']['Data'][name]['Graph'].append( + '{percent} {rate}'.format( + percent=i/test_chunks*100, + rate=cur_rate/(1024**2))) if i % IO_VARS['Progress Refresh Rate'] == 0: # Update vertical graph update_io_progress( percent=i/test_chunks*100, - rate=read_rates[-1], + rate=cur_rate, progress_file=progress_file) # Update offset offset += s + c @@ -774,24 +843,28 @@ def run_iobenchmark(ticket_number): run_program(['tmux', 'kill-pane', '-t', bottom_pane]) # Build report + avg_min_max = 'Average read speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format( + sum(TESTS['iobenchmark']['Data'][name]['Read Rates'])/len( + TESTS['iobenchmark']['Data'][name]['Read Rates'])/(1024**2), + min(TESTS['iobenchmark']['Data'][name]['Read Rates'])/(1024**2), + max(TESTS['iobenchmark']['Data'][name]['Read Rates'])/(1024**2)) + TESTS['iobenchmark']['Data'][name]['Avg/Min/Max'] = avg_min_max h_graph_rates = [] pos = 0 width = int(test_chunks / IO_VARS['Graph Horizontal Width']) for i in range(IO_VARS['Graph Horizontal Width']): # Append average rate for WIDTH number of rates to new array - h_graph_rates.append(sum(read_rates[pos:pos+width])/width) + h_graph_rates.append(sum( + TESTS['iobenchmark']['Data'][name]['Read Rates'][pos:pos+width])/width) pos += width report = generate_horizontal_graph(h_graph_rates) - report += '\nAverage read speed: {:3.1f} MB/s (Min: {:3.1f}, Max: {:3.1f})'.format( - sum(read_rates)/len(read_rates)/(1024**2), - min(read_rates)/(1024**2), - max(read_rates)/(1024**2)) + report += '\n{}'.format(avg_min_max) TESTS['iobenchmark']['Results'][name] = report # Set CS/NS - if min(read_rates) <= IO_VARS['Threshold Fail']: + if min(TESTS['iobenchmark']['Data'][name]['Read Rates']) <= IO_VARS['Threshold Fail']: TESTS['iobenchmark']['Status'][name] = 'NS' - elif min(read_rates) <= IO_VARS['Threshold Warn']: + elif min(TESTS['iobenchmark']['Data'][name]['Read Rates']) <= IO_VARS['Threshold Warn']: TESTS['iobenchmark']['Status'][name] = 'Unknown' else: TESTS['iobenchmark']['Status'][name] = 'CS' @@ -800,8 +873,7 @@ def run_iobenchmark(ticket_number): dest_filename = '{}/iobenchmark-{}.log'.format(global_vars['LogDir'], name) shutil.move(progress_file, dest_filename) with open(dest_filename.replace('.', '-raw.'), 'a') as f: - for rate in read_rates: - f.write('{} MB/s\n'.format(rate/(1024**2))) + f.write('\n'.join(TESTS['iobenchmark']['Data'][name]['Graph'])) update_progress() # Done @@ -1417,6 +1489,64 @@ def update_progress(): with open(TESTS['Progress Out'], 'w') as f: f.writelines(output) +def upload_to_imgur(image_path): + """Upload image to Imgur and return image url as str.""" + image_data = None + image_link = None + + # Bail early + if not image_path: + raise GenericError + + # Read image file and convert to base64 + with open(image_path, 'rb') as f: + image_data = base64.b64encode(f.read()) + + # POST image + url = "https://api.imgur.com/3/image" + boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW' + payload = ('--{boundary}\r\nContent-Disposition: form-data; ' + 'name="image"\r\n\r\n{data}\r\n--{boundary}--') + payload = payload.format(boundary=boundary, data=image_data) + headers = { + 'content-type': 'multipart/form-data; boundary={}'.format(boundary), + 'Authorization': 'Client-ID {}'.format(IMGUR_CLIENT_ID), + } + response = requests.request("POST", url, data=payload, headers=headers) + + # Return image link + if response.ok: + json_data = json.loads(response.text) + image_link = json_data['data']['link'] + return image_link + +def upload_to_nextcloud(image_path, ticket_number, dev_name): + """Upload image to Nextcloud server and return folder url as str.""" + image_data = None + + # Bail early + if not image_path: + raise GenericError + + # Read image file and convert to base64 + with open(image_path, 'rb') as f: + image_data = f.read() + + # PUT image + url = '{base_url}/{ticket_number}_iobenchmark_{dev_name}_{date}'.format( + base_url=BENCHMARK_SERVER['Url'], + ticket_number=ticket_number, + dev_name=dev_name, + date=global_vars.get('Date-Time', 'Unknown Date-Time')) + requests.put( + url, + data=image_data, + headers = {'X-Requested-With': 'XMLHttpRequest'}, + auth = (BENCHMARK_SERVER['User'], BENCHMARK_SERVER['Pass'])) + + # Return folder link + return BENCHMARK_SERVER['Short Url'] + if __name__ == '__main__': print("This file is not meant to be called directly.") diff --git a/.bin/Scripts/settings/main.py b/.bin/Scripts/settings/main.py index 349723d1..4ce4e533 100644 --- a/.bin/Scripts/settings/main.py +++ b/.bin/Scripts/settings/main.py @@ -18,6 +18,8 @@ DB_USER='wizardkit' DB_PASS='Abracadabra' SSH_PORT='22' SSH_USER='sql_tunnel' +# imgur +IMGUR_CLIENT_ID='' # Live Linux MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags ROOT_PASSWORD='Abracadabra' @@ -56,6 +58,13 @@ BACKUP_SERVERS = [ 'RW-Pass': 'Abracadabra', }, ] +BENCHMARK_SERVER = { + 'Name': 'BenchmarkServer', + 'Short Url': '', + 'Url': '', + 'User': '', + 'Pass': '', +} CRASH_SERVER = { 'Name': 'CrashServer', 'Url': '', From 4206afe0c36d96ae1a77e0bc80974caaf550b7d2 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 24 Sep 2018 15:45:52 -0600 Subject: [PATCH 14/20] Disabled osticket_needs_attention() * The flag has been repurposed in osTicket --- .bin/Scripts/functions/hw_diags.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 0525d323..7817cde2 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -400,7 +400,9 @@ def osticket_get_ticket_name(ticket_id): ost_db['Errors'] = True def osticket_needs_attention(ticket_id): - """Marks the ticket as "NEEDS ATTENTION" in osTicket.""" + """[DISABLED] Marks the ticket as "NEEDS ATTENTION" in osTicket.""" + return # This function has been DISABLED due to a repurposing of that flag + if not ticket_id: raise GenericError if not ost_db['Cursor']: From 8d5a4b4079bb99aa5aea0c7922f1647ab8f3a459 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 24 Sep 2018 16:07:23 -0600 Subject: [PATCH 15/20] Add python-gnuplot package --- .linux_items/packages/live_add | 1 + 1 file changed, 1 insertion(+) diff --git a/.linux_items/packages/live_add b/.linux_items/packages/live_add index 57782fe9..7ac1faf5 100644 --- a/.linux_items/packages/live_add +++ b/.linux_items/packages/live_add @@ -63,6 +63,7 @@ p7zip papirus-icon-theme progsreiserfs python +python-gnuplot python-mysql-connector python-psutil python-requests From 7506cd017b2789ee0897f278287d71f5425abe0b Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 24 Sep 2018 17:47:52 -0600 Subject: [PATCH 16/20] PNG graph export and uploads working --- .bin/Scripts/functions/hw_diags.py | 50 ++++++++++++++---------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index 7817cde2..d6ca9535 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -4,8 +4,9 @@ import base64 import Gnuplot import json import math -import time import mysql.connector as mariadb +import requests +import time from functions.common import * from numpy import * @@ -92,7 +93,7 @@ TESTS = { }, 'iobenchmark': { 'Enabled': False, - 'Rates': {}, + 'Data': {}, 'Results': {}, 'Status': {}, }, @@ -134,15 +135,19 @@ def disconnect_from_db(): def export_png_graph(name, dev): """Exports PNG graph using gnuplot, returns file path as str.""" - max_rate = max(800, max(rates)) - out_path = '{}/iobenchmark.png'.format(global_vars['TmpDir']) + max_rate = max(TESTS['iobenchmark']['Data'][name]['Read Rates']) + max_rate /= (1024**2) + max_rate = max(800, max_rate) + out_path = '{}/iobenchmark-{}.png'.format(global_vars['LogDir'], name) plot_data = '{}/iobenchmark-{}-raw.log'.format(global_vars['LogDir'], name) # Adjust Y-axis range to either 1000 or roughly max rate + 200 - y_range = math.ceil(max_rate/100)*100 + 200 + ## Round up to the nearest 100 and then add 200 + y_range = (math.ceil(max_rate/100)*100) + 200 # Run gnuplot commands g = Gnuplot.Gnuplot() + g('reset') g('set output "{}"'.format(out_path)) g('set terminal png large size 660,300 truecolor font "Noto Sans,11"') g('set title "I/O Benchmark"') @@ -402,7 +407,6 @@ def osticket_get_ticket_name(ticket_id): def osticket_needs_attention(ticket_id): """[DISABLED] Marks the ticket as "NEEDS ATTENTION" in osTicket.""" return # This function has been DISABLED due to a repurposing of that flag - if not ticket_id: raise GenericError if not ost_db['Cursor']: @@ -594,7 +598,7 @@ def post_drive_results(ticket_number): io_status = TESTS['iobenchmark']['Status'].get(name, None) if TESTS['iobenchmark']['Enabled'] and io_status not in ['Denied', 'Skipped']: one_line_graph = generate_horizontal_graph( - rates=TESTS['iobenchmark']['Data'][name]['Read Rates'], + rates=TESTS['iobenchmark']['Data'][name]['Merged Rates'], oneline=True) for c in COLORS.values(): one_line_graph = one_line_graph.replace(c, '') @@ -611,26 +615,19 @@ def post_drive_results(ticket_number): # imgur try: - url = upload_to_imgur(image_path) + url = upload_to_imgur(png_path) + report.append('Imgur: {}'.format(url)) except: # Oh well pass - else: - report.append('Imgur: {}'.format(url)) # Nextcloud try: - url = upload_to_nextcloud(image_path, ticket_number, name) + url = upload_to_nextcloud(png_path, ticket_number, name) + report.append('Nextcloud: {}'.format(url)) except: # Oh well pass - else: - report.append('Nextcloud: {}'.format(url)) - - # TODO-REMOVE TESTING - with open('/home/twoshirt/__ost_report_{}.txt'.format(name), 'w') as f: - for line in report: - f.write('{}\n'.format(line.strip())) # Post reply for drive osticket_post_reply( @@ -828,9 +825,9 @@ def run_iobenchmark(ticket_number): TESTS['iobenchmark']['Data'][name]['Read Rates'].append( cur_rate) TESTS['iobenchmark']['Data'][name]['Graph'].append( - '{percent} {rate}'.format( + '{percent:0.1f} {rate}'.format( percent=i/test_chunks*100, - rate=cur_rate/(1024**2))) + rate=int(cur_rate/(1024**2)))) if i % IO_VARS['Progress Refresh Rate'] == 0: # Update vertical graph update_io_progress( @@ -851,15 +848,16 @@ def run_iobenchmark(ticket_number): min(TESTS['iobenchmark']['Data'][name]['Read Rates'])/(1024**2), max(TESTS['iobenchmark']['Data'][name]['Read Rates'])/(1024**2)) TESTS['iobenchmark']['Data'][name]['Avg/Min/Max'] = avg_min_max - h_graph_rates = [] + TESTS['iobenchmark']['Data'][name]['Merged Rates'] = [] pos = 0 width = int(test_chunks / IO_VARS['Graph Horizontal Width']) for i in range(IO_VARS['Graph Horizontal Width']): # Append average rate for WIDTH number of rates to new array - h_graph_rates.append(sum( + TESTS['iobenchmark']['Data'][name]['Merged Rates'].append(sum( TESTS['iobenchmark']['Data'][name]['Read Rates'][pos:pos+width])/width) pos += width - report = generate_horizontal_graph(h_graph_rates) + report = generate_horizontal_graph( + TESTS['iobenchmark']['Data'][name]['Merged Rates']) report += '\n{}'.format(avg_min_max) TESTS['iobenchmark']['Results'][name] = report @@ -1500,9 +1498,9 @@ def upload_to_imgur(image_path): if not image_path: raise GenericError - # Read image file and convert to base64 + # Read image file and convert to base64 then convert to str with open(image_path, 'rb') as f: - image_data = base64.b64encode(f.read()) + image_data = base64.b64encode(f.read()).decode() # POST image url = "https://api.imgur.com/3/image" @@ -1535,7 +1533,7 @@ def upload_to_nextcloud(image_path, ticket_number, dev_name): image_data = f.read() # PUT image - url = '{base_url}/{ticket_number}_iobenchmark_{dev_name}_{date}'.format( + url = '{base_url}/{ticket_number}_iobenchmark_{dev_name}_{date}.png'.format( base_url=BENCHMARK_SERVER['Url'], ticket_number=ticket_number, dev_name=dev_name, From e9d65fe29be2d1d9e7b5d3906232f6a1a2cb1c07 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Mon, 24 Sep 2018 17:49:20 -0600 Subject: [PATCH 17/20] Enable direct I/O for I/O Benchmark * Really this should've been enabled earlier * Also increased chunk size --- .bin/Scripts/functions/hw_diags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index d6ca9535..3c7df905 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -43,7 +43,7 @@ ATTRIBUTES = { } IO_VARS = { 'Block Size': 512*1024, - 'Chunk Size': 16*1024**2, + 'Chunk Size': 32*1024**2, 'Minimum Dev Size': 8*1024**3, 'Minimum Test Size': 10*1024**3, 'Alt Test Size Factor': 0.01, @@ -813,7 +813,7 @@ def run_iobenchmark(ticket_number): c = int(IO_VARS['Chunk Size'] / IO_VARS['Block Size']) if skip_extra and i % skip_extra == 0: s += 1 - cmd = 'sudo dd bs={b} skip={s} count={c} if=/dev/{n} of={o}'.format( + cmd = 'sudo dd bs={b} skip={s} count={c} if=/dev/{n} of={o} iflag=direct'.format( b=IO_VARS['Block Size'], s=offset+s, c=c, From 402d73c395ea24c01a019a1927ddc7b8bc7267ee Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 25 Sep 2018 13:57:28 -0600 Subject: [PATCH 18/20] Add osticket.1201.com to known_hosts --- .linux_items/include/airootfs/etc/skel/.ssh/known_hosts | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .linux_items/include/airootfs/etc/skel/.ssh/known_hosts diff --git a/.linux_items/include/airootfs/etc/skel/.ssh/known_hosts b/.linux_items/include/airootfs/etc/skel/.ssh/known_hosts new file mode 100644 index 00000000..b2cbbaee --- /dev/null +++ b/.linux_items/include/airootfs/etc/skel/.ssh/known_hosts @@ -0,0 +1,2 @@ +osticket.1201.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJDDXtNvh4Vd3q3qZkZbIcnDWWO +fJPZb6LVCFptr4awYjlZNL5ieWIUW080IUgtnzWNR7UvetQRtGDsyGu65L+4= From 251bc37fb649057ed2efcaa4ec52dec918cd7e74 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 25 Sep 2018 13:57:43 -0600 Subject: [PATCH 19/20] Updated main.py --- .bin/Scripts/settings/main.py | 93 ++++++++++++++++------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/.bin/Scripts/settings/main.py b/.bin/Scripts/settings/main.py index 4ce4e533..f08d6077 100644 --- a/.bin/Scripts/settings/main.py +++ b/.bin/Scripts/settings/main.py @@ -1,105 +1,96 @@ # Wizard Kit: Settings - Main / Branding # Features -ENABLED_UPLOAD_DATA = False +ENABLED_UPLOAD_DATA = True ENABLED_TICKET_NUMBERS = False # STATIC VARIABLES (also used by BASH and BATCH files) ## NOTE: There are no spaces around the = for easier parsing in BASH and BATCH # Main Kit -ARCHIVE_PASSWORD='Abracadabra' -KIT_NAME_FULL='Wizard Kit' -KIT_NAME_SHORT='WK' -SUPPORT_MESSAGE='Please let 2Shirt know by opening an issue on GitHub' +ARCHIVE_PASSWORD='Sorted1201' +KIT_NAME_FULL='1201-WizardKit' +KIT_NAME_SHORT='1201' +SUPPORT_MESSAGE='Please let us know by opening an issue on Gogs' # osTicket -DB_HOST='127.0.0.1' +DB_HOST='osticket.1201.com' DB_NAME='osticket' DB_USER='wizardkit' -DB_PASS='Abracadabra' +DB_PASS='U9bJnF9eamVkfsVw' SSH_PORT='22' SSH_USER='sql_tunnel' # imgur -IMGUR_CLIENT_ID='' +IMGUR_CLIENT_ID='3d1ee1d38707b85' # Live Linux MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags -ROOT_PASSWORD='Abracadabra' -TECH_PASSWORD='Abracadabra' +ROOT_PASSWORD='1201 loves computers!' +TECH_PASSWORD='Sorted1201' # Server IP addresses -OFFICE_SERVER_IP='10.0.0.10' -QUICKBOOKS_SERVER_IP='10.0.0.10' +OFFICE_SERVER_IP='10.11.1.20' +QUICKBOOKS_SERVER_IP='10.11.1.20' # Time Zones LINUX_TIME_ZONE='America/Los_Angeles' # See 'timedatectl list-timezones' for valid values WINDOWS_TIME_ZONE='Pacific Standard Time' # See 'tzutil /l' for valid values # WiFi -WIFI_SSID='SomeWifi' -WIFI_PASSWORD='Abracadabra' +WIFI_SSID='HamsterFi' +WIFI_PASSWORD='16Hamsters' # SERVER VARIABLES ## NOTE: Windows can only use one user per server. This means that if ## one server serves multiple shares then you have to use the same ## user/password for all of those shares. BACKUP_SERVERS = [ - { 'IP': '10.0.0.10', - 'Name': 'ServerOne', + { 'IP': '10.11.1.20', + 'Name': 'Anaconda', 'Mounted': False, 'Share': 'Backups', - 'User': 'restore', - 'Pass': 'Abracadabra', + 'User': 'cx', + 'Pass': 'cx', 'RW-User': 'backup', - 'RW-Pass': 'Abracadabra', - }, - { 'IP': '10.0.0.11', - 'Name': 'ServerTwo', - 'Mounted': False, - 'Share': 'Backups', - 'User': 'restore', - 'Pass': 'Abracadabra', - 'RW-User': 'backup', - 'RW-Pass': 'Abracadabra', + 'RW-Pass': '1201 loves computers!', }, ] BENCHMARK_SERVER = { - 'Name': 'BenchmarkServer', - 'Short Url': '', - 'Url': '', - 'User': '', + 'Name': 'Nextcloud', + 'Short Url': 'https://1201north.ddns.net:8001/index.php/f/27892', + 'Url': 'https://1201north.ddns.net:8001/public.php/webdav/Benchmarks', + 'User': 'RAE7ajRk25CBnW6', 'Pass': '', } CRASH_SERVER = { - 'Name': 'CrashServer', - 'Url': '', - 'User': '', + 'Name': 'Nextcloud', + 'Url': 'https://1201north.ddns.net:8001/public.php/webdav/WizardKit_Issues', + 'User': 'LoQ97J3r6CFGT2T', 'Pass': '', } OFFICE_SERVER = { 'IP': OFFICE_SERVER_IP, - 'Name': 'ServerOne', + 'Name': 'Anaconda', 'Mounted': False, - 'Share': 'Office', - 'User': 'restore', - 'Pass': 'Abracadabra', + 'Share': r'Public\Office\MS Office', + 'User': 'cx', + 'Pass': 'cx', 'RW-User': 'backup', - 'RW-Pass': 'Abracadabra', + 'RW-Pass': '1201 loves computers!', } QUICKBOOKS_SERVER = { 'IP': QUICKBOOKS_SERVER_IP, - 'Name': 'ServerOne', + 'Name': 'Anaconda', 'Mounted': False, - 'Share': 'QuickBooks', - 'User': 'restore', - 'Pass': 'Abracadabra', + 'Share': r'Public\QuickBooks', + 'User': 'cx', + 'Pass': 'cx', 'RW-User': 'backup', - 'RW-Pass': 'Abracadabra', + 'RW-Pass': '1201 loves computers!', } WINDOWS_SERVER = { - 'IP': '10.0.0.10', - 'Name': 'ServerOne', + 'IP': '10.11.1.20', + 'Name': 'Anaconda', 'Mounted': False, - 'Share': 'Windows', - 'User': 'restore', - 'Pass': 'Abracadabra', + 'Share': r'Public\Windows', + 'User': 'cx', + 'Pass': 'cx', 'RW-User': 'backup', - 'RW-Pass': 'Abracadabra', + 'RW-Pass': '1201 loves computers!', } if __name__ == '__main__': From 787e944cd116a6b0a814277e42ab7fff297fe417 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 25 Sep 2018 14:18:44 -0600 Subject: [PATCH 20/20] Add 1201 Root CA --- .../trust-source/anchors/1201_Root_CA.crt | 36 +++++++++++++++++++ Build Linux | 4 +++ 2 files changed, 40 insertions(+) create mode 100644 .linux_items/include/airootfs/etc/ca-certificates/trust-source/anchors/1201_Root_CA.crt diff --git a/.linux_items/include/airootfs/etc/ca-certificates/trust-source/anchors/1201_Root_CA.crt b/.linux_items/include/airootfs/etc/ca-certificates/trust-source/anchors/1201_Root_CA.crt new file mode 100644 index 00000000..7d8ae206 --- /dev/null +++ b/.linux_items/include/airootfs/etc/ca-certificates/trust-source/anchors/1201_Root_CA.crt @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGTzCCBDegAwIBAgIBfDANBgkqhkiG9w0BAQsFADCBsDELMAkGA1UEBhMCVVMx +DzANBgNVBAgTBk9yZWdvbjERMA8GA1UEBxMIUG9ydGxhbmQxHTAbBgNVBAoTFDEy +MDEgQ29tcHV0ZXIgUmVwYWlyMSMwIQYDVQQLExoxMjAxIENlcnRpZmljYXRlIEF1 +dGhvcml0eTEVMBMGA1UEAxMMMTIwMSBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNt +YW5hZ2VtZW50QDEyMDEuY29tMB4XDTE4MDgyMDA2MDEwMFoXDTM4MDgyMDA2MDEw +MFowgbAxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZPcmVnb24xETAPBgNVBAcTCFBv +cnRsYW5kMR0wGwYDVQQKExQxMjAxIENvbXB1dGVyIFJlcGFpcjEjMCEGA1UECxMa +MTIwMSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFTATBgNVBAMTDDEyMDEgUm9vdCBD +QTEiMCAGCSqGSIb3DQEJARYTbWFuYWdlbWVudEAxMjAxLmNvbTCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBANGYohJk5/CC/p14R7EpnhdEUF7Wvlnt8yuF +dtuyStlIGkLxPMlj9hQfoLDplQqlKBefTaI3WwrI/Hndso+jStLKgtRWRdyNB34K +AWqT04zXYGicdi3fqaMhEC4SPyX1tRXU2e9kjtIJ21AZx2F40NUjfOMKLVymZgXm +gkG1oA/BSzE8vIidrd/lJPwo0u+EYFa87y+9SHS93Ze1AVoTVqUzSMkjqt+6YIzJ +4XBD7UBvps0Mnd18HMUlXHFXusUL1K921W3wDVcMlNIIA8MJjQk+aVS/1EGSn+81 +C+r40x64lYkyh0ZUAHkVXUC/BUfa0SKx1Nfa4mSvtyPnUCb7Dir8MkTDKgopGCok +KmW+VvE2H8AEPCbcctFmhdip19laYxzyDhZ5wiQN6AOg64cWvDf6/uT9hyPvxkj1 +ps5vWElryzawTE7h1BI8liMqwsG1Y7cc6D0PABxPsp4iR8pde0oZtpLnEvejRodo +zz3BGvZjq+pHtRMjL+yiDtdAL+K+7/e7gNCQBIGsphahWIOf7TczWVgMNclTNxl3 +WZjKkOEs7j+prRTDvffV6H32+Tk5TwgMsfvnY4a37CkJ0L0d1JhWj9wO+gESfg3W +8yvt3hfcj3NOUMJWhJstqlIeX8dj7vVcMhjNvYJxabJmJgk+DNlHe55fXDGJ1CLO +E0EbRTyBAgMBAAGjcjBwMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFM+hXjFx +6BldZFBQW1Pn/Yp3vbw+MAsGA1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAAcw +HgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOC +AgEALWcnu3auMSnSSF/kOiLvJ4RAnHZebGYNcUWM14u1K1/XtTB7AFzQIHX7BcDH +m/z4UEyhl9EdR5Bgf2Szuk+8+LyGqcdAdbPoK+bmcwwL8lufDnlIYBThKIBfU2Xw +vw41972B+HH5r1TZXve1EdJaLyImbxmq5s41oH7djGC+sowtyGuVqP7RBguXBGiJ +At1yfdPWVaxLmE8QFknkIvpgTmELpxasTfvgnQBenA3Ts0Z2hwN4796hLbRzGsb8 +4hKWAfQDP0klzXKRRyVeAueXxj/FcNZilYxv15MqMc4qrUiW0hXHluQM1yceNjNZ +SE4Igi1Ap71L4PpgkHIDfZD908UexGGkql+p4EWrpnXUYWTa0sHg1bFKQntgpyFg +86Ug0Q7ZNhImENzeigZknL0ceIdaNUCs7UPrkqpUSJR2yujp1JC3tX1LgKZw8B3J +fQx/8h3zzNuz5dVtr1wUJaUD0nGhMIRBEXb2t4jupEISSTN1pkHPcbNzhAQXjVUA +CJxnnz3jmyGsNCoQf7NWfaN6RSRTWehsC6m7JvPvoU2EZoQkSlNOv4xZuFpEx0u7 +MFDtC1cSGPH7YwYXPVc45xAMC6Ni8mvq93oT89XZNHIqE8/T8aPHLwYFgu1b1r/A +L8oMEnG5s8tG3n0DcFoOYsaIzVeP0r7B7e3zKui6DQLuu9E= +-----END CERTIFICATE----- diff --git a/Build Linux b/Build Linux index af01c928..c65ffed0 100755 --- a/Build Linux +++ b/Build Linux @@ -253,6 +253,10 @@ function update_live_env() { echo "ln -sf '/usr/share/zoneinfo/$LINUX_TIME_ZONE' '/etc/localtime'" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh" echo 'sed -i "s/#FallbackNTP/NTP/" /etc/systemd/timesyncd.conf' >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh" + # Trust root CA(s) + echo "trust extract" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh" + echo "trust extract-compat" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh" + # udevil fix echo "mkdir /media" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"