v1.4.0 - Wizard's New Year
* Added ability to upload crash log to a webdav share * Verified functionailty with Nextcloud 12 * Added missing CBS fix launcher * Rewrote user_data_transfer sections * Added ability to answer "All" to extract items * Fixed issue that caused an infinite recursion involving Windows.old items * Unified code for both image and folder sources by using SourceItem objects * Various bugfixes * Various bugfixes * Improved safety checks for the "Build Linux" script * Updated hw-sensors script to skip all non-temperature sensors * New build-ufd script to combine the three parts of the kit together * See README.md for details * Various bugfixes
This commit is contained in:
commit
f844977d62
27 changed files with 773 additions and 342 deletions
215
.bin/Scripts/build-ufd
Executable file
215
.bin/Scripts/build-ufd
Executable file
|
|
@ -0,0 +1,215 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
## Wizard Kit: UFD Build Tool
|
||||
|
||||
set -o errexit
|
||||
set -o errtrace
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
DEST_DEV="$1"
|
||||
DEST_PARTITION="${DEST_DEV}1"
|
||||
MAIN_PY="/usr/local/bin/settings/main.py"
|
||||
LOG_FILE="${HOME}/build-ufd_${DEST_DEV##*/}_$(date +%Y-%m-%d_%H%M_%z).log"
|
||||
RSYNC_ARGS="-hrtuvS --modify-window=1 --progress"
|
||||
WD=$(pwd)
|
||||
EXTRA_DIR="${WD}/Extras"
|
||||
MAIN_KIT="$(dirname $(find $WD -type d -name '.bin' || true) 2>/dev/null || true)"
|
||||
LINUX_ISO="$((find $WD -maxdepth 1 -type f -iname '*Linux*iso' 2>/dev/null || echo "__Missing__") | sort -r | head -1)"
|
||||
WINPE_ISO="$((find $WD -maxdepth 1 -type f -iname '*WinPE*amd64*iso' 2>/dev/null || echo "__Missing__") | sort -r | head -1)"
|
||||
if [ "${2:-}" == "--silent" ]; then
|
||||
SILENT="True"
|
||||
else
|
||||
SILENT="False"
|
||||
fi
|
||||
|
||||
# COLORS
|
||||
CLEAR="\e[0m"
|
||||
RED="\e[31m"
|
||||
GREEN="\e[32m"
|
||||
YELLOW="\e[33m"
|
||||
BLUE="\e[34m"
|
||||
|
||||
# Functions
|
||||
function ask() {
|
||||
if [[ "${SILENT}" == "True" ]]; then
|
||||
echo -e "${1:-} Yes ${BLUE}(Silent)${CLEAR}"
|
||||
return 0
|
||||
fi
|
||||
while :; do
|
||||
read -p "${1:-} [Y/N] " -r answer
|
||||
if echo "$answer" | egrep -iq '^(y|yes|sure)$'; then
|
||||
return 0
|
||||
elif echo "$answer" | egrep -iq '^(n|no|nope)$'; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
die () {
|
||||
echo "$@" >&2
|
||||
echo ""
|
||||
read -p "Press Enter to exit... " -r
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Load main.py settings
|
||||
if [ ! -f "$MAIN_PY" ]; then
|
||||
echo -e "${RED}ERROR${CLEAR}: $MAIN_PY not found."
|
||||
die "Aborted."
|
||||
fi
|
||||
while read line; do
|
||||
if echo "$line" | egrep -q "^\w+='"; then
|
||||
line="$(echo "$line" | sed -r 's/[\r\n]+//')"
|
||||
eval "$line"
|
||||
fi
|
||||
done < "$MAIN_PY"
|
||||
if [ -z ${KIT_NAME_FULL+x} ]; then
|
||||
# KIT_NAME_FULL is not set, assume main.py missing or malformatted
|
||||
echo -e "${RED}ERROR${CLEAR}: failed to load settings from $MAIN_PY"
|
||||
die "Aborted."
|
||||
fi
|
||||
UFD_LABEL="${KIT_NAME_SHORT}_LINUX"
|
||||
|
||||
# Check if root
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
echo -e "${RED}ERROR${CLEAR}: This script must be run as root."
|
||||
die "Aborted."
|
||||
fi
|
||||
|
||||
# Check if in tmux
|
||||
if ! tmux list-session 2>/dev/null | grep -q "build-ufd"; then
|
||||
# Reload in tmux
|
||||
tmux new-session -s "build-ufd" "${0:-}" $*
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Header
|
||||
echo -e "${GREEN}$KIT_NAME_FULL${CLEAR}: UFD Build Tool"
|
||||
echo ""
|
||||
|
||||
# Dest and Sources Check
|
||||
abort="False"
|
||||
if [ ! -b "$DEST_DEV" ]; then
|
||||
echo -e "${RED}ERROR${CLEAR}: Device $DEST_DEV not found."
|
||||
abort="True"
|
||||
fi
|
||||
if [ ! -f "$LINUX_ISO" ]; then
|
||||
echo -e "${RED}ERROR${CLEAR}: Linux ISO not found."
|
||||
abort="True"
|
||||
fi
|
||||
if [ ! -f "$WINPE_ISO" ]; then
|
||||
echo -e "${RED}ERROR${CLEAR}: WinPE ISO not found."
|
||||
abort="True"
|
||||
fi
|
||||
if [ ! -d "$MAIN_KIT" ]; then
|
||||
echo -e "${RED}ERROR${CLEAR}: Wizard Kit directory not found."
|
||||
abort="True"
|
||||
fi
|
||||
if [ ! -d "$EXTRA_DIR" ]; then
|
||||
# Warn but don't abort
|
||||
echo -e "${YELLOW}WARNING${CLEAR}: $EXTRA_DIR not found."
|
||||
echo ""
|
||||
EXTRA_DIR='__None__'
|
||||
fi
|
||||
if [ "$abort" == "True" ]; then
|
||||
echo ""
|
||||
die "Aborted."
|
||||
fi
|
||||
|
||||
# Display Job Settings
|
||||
echo -e "${BLUE}Sources${CLEAR}"
|
||||
echo "Main Kit: $MAIN_KIT"
|
||||
echo "Linux ISO: $LINUX_ISO"
|
||||
echo "WinPE ISO: $WINPE_ISO"
|
||||
echo "Extras: $EXTRA_DIR"
|
||||
echo ""
|
||||
echo -e "${BLUE}Destination${CLEAR}"
|
||||
lsblk -n -o NAME,LABEL,SIZE,MODEL,SERIAL $DEST_DEV
|
||||
|
||||
# Ask before starting job
|
||||
echo ""
|
||||
if ask "Is the above information correct?"; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}SAFETY CHECK${CLEAR}"
|
||||
echo "All data will be DELETED from the disk and partition(s) listed above."
|
||||
echo -e "This is irreversible and will lead to ${RED}DATA LOSS.${CLEAR}"
|
||||
if ! ask "Asking again to confirm, is this correct?"; then
|
||||
die "Aborted."
|
||||
fi
|
||||
else
|
||||
die "Aborted."
|
||||
fi
|
||||
|
||||
# Start Build
|
||||
echo ""
|
||||
echo -e "${GREEN}Building Kit${CLEAR}"
|
||||
touch "$LOG_FILE"
|
||||
tmux split-window -dl 10 tail -f "$LOG_FILE"
|
||||
|
||||
# Format
|
||||
echo "Formatting drive..."
|
||||
parted "$DEST_DEV" -s -- mklabel msdos mkpart primary fat32 1MiB -1s >> "$LOG_FILE" 2>&1
|
||||
parted "$DEST_DEV" set 1 boot on >> "$LOG_FILE" 2>&1
|
||||
mkfs.vfat -F 32 -n "$UFD_LABEL" "$DEST_PARTITION" >> "$LOG_FILE" 2>&1
|
||||
|
||||
# Mount sources and dest
|
||||
echo "Mounting sources and destination..."
|
||||
mkdir /mnt/{Dest,Linux,WinPE} -p >> "$LOG_FILE" 2>&1
|
||||
mount $DEST_PARTITION /mnt/Dest >> "$LOG_FILE" 2>&1
|
||||
mount "$LINUX_ISO" /mnt/Linux -r >> "$LOG_FILE" 2>&1
|
||||
mount "$WINPE_ISO" /mnt/WinPE -r >> "$LOG_FILE" 2>&1
|
||||
|
||||
# Copy files
|
||||
echo "Copying Linux files..."
|
||||
rsync ${RSYNC_ARGS} /mnt/Linux/* /mnt/Dest/ >> "$LOG_FILE" 2>&1
|
||||
|
||||
echo "Copying WinPE files..."
|
||||
rsync ${RSYNC_ARGS} /mnt/WinPE/{Boot,bootmgr{,.efi},en-us,sources} /mnt/Dest/ >> "$LOG_FILE" 2>&1
|
||||
rsync ${RSYNC_ARGS} /mnt/WinPE/EFI/Microsoft /mnt/Dest/EFI/ >> "$LOG_FILE" 2>&1
|
||||
rsync ${RSYNC_ARGS} /mnt/WinPE/EFI/Boot/* /mnt/Dest/EFI/Microsoft/ >> "$LOG_FILE" 2>&1
|
||||
rsync ${RSYNC_ARGS} /mnt/WinPE/{Boot/{BCD,boot.sdi},bootmgr} /mnt/Dest/sources/ >> "$LOG_FILE" 2>&1
|
||||
|
||||
echo "Copying Main Kit..."
|
||||
rsync ${RSYNC_ARGS} "$MAIN_KIT/" "/mnt/Dest/$KIT_NAME_FULL/" >> "$LOG_FILE" 2>&1
|
||||
if [ "$EXTRA_DIR" != "__None__" ]; then
|
||||
echo "Copying Extra files..."
|
||||
rsync ${RSYNC_ARGS} "$EXTRA_DIR"/* /mnt/Dest/ >> "$LOG_FILE" 2>&1
|
||||
fi
|
||||
|
||||
# Install syslinux
|
||||
echo "Copying Syslinux files..."
|
||||
rsync ${RSYNC_ARGS} /usr/lib/syslinux/bios/*.c32 /mnt/Dest/arch/boot/syslinux/ >> "$LOG_FILE" 2>&1
|
||||
syslinux --install -d /arch/boot/syslinux/ $DEST_PARTITION >> "$LOG_FILE" 2>&1
|
||||
|
||||
echo "Unmounting destination..."
|
||||
umount /mnt/Dest >> "$LOG_FILE" 2>&1
|
||||
sync
|
||||
|
||||
echo "Installing Syslinux MBR..."
|
||||
dd bs=440 count=1 if=/usr/lib/syslinux/bios/mbr.bin of=$DEST_DEV >> "$LOG_FILE" 2>&1
|
||||
sync
|
||||
|
||||
# Cleanup
|
||||
echo "Hiding boot files..."
|
||||
echo "drive s: file=\"$DEST_PARTITION\"" > /root/.mtoolsrc
|
||||
echo 'mtools_skip_check=1' >> /root/.mtoolsrc
|
||||
for item in boot{,mgr,mgr.efi} efi en-us images isolinux sources "$KIT_NAME_FULL"/{.bin,.cbin}; do
|
||||
yes | mattrib +h "S:/$item" >> "$LOG_FILE" 2>&1 || true
|
||||
done
|
||||
sync
|
||||
|
||||
# Unmount Sources
|
||||
echo "Unmounting sources..."
|
||||
umount /mnt/{Linux,WinPE} -R >> "$LOG_FILE" 2>&1
|
||||
|
||||
# Close progress pane
|
||||
pkill -f "tail.*$LOG_FILE"
|
||||
|
||||
# Done
|
||||
echo ""
|
||||
echo "Done."
|
||||
echo ""
|
||||
read -p "Press Enter to exit..." -r
|
||||
exit 0
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ $Host.UI.RawUI.WindowTitle = "Wizard Kit: Windows PE Build Tool"
|
|||
$WD = $(Split-Path $MyInvocation.MyCommand.Path)
|
||||
$Bin = (Get-Item $WD -Force).Parent.FullName
|
||||
$Root = (Get-Item $Bin -Force).Parent.FullName
|
||||
$Build = "$Root\BUILD"
|
||||
$Build = "$Root\BUILD_PE"
|
||||
$LogDir = "$Build\Logs"
|
||||
$Temp = "$Build\Temp"
|
||||
$Date = Get-Date -UFormat "%Y-%m-%d"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ if __name__ == '__main__':
|
|||
|
||||
# Show details
|
||||
print_info('{}: CBS Cleanup Tool\n'.format(KIT_NAME_FULL))
|
||||
show_info('Backup / Temp path:', dest)
|
||||
show_data('Backup / Temp path:', dest)
|
||||
print_standard('\n')
|
||||
if (not ask('Proceed with CBS cleanup?')):
|
||||
abort()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ REGEX_BAD_PATH_NAMES = re.compile(
|
|||
re.IGNORECASE)
|
||||
|
||||
def backup_partition(disk, par):
|
||||
"""Create a backup image of a partition."""
|
||||
if par.get('Image Exists', False) or par['Number'] in disk['Bad Partitions']:
|
||||
raise GenericAbort
|
||||
|
||||
|
|
@ -28,9 +29,15 @@ def backup_partition(disk, par):
|
|||
run_program(cmd)
|
||||
|
||||
def fix_path(path):
|
||||
"""Replace invalid filename characters with underscores."""
|
||||
return REGEX_BAD_PATH_NAMES.sub('_', path)
|
||||
|
||||
def prep_disk_for_backup(destination, disk, ticket_number):
|
||||
"""Gather details about the disk and its partitions.
|
||||
|
||||
This includes partitions that can't be backed up,
|
||||
whether backups already exist on the BACKUP_SERVER,
|
||||
partition names/sizes/used space, etc."""
|
||||
disk['Clobber Risk'] = []
|
||||
width = len(str(len(disk['Partitions'])))
|
||||
|
||||
|
|
@ -102,7 +109,7 @@ def prep_disk_for_backup(destination, disk, ticket_number):
|
|||
disk['Backup Warnings'] = warnings
|
||||
|
||||
def select_backup_destination(auto_select=True):
|
||||
# Build menu
|
||||
"""Select a backup destination from a menu, returns server dict."""
|
||||
destinations = [s for s in BACKUP_SERVERS if s['Mounted']]
|
||||
actions = [
|
||||
{'Name': 'Main Menu', 'Letter': 'M'},
|
||||
|
|
@ -136,6 +143,7 @@ def select_backup_destination(auto_select=True):
|
|||
return destinations[int(selection)-1]
|
||||
|
||||
def verify_wim_backup(partition):
|
||||
"""Verify WIM integrity."""
|
||||
if not os.path.exists(partition['Image Path']):
|
||||
raise PathNotFoundError
|
||||
cmd = [
|
||||
|
|
|
|||
|
|
@ -93,6 +93,34 @@ def ask(prompt='Kotaero!'):
|
|||
print_log(message=message)
|
||||
return answer
|
||||
|
||||
def choice(choices, prompt='Kotaero!'):
|
||||
"""Prompt the user with a choice question, log answer, and returns str."""
|
||||
answer = None
|
||||
choices = [str(c) for c in choices]
|
||||
choices_short = {c[:1].upper(): c for c in choices}
|
||||
prompt = '{} [{}]: '.format(prompt, '/'.join(choices))
|
||||
regex = '^({}|{})$'.format(
|
||||
'|'.join([c[:1] for c in choices]),
|
||||
'|'.join(choices))
|
||||
|
||||
# Get user's choice
|
||||
while answer is None:
|
||||
tmp = input(prompt)
|
||||
if re.search(regex, tmp, re.IGNORECASE):
|
||||
answer = tmp
|
||||
|
||||
# Log result
|
||||
message = '{prompt}{answer_text}'.format(
|
||||
prompt = prompt,
|
||||
answer_text = 'Yes' if answer else 'No')
|
||||
print_log(message=message)
|
||||
|
||||
# Fix answer formatting to match provided values
|
||||
answer = choices_short[answer[:1].upper()]
|
||||
|
||||
# Done
|
||||
return answer
|
||||
|
||||
def clear_screen():
|
||||
"""Simple wrapper for cls/clear."""
|
||||
if psutil.WINDOWS:
|
||||
|
|
@ -227,7 +255,20 @@ def major_exception():
|
|||
print_warning(SUPPORT_MESSAGE)
|
||||
print(traceback.format_exc())
|
||||
print_log(traceback.format_exc())
|
||||
sleep(30)
|
||||
try:
|
||||
upload_crash_details()
|
||||
except GenericAbort:
|
||||
# User declined upload
|
||||
print_warning('Upload: Aborted')
|
||||
sleep(30)
|
||||
except GenericError:
|
||||
# No log file or uploading disabled
|
||||
sleep(30)
|
||||
except:
|
||||
print_error('Upload: NS')
|
||||
sleep(30)
|
||||
else:
|
||||
print_success('Upload: CS')
|
||||
pause('Press Enter to exit...')
|
||||
exit_script(1)
|
||||
|
||||
|
|
@ -358,6 +399,7 @@ def print_warning(*args, **kwargs):
|
|||
print_standard(*args, color=COLORS['YELLOW'], **kwargs)
|
||||
|
||||
def print_log(message='', end='\n', timestamp=True):
|
||||
"""Writes message to a log if LogFile is set."""
|
||||
time_str = time.strftime("%Y-%m-%d %H%M%z: ") if timestamp else ''
|
||||
if 'LogFile' in global_vars and global_vars['LogFile']:
|
||||
with open(global_vars['LogFile'], 'a', encoding='utf-8') as f:
|
||||
|
|
@ -406,9 +448,6 @@ def show_data(message='~Some message~', data='~Some data~', indent=8, width=32,
|
|||
else:
|
||||
print_standard(message)
|
||||
|
||||
def show_info(message='~Some message~', info='~Some info~', indent=8, width=32):
|
||||
show_data(message=message, data=info, indent=indent, width=width)
|
||||
|
||||
def sleep(seconds=2):
|
||||
"""Wait for a while."""
|
||||
time.sleep(seconds)
|
||||
|
|
@ -487,58 +526,43 @@ def try_and_print(message='Trying...',
|
|||
else:
|
||||
return {'CS': not bool(err), 'Error': err, 'Out': out}
|
||||
|
||||
def upload_data(path, file):
|
||||
"""Add CLIENT_INFO_SERVER to authorized connections and upload file."""
|
||||
def upload_crash_details():
|
||||
"""Upload log and runtime data to the CRASH_SERVER.
|
||||
|
||||
Intended for uploading to a public Nextcloud share."""
|
||||
if not ENABLED_UPLOAD_DATA:
|
||||
raise GenericError('Feature disabled.')
|
||||
raise GenericError
|
||||
|
||||
extract_item('PuTTY', filter='wizkit.ppk psftp.exe', silent=True)
|
||||
import requests
|
||||
if 'LogFile' in global_vars and global_vars['LogFile']:
|
||||
if ask('Upload crash details to {}?'.format(CRASH_SERVER['Name'])):
|
||||
with open(global_vars['LogFile']) as f:
|
||||
data = '''{}
|
||||
#############################
|
||||
Runtime Details:
|
||||
|
||||
# Authorize connection to the server
|
||||
winreg.CreateKey(HKCU, r'Software\SimonTatham\PuTTY\SshHostKeys')
|
||||
with winreg.OpenKey(HKCU, r'Software\SimonTatham\PuTTY\SshHostKeys',
|
||||
access=winreg.KEY_WRITE) as key:
|
||||
winreg.SetValueEx(key,
|
||||
'rsa2@22:{IP}'.format(**CLIENT_INFO_SERVER), 0,
|
||||
winreg.REG_SZ, CLIENT_INFO_SERVER['RegEntry'])
|
||||
sys.argv: {}
|
||||
|
||||
# Write batch file
|
||||
with open(r'{}\psftp.batch'.format(global_vars['TmpDir']),
|
||||
'w', encoding='ascii') as f:
|
||||
f.write('lcd "{path}"\n'.format(path=path))
|
||||
f.write('cd "{Share}"\n'.format(**CLIENT_INFO_SERVER))
|
||||
f.write('mkdir {TicketNumber}\n'.format(**global_vars))
|
||||
f.write('cd {TicketNumber}\n'.format(**global_vars))
|
||||
f.write('put "{file}"\n'.format(file=file))
|
||||
|
||||
# Upload Info
|
||||
cmd = [
|
||||
global_vars['Tools']['PuTTY-PSFTP'],
|
||||
'-noagent',
|
||||
'-i', r'{BinDir}\PuTTY\wizkit.ppk'.format(**global_vars),
|
||||
'{User}@{IP}'.format(**CLIENT_INFO_SERVER),
|
||||
'-b', r'{TmpDir}\psftp.batch'.format(**global_vars)]
|
||||
run_program(cmd)
|
||||
|
||||
def upload_info():
|
||||
"""Upload compressed Info file to the NAS as set in settings.main.py."""
|
||||
if not ENABLED_UPLOAD_DATA:
|
||||
raise GenericError('Feature disabled.')
|
||||
|
||||
path = '{ClientDir}'.format(**global_vars)
|
||||
file = 'Info_{Date-Time}.7z'.format(**global_vars)
|
||||
upload_data(path, file)
|
||||
|
||||
def compress_info():
|
||||
"""Compress ClientDir info folders with 7-Zip for upload_info()."""
|
||||
path = '{ClientDir}'.format(**global_vars)
|
||||
file = 'Info_{Date-Time}.7z'.format(**global_vars)
|
||||
_cmd = [
|
||||
global_vars['Tools']['SevenZip'],
|
||||
'a', '-t7z', '-mx=9', '-bso0', '-bse0',
|
||||
r'{}\{}'.format(path, file),
|
||||
r'{ClientDir}\Info'.format(**global_vars)]
|
||||
run_program(_cmd)
|
||||
global_vars: {}'''.format(f.read(), sys.argv, global_vars)
|
||||
filename = global_vars.get('LogFile', 'Unknown')
|
||||
filename = re.sub(r'.*(\\|/)', '', filename)
|
||||
filename += '.txt'
|
||||
url = '{}/Crash_{}__{}'.format(
|
||||
CRASH_SERVER['Url'],
|
||||
global_vars.get('Date-Time', 'Unknown Date-Time'),
|
||||
filename)
|
||||
r = requests.put(url, data=data,
|
||||
headers = {'X-Requested-With': 'XMLHttpRequest'},
|
||||
auth = (CRASH_SERVER['User'], CRASH_SERVER['Pass']))
|
||||
# Raise exception if upload NS
|
||||
if not r.ok:
|
||||
raise Exception
|
||||
else:
|
||||
# User said no
|
||||
raise GenericAbort
|
||||
else:
|
||||
# No LogFile defined (or invalid LogFile)
|
||||
raise GenericError
|
||||
|
||||
def wait_for_process(name, poll_rate=3):
|
||||
"""Wait for process by name."""
|
||||
|
|
@ -742,6 +766,9 @@ def set_common_vars():
|
|||
**global_vars)
|
||||
|
||||
def set_linux_vars():
|
||||
"""Set common variables in a Linux environment.
|
||||
|
||||
These assume we're running under a WK-Linux build."""
|
||||
result = run_program(['mktemp', '-d'])
|
||||
global_vars['TmpDir'] = result.stdout.decode().strip()
|
||||
global_vars['Date'] = time.strftime("%Y-%m-%d")
|
||||
|
|
@ -749,6 +776,10 @@ def set_linux_vars():
|
|||
global_vars['Env'] = os.environ.copy()
|
||||
global_vars['BinDir'] = '/usr/local/bin'
|
||||
global_vars['LogDir'] = global_vars['TmpDir']
|
||||
global_vars['Tools'] = {
|
||||
'wimlib-imagex': 'wimlib-imagex',
|
||||
'SevenZip': '7z',
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("This file is not meant to be called directly.")
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ class LocalDisk():
|
|||
# Should always be true
|
||||
return True
|
||||
|
||||
class SourceItem():
|
||||
def __init__(self, name, path):
|
||||
self.name = name
|
||||
self.path = path
|
||||
|
||||
# Regex
|
||||
REGEX_EXCL_ITEMS = re.compile(
|
||||
r'^(\.(AppleDB|AppleDesktop|AppleDouble'
|
||||
|
|
@ -29,7 +34,7 @@ REGEX_EXCL_ITEMS = re.compile(
|
|||
r'|Thumbs\.db)$',
|
||||
re.IGNORECASE)
|
||||
REGEX_EXCL_ROOT_ITEMS = re.compile(
|
||||
r'^\\?(boot(mgr|nxt)$|Config.msi'
|
||||
r'^(boot(mgr|nxt)$|Config.msi'
|
||||
r'|(eula|globdata|install|vc_?red)'
|
||||
r'|.*.sys$|System Volume Information|RECYCLER?|\$Recycle\.bin'
|
||||
r'|\$?Win(dows(.old.*|\.~BT|)$|RE_)|\$GetCurrent|Windows10Upgrade'
|
||||
|
|
@ -37,7 +42,7 @@ REGEX_EXCL_ROOT_ITEMS = re.compile(
|
|||
r'|.*\.(esd|swm|wim|dd|map|dmg|image)$)',
|
||||
re.IGNORECASE)
|
||||
REGEX_INCL_ROOT_ITEMS = re.compile(
|
||||
r'^\\?(AdwCleaner|(My\s*|)(Doc(uments?( and Settings|)|s?)|Downloads'
|
||||
r'^(AdwCleaner|(My\s*|)(Doc(uments?( and Settings|)|s?)|Downloads'
|
||||
r'|Media|Music|Pic(ture|)s?|Vid(eo|)s?)'
|
||||
r'|{prefix}(-?Info|-?Transfer|)'
|
||||
r'|(ProgramData|Recovery|Temp.*|Users)$'
|
||||
|
|
@ -48,7 +53,7 @@ REGEX_WIM_FILE = re.compile(
|
|||
r'\.wim$',
|
||||
re.IGNORECASE)
|
||||
REGEX_WINDOWS_OLD = re.compile(
|
||||
r'^\\Win(dows|)\.old',
|
||||
r'^Win(dows|)\.old',
|
||||
re.IGNORECASE)
|
||||
|
||||
# STATIC VARIABLES
|
||||
|
|
@ -234,7 +239,7 @@ def mount_all_volumes():
|
|||
line.append(info)
|
||||
return report
|
||||
|
||||
def mount_backup_shares():
|
||||
def mount_backup_shares(read_write=False):
|
||||
"""Mount the backup shares unless labeled as already mounted."""
|
||||
if psutil.LINUX:
|
||||
mounted_data = get_mounted_data()
|
||||
|
|
@ -253,12 +258,22 @@ def mount_backup_shares():
|
|||
print_warning(mounted_str)
|
||||
continue
|
||||
|
||||
mount_network_share(server)
|
||||
mount_network_share(server, read_write)
|
||||
|
||||
def mount_network_share(server):
|
||||
def mount_network_share(server, read_write=False):
|
||||
"""Mount a network share defined by server."""
|
||||
if read_write:
|
||||
username = server['RW-User']
|
||||
password = server['RW-Pass']
|
||||
else:
|
||||
username = server['User']
|
||||
password = server['Pass']
|
||||
if psutil.WINDOWS:
|
||||
cmd = r'net use \\{IP}\{Share} /user:{User} {Pass}'.format(**server)
|
||||
cmd = r'net use \\{ip}\{share} /user:{username} {password}'.format(
|
||||
ip = server['IP'],
|
||||
share = server['Share'],
|
||||
username = username,
|
||||
password = password)
|
||||
cmd = cmd.split(' ')
|
||||
warning = r'Failed to mount \\{Name}\{Share}, {IP} unreachable.'.format(
|
||||
**server)
|
||||
|
|
@ -273,7 +288,10 @@ def mount_network_share(server):
|
|||
'sudo', 'mount',
|
||||
'//{IP}/{Share}'.format(**server),
|
||||
'/Backups/{Name}'.format(**server),
|
||||
'-o', 'username={User},password={Pass}'.format(**server)]
|
||||
'-o', '{}username={},password={}'.format(
|
||||
'' if read_write else 'ro,',
|
||||
username,
|
||||
password)]
|
||||
warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format(
|
||||
**server)
|
||||
error = 'Failed to mount /Backups/{Name}'.format(**server)
|
||||
|
|
@ -334,150 +352,163 @@ def run_wimextract(source, items, dest):
|
|||
'--nullglob']
|
||||
run_program(cmd)
|
||||
|
||||
def scan_source(source_obj, dest_path):
|
||||
"""Scan source for files/folders to transfer."""
|
||||
selected_items = []
|
||||
|
||||
def list_source_items(source_obj, rel_path=None):
|
||||
"""List items in a dir or WIM, returns a list of SourceItem objects."""
|
||||
items = []
|
||||
rel_path = '{}{}'.format(os.sep, rel_path) if rel_path else ''
|
||||
if source_obj.is_dir():
|
||||
# File-Based
|
||||
print_standard('Scanning source (folder): {}'.format(source_obj.path))
|
||||
selected_items = scan_source_path(source_obj.path, dest_path)
|
||||
source_path = '{}{}'.format(source_obj.path, rel_path)
|
||||
items = [SourceItem(name=item.name, path=item.path)
|
||||
for item in os.scandir(source_path)]
|
||||
else:
|
||||
# Image-Based
|
||||
if REGEX_WIM_FILE.search(source_obj.name):
|
||||
print_standard('Scanning source (image): {}'.format(
|
||||
source_obj.path))
|
||||
selected_items = scan_source_wim(source_obj.path, dest_path)
|
||||
else:
|
||||
print_error('ERROR: Unsupported image: {}'.format(
|
||||
source_obj.path))
|
||||
raise GenericError
|
||||
# Prep wimlib-imagex
|
||||
if psutil.WINDOWS:
|
||||
extract_item('wimlib', silent=True)
|
||||
cmd = [
|
||||
global_vars['Tools']['wimlib-imagex'], 'dir',
|
||||
source_obj.path, '1']
|
||||
if rel_path:
|
||||
cmd.append('--path={}'.format(rel_path))
|
||||
|
||||
return selected_items
|
||||
# Get item list
|
||||
try:
|
||||
items = run_program(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
print_error('ERROR: Failed to get file list.')
|
||||
raise
|
||||
|
||||
def scan_source_path(source_path, dest_path, rel_path=None, interactive=True):
|
||||
"""Scan source folder for files/folders to transfer, returns list.
|
||||
|
||||
This will scan the root and (recursively) any Windows.old folders."""
|
||||
rel_path = '\\' + rel_path if rel_path else ''
|
||||
if rel_path:
|
||||
dest_path = dest_path + rel_path
|
||||
selected_items = []
|
||||
win_olds = []
|
||||
|
||||
# Root items
|
||||
root_items = []
|
||||
for item in os.scandir(source_path):
|
||||
if REGEX_INCL_ROOT_ITEMS.search(item.name):
|
||||
root_items.append(item.path)
|
||||
elif not REGEX_EXCL_ROOT_ITEMS.search(item.name):
|
||||
if (not interactive
|
||||
or ask('Copy: "{}{}" ?'.format(rel_path, item.name))):
|
||||
root_items.append(item.path)
|
||||
if REGEX_WINDOWS_OLD.search(item.name):
|
||||
win_olds.append(item)
|
||||
if root_items:
|
||||
selected_items.append({
|
||||
'Message': '{}Root Items...'.format(rel_path),
|
||||
'Items': root_items.copy(),
|
||||
'Destination': dest_path})
|
||||
|
||||
# Fonts
|
||||
if os.path.exists(r'{}\Windows\Fonts'.format(source_path)):
|
||||
selected_items.append({
|
||||
'Message': '{}Fonts...'.format(rel_path),
|
||||
'Items': [r'{}\Windows\Fonts'.format(rel_path)],
|
||||
'Destination': r'{}\Windows'.format(dest_path)})
|
||||
|
||||
# Registry
|
||||
registry_items = []
|
||||
for folder in ['config', 'OEM']:
|
||||
folder = r'Windows\System32\{}'.format(folder)
|
||||
folder = os.path.join(source_path, folder)
|
||||
if os.path.exists(folder):
|
||||
registry_items.append(folder)
|
||||
if registry_items:
|
||||
selected_items.append({
|
||||
'Message': '{}Registry...'.format(rel_path),
|
||||
'Items': registry_items.copy(),
|
||||
'Destination': r'{}\Windows\System32'.format(dest_path)})
|
||||
|
||||
# Windows.old(s)
|
||||
for old in win_olds:
|
||||
selected_items.append(
|
||||
scan_source_path(
|
||||
old.path, dest_path, rel_path=old.name, interactive=False))
|
||||
# Strip non-root items
|
||||
items = [re.sub(r'(\\|/)', os.sep, i.strip())
|
||||
for i in items.stdout.decode('utf-8', 'ignore').splitlines()]
|
||||
if rel_path:
|
||||
items = [i.replace(rel_path, '') for i in items]
|
||||
items = [i for i in items
|
||||
if i.count(os.sep) == 1 and i.strip() != os.sep]
|
||||
items = [SourceItem(name=i[1:], path=rel_path+i) for i in items]
|
||||
|
||||
# Done
|
||||
return selected_items
|
||||
return items
|
||||
|
||||
def scan_source_wim(source_wim, dest_path, rel_path=None, interactive=True):
|
||||
"""Scan source WIM file for files/folders to transfer, returns list.
|
||||
def scan_source(source_obj, dest_path, rel_path='', interactive=True):
|
||||
"""Scan source for files/folders to transfer, returns list.
|
||||
|
||||
This will scan the root and (recursively) any Windows.old folders."""
|
||||
rel_path = '\\' + rel_path if rel_path else ''
|
||||
selected_items = []
|
||||
win_olds = []
|
||||
|
||||
# Scan source
|
||||
extract_item('wimlib', silent=True)
|
||||
cmd = [
|
||||
global_vars['Tools']['wimlib-imagex'], 'dir',
|
||||
source_wim, '1']
|
||||
try:
|
||||
file_list = run_program(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
print_error('ERROR: Failed to get file list.')
|
||||
raise
|
||||
|
||||
# Root Items
|
||||
file_list = [i.strip()
|
||||
for i in file_list.stdout.decode('utf-8', 'ignore').splitlines()
|
||||
if i.count('\\') == 1 and i.strip() != '\\']
|
||||
root_items = []
|
||||
if rel_path:
|
||||
file_list = [i.replace(rel_path, '') for i in file_list]
|
||||
for item in file_list:
|
||||
if REGEX_INCL_ROOT_ITEMS.search(item):
|
||||
root_items.append(item)
|
||||
elif not REGEX_EXCL_ROOT_ITEMS.search(item):
|
||||
if (not interactive
|
||||
or ask('Extract: "{}{}" ?'.format(rel_path, item))):
|
||||
root_items.append('{}{}'.format(rel_path, item))
|
||||
if REGEX_WINDOWS_OLD.search(item):
|
||||
item_list = list_source_items(source_obj, rel_path)
|
||||
for item in item_list:
|
||||
if REGEX_INCL_ROOT_ITEMS.search(item.name):
|
||||
print_success('Auto-Selected: {}'.format(item.path))
|
||||
root_items.append('{}'.format(item.path))
|
||||
elif not REGEX_EXCL_ROOT_ITEMS.search(item.name):
|
||||
if not interactive:
|
||||
print_success('Auto-Selected: {}'.format(item.path))
|
||||
root_items.append('{}'.format(item.path))
|
||||
else:
|
||||
prompt = 'Transfer: "{}{}{}" ?'.format(
|
||||
rel_path,
|
||||
os.sep if rel_path else '',
|
||||
item.name)
|
||||
choices = ['Yes', 'No', 'All', 'Quit']
|
||||
answer = choice(prompt=prompt, choices=choices)
|
||||
if answer == 'Quit':
|
||||
abort()
|
||||
elif answer == 'All':
|
||||
interactive = False
|
||||
if answer in ['Yes', 'All']:
|
||||
root_items.append('{}'.format(item.path))
|
||||
if REGEX_WINDOWS_OLD.search(item.name):
|
||||
item.name = '{}{}{}'.format(
|
||||
rel_path,
|
||||
os.sep if rel_path else '',
|
||||
item.name)
|
||||
win_olds.append(item)
|
||||
if root_items:
|
||||
selected_items.append({
|
||||
'Message': '{}Root Items...'.format(rel_path),
|
||||
'Message': '{}{}Root Items...'.format(
|
||||
rel_path,
|
||||
' ' if rel_path else ''),
|
||||
'Items': root_items.copy(),
|
||||
'Destination': dest_path})
|
||||
|
||||
# Fonts
|
||||
if wim_contains(source_wim, r'{}Windows\Fonts'.format(rel_path)):
|
||||
font_obj = get_source_item_obj(source_obj, rel_path, 'Windows/Fonts')
|
||||
if font_obj:
|
||||
selected_items.append({
|
||||
'Message': '{}Fonts...'.format(rel_path),
|
||||
'Items': [r'{}\Windows\Fonts'.format(rel_path)],
|
||||
'Message': '{}{}Fonts...'.format(
|
||||
rel_path,
|
||||
' ' if rel_path else ''),
|
||||
'Items': [font_obj.path],
|
||||
'Destination': dest_path})
|
||||
|
||||
# Registry
|
||||
registry_items = []
|
||||
for folder in ['config', 'OEM']:
|
||||
folder = r'{}Windows\System32\{}'.format(rel_path, folder)
|
||||
if wim_contains(source_wim, folder):
|
||||
registry_items.append(folder)
|
||||
folder_obj = get_source_item_obj(
|
||||
source_obj, rel_path, 'Windows/System32/{}'.format(folder))
|
||||
if folder_obj:
|
||||
registry_items.append(folder_obj.path)
|
||||
if registry_items:
|
||||
selected_items.append({
|
||||
'Message': '{}Registry...'.format(rel_path),
|
||||
'Message': '{}{}Registry...'.format(
|
||||
rel_path,
|
||||
' ' if rel_path else ''),
|
||||
'Items': registry_items.copy(),
|
||||
'Destination': dest_path})
|
||||
|
||||
# Windows.old(s)
|
||||
for old in win_olds:
|
||||
scan_source_wim(source_wim, dest_path, rel_path=old, interactive=False)
|
||||
selected_items.extend(scan_source(
|
||||
source_obj,
|
||||
dest_path,
|
||||
rel_path=old.name,
|
||||
interactive=False))
|
||||
|
||||
# Done
|
||||
return selected_items
|
||||
|
||||
def get_source_item_obj(source_obj, rel_path, item_path):
|
||||
"""Check if the item exists and return a SourceItem object if it does."""
|
||||
item_obj = None
|
||||
item_path = re.sub(r'(\\|/)', os.sep, item_path)
|
||||
if source_obj.is_dir():
|
||||
item_obj = SourceItem(
|
||||
name = item_path,
|
||||
path = '{}{}{}{}{}'.format(
|
||||
source_obj.path,
|
||||
os.sep,
|
||||
rel_path,
|
||||
os.sep if rel_path else '',
|
||||
item_path))
|
||||
if not os.path.exists(item_obj.path):
|
||||
item_obj = None
|
||||
else:
|
||||
# Assuming WIM file
|
||||
if psutil.WINDOWS:
|
||||
extract_item('wimlib', silent=True)
|
||||
cmd = [
|
||||
global_vars['Tools']['wimlib-imagex'], 'dir',
|
||||
source_obj.path, '1',
|
||||
'--path={}'.format(item_path),
|
||||
'--one-file-only']
|
||||
try:
|
||||
run_program(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
# function will return None below
|
||||
pass
|
||||
else:
|
||||
item_obj = SourceItem(
|
||||
name = item_path,
|
||||
path = '{}{}{}{}'.format(
|
||||
os.sep,
|
||||
rel_path,
|
||||
os.sep if rel_path else '',
|
||||
item_path))
|
||||
return item_obj
|
||||
|
||||
def select_destination(folder_path, prompt='Select destination'):
|
||||
"""Select destination drive, returns path as string."""
|
||||
disk = select_volume(prompt)
|
||||
|
|
@ -500,7 +531,7 @@ def select_source(ticket_number):
|
|||
local_sources = []
|
||||
remote_sources = []
|
||||
sources = []
|
||||
mount_backup_shares()
|
||||
mount_backup_shares(read_write=False)
|
||||
|
||||
# Check for ticket folders on servers
|
||||
for server in BACKUP_SERVERS:
|
||||
|
|
@ -623,6 +654,14 @@ def select_source(ticket_number):
|
|||
pause("Press Enter to exit...")
|
||||
exit_script()
|
||||
|
||||
# Sanity check
|
||||
if selected_source.is_file():
|
||||
# Image-Based
|
||||
if not REGEX_WIM_FILE.search(selected_source.name):
|
||||
print_error('ERROR: Unsupported image: {}'.format(
|
||||
selected_source.path))
|
||||
raise GenericError
|
||||
|
||||
# Done
|
||||
return selected_source
|
||||
|
||||
|
|
@ -699,12 +738,12 @@ def transfer_source(source_obj, dest_path, selected_items):
|
|||
raise GenericError
|
||||
|
||||
def umount_backup_shares():
|
||||
"""Unnount the backup shares regardless of current status."""
|
||||
"""Unmount the backup shares regardless of current status."""
|
||||
for server in BACKUP_SERVERS:
|
||||
umount_network_share(server)
|
||||
|
||||
def umount_network_share(server):
|
||||
"""Unnount a network share defined by server."""
|
||||
"""Unmount a network share defined by server."""
|
||||
cmd = r'net use \\{IP}\{Share} /delete'.format(**server)
|
||||
cmd = cmd.split(' ')
|
||||
try:
|
||||
|
|
@ -716,19 +755,5 @@ def umount_network_share(server):
|
|||
print_info('Umounted {Name}'.format(**server))
|
||||
server['Mounted'] = False
|
||||
|
||||
def wim_contains(source_path, file_path):
|
||||
"""Check if the WIM contains a file or folder."""
|
||||
_cmd = [
|
||||
global_vars['Tools']['wimlib-imagex'], 'dir',
|
||||
source_path, '1',
|
||||
'--path={}'.format(file_path),
|
||||
'--one-file-only']
|
||||
try:
|
||||
run_program(_cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("This file is not meant to be called directly.")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ REGEX_DISK_MBR = re.compile(r'Disk ID: [A-Z0-9]+', re.IGNORECASE)
|
|||
REGEX_DISK_RAW = re.compile(r'Disk ID: 00000000', re.IGNORECASE)
|
||||
|
||||
def assign_volume_letters():
|
||||
"""Assign a volume letter to all available volumes."""
|
||||
remove_volume_letters()
|
||||
|
||||
# Write script
|
||||
|
|
@ -24,6 +25,7 @@ def assign_volume_letters():
|
|||
run_diskpart(script)
|
||||
|
||||
def get_boot_mode():
|
||||
"""Check if the boot mode was UEFI or legacy."""
|
||||
boot_mode = 'Legacy'
|
||||
try:
|
||||
reg_key = winreg.OpenKey(
|
||||
|
|
@ -37,6 +39,7 @@ def get_boot_mode():
|
|||
return boot_mode
|
||||
|
||||
def get_disk_details(disk):
|
||||
"""Get disk details using DiskPart."""
|
||||
details = {}
|
||||
script = [
|
||||
'select disk {}'.format(disk['Number']),
|
||||
|
|
@ -61,6 +64,7 @@ def get_disk_details(disk):
|
|||
return details
|
||||
|
||||
def get_disks():
|
||||
"""Get list of attached disks using DiskPart."""
|
||||
disks = []
|
||||
|
||||
try:
|
||||
|
|
@ -79,6 +83,7 @@ def get_disks():
|
|||
return disks
|
||||
|
||||
def get_partition_details(disk, partition):
|
||||
"""Get partition details using DiskPart and fsutil."""
|
||||
details = {}
|
||||
script = [
|
||||
'select disk {}'.format(disk['Number']),
|
||||
|
|
@ -157,6 +162,7 @@ def get_partition_details(disk, partition):
|
|||
return details
|
||||
|
||||
def get_partitions(disk):
|
||||
"""Get list of partition using DiskPart."""
|
||||
partitions = []
|
||||
script = [
|
||||
'select disk {}'.format(disk['Number']),
|
||||
|
|
@ -179,6 +185,7 @@ def get_partitions(disk):
|
|||
return partitions
|
||||
|
||||
def get_table_type(disk):
|
||||
"""Get disk partition table type using DiskPart."""
|
||||
part_type = 'Unknown'
|
||||
script = [
|
||||
'select disk {}'.format(disk['Number']),
|
||||
|
|
@ -200,6 +207,7 @@ def get_table_type(disk):
|
|||
return part_type
|
||||
|
||||
def get_volumes():
|
||||
"""Get list of volumes using DiskPart."""
|
||||
vols = []
|
||||
try:
|
||||
result = run_diskpart(['list volume'])
|
||||
|
|
@ -214,9 +222,11 @@ def get_volumes():
|
|||
return vols
|
||||
|
||||
def is_bad_partition(par):
|
||||
"""Check if the partition is accessible."""
|
||||
return 'Letter' not in par or REGEX_BAD_PARTITION.search(par['FileSystem'])
|
||||
|
||||
def prep_disk_for_formatting(disk=None):
|
||||
"""Gather details about the disk and its partitions."""
|
||||
disk['Format Warnings'] = '\n'
|
||||
width = len(str(len(disk['Partitions'])))
|
||||
|
||||
|
|
@ -261,6 +271,7 @@ def prep_disk_for_formatting(disk=None):
|
|||
partition['Display String'] = display
|
||||
|
||||
def reassign_volume_letter(letter, new_letter='I'):
|
||||
"""Assign a new letter to a volume using DiskPart."""
|
||||
if not letter:
|
||||
# Ignore
|
||||
return None
|
||||
|
|
@ -276,6 +287,7 @@ def reassign_volume_letter(letter, new_letter='I'):
|
|||
return new_letter
|
||||
|
||||
def remove_volume_letters(keep=None):
|
||||
"""Remove all assigned volume letters using DiskPart."""
|
||||
if not keep:
|
||||
keep = ''
|
||||
|
||||
|
|
@ -292,6 +304,7 @@ def remove_volume_letters(keep=None):
|
|||
pass
|
||||
|
||||
def run_diskpart(script):
|
||||
"""Run DiskPart script."""
|
||||
tempfile = r'{}\diskpart.script'.format(global_vars['Env']['TMP'])
|
||||
|
||||
# Write script
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ TESTS = {
|
|||
}
|
||||
|
||||
def get_smart_details(dev):
|
||||
"""Get SMART data for dev if possible, returns dict."""
|
||||
cmd = 'sudo smartctl --all --json /dev/{}'.format(dev).split()
|
||||
result = run_program(cmd, check=False)
|
||||
try:
|
||||
|
|
@ -51,6 +52,7 @@ def get_smart_details(dev):
|
|||
return {}
|
||||
|
||||
def get_status_color(s):
|
||||
"""Get color based on status, returns str."""
|
||||
color = COLORS['CLEAR']
|
||||
if s in ['Denied', 'NS', 'OVERRIDE', 'Unknown']:
|
||||
color = COLORS['RED']
|
||||
|
|
@ -61,6 +63,7 @@ def get_status_color(s):
|
|||
return color
|
||||
|
||||
def menu_diags(*args):
|
||||
"""Main HW-Diagnostic menu."""
|
||||
diag_modes = [
|
||||
{'Name': 'All tests',
|
||||
'Tests': ['Prime95', 'NVMe/SMART', 'badblocks']},
|
||||
|
|
@ -83,6 +86,13 @@ def menu_diags(*args):
|
|||
{'Letter': 'Q', 'Name': 'Quit', 'CRLF': True},
|
||||
]
|
||||
|
||||
# CLI-mode actions
|
||||
if 'DISPLAY' not in global_vars['Env']:
|
||||
actions.extend([
|
||||
{'Letter': 'R', 'Name': 'Reboot', 'CRLF': True},
|
||||
{'Letter': 'S', 'Name': 'Shutdown'},
|
||||
])
|
||||
|
||||
# Quick disk check
|
||||
if 'quick' in args:
|
||||
run_tests(['Quick', 'NVMe/SMART'])
|
||||
|
|
@ -118,10 +128,15 @@ def menu_diags(*args):
|
|||
run_program(
|
||||
'pipes -t 0 -t 1 -t 2 -t 3 -p 5 -R -r 4000'.split(),
|
||||
check=False, pipe=False)
|
||||
elif selection == 'R':
|
||||
run_program(['reboot'])
|
||||
elif selection == 'S':
|
||||
run_program(['poweroff'])
|
||||
elif selection == 'Q':
|
||||
break
|
||||
|
||||
def run_badblocks():
|
||||
"""Run a read-only test for all detected disks."""
|
||||
aborted = False
|
||||
clear_screen()
|
||||
print_log('\nStart badblocks test(s)\n')
|
||||
|
|
@ -180,6 +195,7 @@ def run_badblocks():
|
|||
pass
|
||||
|
||||
def run_mprime():
|
||||
"""Run Prime95 for MPRIME_LIMIT minutes while showing the temps."""
|
||||
aborted = False
|
||||
clear_screen()
|
||||
print_log('\nStart Prime95 test')
|
||||
|
|
@ -271,6 +287,7 @@ def run_mprime():
|
|||
run_program('tmux kill-pane -a'.split())
|
||||
|
||||
def run_nvme_smart():
|
||||
"""Run the built-in NVMe or SMART test for all detected disks."""
|
||||
aborted = False
|
||||
clear_screen()
|
||||
print_log('\nStart NVMe/SMART test(s)\n')
|
||||
|
|
@ -365,6 +382,7 @@ def run_nvme_smart():
|
|||
run_program('tmux kill-pane -a'.split(), check=False)
|
||||
|
||||
def run_tests(tests):
|
||||
"""Run selected hardware test(s)."""
|
||||
print_log('Starting Hardware Diagnostics')
|
||||
print_log('\nRunning tests: {}'.format(', '.join(tests)))
|
||||
# Enable selected tests
|
||||
|
|
@ -403,6 +421,7 @@ def run_tests(tests):
|
|||
pause('Press Enter to exit...')
|
||||
|
||||
def scan_disks():
|
||||
"""Scan for disks eligible for hardware testing."""
|
||||
clear_screen()
|
||||
|
||||
# Get eligible disk list
|
||||
|
|
@ -410,10 +429,18 @@ def scan_disks():
|
|||
json_data = json.loads(result.stdout.decode())
|
||||
devs = {}
|
||||
for d in json_data.get('blockdevices', []):
|
||||
if d['type'] == 'disk' and d['hotplug'] == '0':
|
||||
devs[d['name']] = {'lsblk': d}
|
||||
TESTS['NVMe/SMART']['Status'][d['name']] = 'Pending'
|
||||
TESTS['badblocks']['Status'][d['name']] = 'Pending'
|
||||
if d['type'] == 'disk':
|
||||
if d['hotplug'] == '0':
|
||||
devs[d['name']] = {'lsblk': d}
|
||||
TESTS['NVMe/SMART']['Status'][d['name']] = 'Pending'
|
||||
TESTS['badblocks']['Status'][d['name']] = 'Pending'
|
||||
else:
|
||||
# Skip WizardKit devices
|
||||
wk_label = '{}_LINUX'.format(KIT_NAME_SHORT)
|
||||
if wk_label not in [c.get('label', '') for c in d['children']]:
|
||||
devs[d['name']] = {'lsblk': d}
|
||||
TESTS['NVMe/SMART']['Status'][d['name']] = 'Pending'
|
||||
TESTS['badblocks']['Status'][d['name']] = 'Pending'
|
||||
|
||||
for dev, data in devs.items():
|
||||
# Get SMART attributes
|
||||
|
|
@ -470,6 +497,7 @@ def scan_disks():
|
|||
TESTS['badblocks']['Devices'] = devs
|
||||
|
||||
def show_disk_details(dev):
|
||||
"""Display disk details."""
|
||||
dev_name = dev['lsblk']['name']
|
||||
# Device description
|
||||
print_info('Device: /dev/{}'.format(dev['lsblk']['name']))
|
||||
|
|
@ -547,6 +575,7 @@ def show_disk_details(dev):
|
|||
print_success(raw_str, timestamp=False)
|
||||
|
||||
def show_results():
|
||||
"""Show results for selected test(s)."""
|
||||
clear_screen()
|
||||
print_log('\n───────────────────────────')
|
||||
print_standard('Hardware Diagnostic Results')
|
||||
|
|
@ -610,6 +639,7 @@ def show_results():
|
|||
run_program('tmux kill-pane -a'.split())
|
||||
|
||||
def update_progress():
|
||||
"""Update progress file."""
|
||||
if 'Progress Out' not in TESTS:
|
||||
TESTS['Progress Out'] = '{}/progress.out'.format(global_vars['LogDir'])
|
||||
output = []
|
||||
|
|
|
|||
|
|
@ -460,8 +460,8 @@ def show_user_data_summary(indent=8, width=32):
|
|||
indent = ' ' * indent,
|
||||
width = width,
|
||||
folder = folder,
|
||||
size = folders[folder]['Size'],
|
||||
path = folders[folder]['Path']))
|
||||
size = folders[folder].get('Size', 'Unknown'),
|
||||
path = folders[folder].get('Path', 'Unknown')))
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("This file is not meant to be called directly.")
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ def is_connected():
|
|||
return False
|
||||
|
||||
def show_valid_addresses():
|
||||
"""Show all valid private IP addresses assigned to the system."""
|
||||
devs = psutil.net_if_addrs()
|
||||
for dev, families in sorted(devs.items()):
|
||||
for family in families:
|
||||
|
|
@ -62,6 +63,7 @@ def show_valid_addresses():
|
|||
show_data(message=dev, data=family.address)
|
||||
|
||||
def speedtest():
|
||||
"""Run a network speedtest using speedtest-cli."""
|
||||
result = run_program(['speedtest-cli', '--simple'])
|
||||
output = [line.strip() for line in result.stdout.decode().splitlines()
|
||||
if line.strip()]
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ def config_explorer_user():
|
|||
|
||||
def update_clock():
|
||||
"""Set Timezone and sync clock."""
|
||||
run_program(['tzutil' ,'/s', TIME_ZONE], check=False)
|
||||
run_program(['tzutil' ,'/s', WINDOWS_TIME_ZONE], check=False)
|
||||
run_program(['net', 'stop', 'w32ime'], check=False)
|
||||
run_program(
|
||||
['w32tm', '/config', '/syncfromflags:manual',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from settings.music import *
|
|||
from settings.sources import *
|
||||
|
||||
def compress_and_remove_item(item):
|
||||
"""Compress and delete an item unless an error is encountered."""
|
||||
try:
|
||||
compress_item(item)
|
||||
except:
|
||||
|
|
@ -17,6 +18,7 @@ def compress_and_remove_item(item):
|
|||
remove_item(item.path)
|
||||
|
||||
def compress_item(item):
|
||||
"""Compress an item in a 7-Zip archive using the ARCHIVE_PASSWORD."""
|
||||
# Prep
|
||||
prev_dir = os.getcwd()
|
||||
dest = '{}.7z'.format(item.path)
|
||||
|
|
@ -58,9 +60,11 @@ def download_generic(out_dir, out_name, source_url):
|
|||
raise GenericError('Failed to download file.')
|
||||
|
||||
def download_to_temp(out_name, source_url):
|
||||
"""Download a file to the TmpDir."""
|
||||
download_generic(global_vars['TmpDir'], out_name, source_url)
|
||||
|
||||
def extract_generic(source, dest, mode='x', sz_args=[]):
|
||||
"""Extract a file to a destination."""
|
||||
cmd = [
|
||||
global_vars['Tools']['SevenZip'],
|
||||
mode, source, r'-o{}'.format(dest),
|
||||
|
|
@ -70,11 +74,13 @@ def extract_generic(source, dest, mode='x', sz_args=[]):
|
|||
run_program(cmd)
|
||||
|
||||
def extract_temp_to_bin(source, item, mode='x', sz_args=[]):
|
||||
"""Extract a file to the .bin folder."""
|
||||
source = r'{}\{}'.format(global_vars['TmpDir'], source)
|
||||
dest = r'{}\{}'.format(global_vars['BinDir'], item)
|
||||
extract_generic(source, dest, mode, sz_args)
|
||||
|
||||
def extract_temp_to_cbin(source, item, mode='x', sz_args=[]):
|
||||
"""Extract a file to the .cbin folder."""
|
||||
source = r'{}\{}'.format(global_vars['TmpDir'], source)
|
||||
dest = r'{}\{}'.format(global_vars['CBinDir'], item)
|
||||
include_path = r'{}\_include\{}'.format(global_vars['CBinDir'], item)
|
||||
|
|
@ -83,6 +89,7 @@ def extract_temp_to_cbin(source, item, mode='x', sz_args=[]):
|
|||
extract_generic(source, dest, mode, sz_args)
|
||||
|
||||
def generate_launcher(section, name, options):
|
||||
"""Generate a launcher script."""
|
||||
# Prep
|
||||
dest = r'{}\{}'.format(global_vars['BaseDir'], section)
|
||||
if section == '(Root)':
|
||||
|
|
@ -119,6 +126,7 @@ def generate_launcher(section, name, options):
|
|||
f.write('\n'.join(out_text))
|
||||
|
||||
def remove_item(item_path):
|
||||
"""Delete a file or folder."""
|
||||
if os.path.exists(item_path):
|
||||
if os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path, ignore_errors=True)
|
||||
|
|
@ -126,6 +134,7 @@ def remove_item(item_path):
|
|||
os.remove(item_path)
|
||||
|
||||
def remove_from_kit(item):
|
||||
"""Delete a file or folder from the .bin/.cbin folders."""
|
||||
item_locations = []
|
||||
for p in [global_vars['BinDir'], global_vars['CBinDir']]:
|
||||
item_locations.append(r'{}\{}'.format(p, item))
|
||||
|
|
@ -134,6 +143,7 @@ def remove_from_kit(item):
|
|||
remove_item(item_path)
|
||||
|
||||
def remove_from_temp(item):
|
||||
"""Delete a file or folder from the TmpDir folder."""
|
||||
item_path = r'{}\{}'.format(global_vars['TmpDir'], item)
|
||||
remove_item(item_path)
|
||||
|
||||
|
|
@ -159,6 +169,7 @@ def resolve_dynamic_url(source_url, regex):
|
|||
return url
|
||||
|
||||
def scan_for_net_installers(server, family_name, min_year):
|
||||
"""Scan network shares for installers."""
|
||||
if not server['Mounted']:
|
||||
mount_network_share(server)
|
||||
|
||||
|
|
|
|||
|
|
@ -154,11 +154,15 @@ def format_mbr(disk):
|
|||
run_diskpart(script)
|
||||
|
||||
def mount_windows_share():
|
||||
"""Mount the Windows images share unless labeled as already mounted."""
|
||||
"""Mount the Windows images share unless labeled as already mounted."""
|
||||
if not WINDOWS_SERVER['Mounted']:
|
||||
mount_network_share(WINDOWS_SERVER)
|
||||
# Mounting read-write in case a backup was done in the same session
|
||||
# and the server was left mounted read-write. This avoids throwing an
|
||||
# error by trying to mount the same server with multiple credentials.
|
||||
mount_network_share(WINDOWS_SERVER, read_write=True)
|
||||
|
||||
def select_windows_version():
|
||||
"""Select Windows version from a menu, returns dict."""
|
||||
actions = [
|
||||
{'Name': 'Main Menu', 'Letter': 'M'},
|
||||
]
|
||||
|
|
@ -175,6 +179,7 @@ def select_windows_version():
|
|||
raise GenericAbort
|
||||
|
||||
def setup_windows(windows_image, windows_version):
|
||||
"""Apply a Windows image to W:"""
|
||||
cmd = [
|
||||
global_vars['Tools']['wimlib-imagex'],
|
||||
'apply',
|
||||
|
|
@ -186,6 +191,7 @@ def setup_windows(windows_image, windows_version):
|
|||
run_program(cmd)
|
||||
|
||||
def setup_windows_re(windows_version, windows_letter='W', tools_letter='T'):
|
||||
"""Setup the WinRE partition."""
|
||||
win = r'{}:\Windows'.format(windows_letter)
|
||||
winre = r'{}\System32\Recovery\WinRE.wim'.format(win)
|
||||
dest = r'{}:\Recovery\WindowsRE'.format(tools_letter)
|
||||
|
|
@ -203,6 +209,7 @@ def setup_windows_re(windows_version, windows_letter='W', tools_letter='T'):
|
|||
run_program(cmd)
|
||||
|
||||
def update_boot_partition(system_letter='S', windows_letter='W', mode='ALL'):
|
||||
"""Setup the Windows boot partition."""
|
||||
cmd = [
|
||||
r'{}\Windows\System32\bcdboot.exe'.format(
|
||||
global_vars['Env']['SYSTEMDRIVE']),
|
||||
|
|
@ -212,6 +219,7 @@ def update_boot_partition(system_letter='S', windows_letter='W', mode='ALL'):
|
|||
run_program(cmd)
|
||||
|
||||
def wim_contains_image(filename, imagename):
|
||||
"""Check if an ESD/WIM contains the specified image, returns bool."""
|
||||
cmd = [
|
||||
global_vars['Tools']['wimlib-imagex'],
|
||||
'info',
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ PE_TOOLS = {
|
|||
}
|
||||
|
||||
def check_pe_tools():
|
||||
"""Fix tool paths for WinPE layout."""
|
||||
for k in PE_TOOLS.keys():
|
||||
PE_TOOLS[k]['Path'] = r'{}\{}'.format(
|
||||
global_vars['BinDir'], PE_TOOLS[k]['Path'])
|
||||
|
|
@ -80,7 +81,7 @@ def menu_backup():
|
|||
ticket_number = get_ticket_number()
|
||||
|
||||
# Mount backup shares
|
||||
mount_backup_shares()
|
||||
mount_backup_shares(read_write=True)
|
||||
|
||||
# Select destination
|
||||
destination = select_backup_destination(auto_select=False)
|
||||
|
|
@ -203,6 +204,7 @@ def menu_backup():
|
|||
pause('\nPress Enter to return to main menu... ')
|
||||
|
||||
def menu_root():
|
||||
"""Main WinPE menu."""
|
||||
check_pe_tools()
|
||||
menus = [
|
||||
{'Name': 'Create Backups', 'Menu': menu_backup},
|
||||
|
|
@ -381,6 +383,7 @@ def menu_setup():
|
|||
pause('\nPress Enter to return to main menu... ')
|
||||
|
||||
def menu_tools():
|
||||
"""Tool launcher menu."""
|
||||
tools = [{'Name': k} for k in sorted(PE_TOOLS.keys())]
|
||||
actions = [{'Name': 'Main Menu', 'Letter': 'M'},]
|
||||
set_title(KIT_NAME_FULL)
|
||||
|
|
@ -409,6 +412,7 @@ def menu_tools():
|
|||
break
|
||||
|
||||
def select_minidump_path():
|
||||
"""Select BSOD minidump path from a menu."""
|
||||
dumps = []
|
||||
|
||||
# Assign volume letters first
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ def color_temp(temp):
|
|||
color = COLORS['CLEAR']
|
||||
return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format(
|
||||
color = color,
|
||||
prefix = '+' if temp>0 else '-',
|
||||
prefix = '-' if temp < 0 else '',
|
||||
temp = temp,
|
||||
**COLORS)
|
||||
|
||||
|
|
@ -61,12 +61,9 @@ def get_feature_string(chip, feature):
|
|||
skipname = len(feature.name)+1 # skip common prefix
|
||||
data = {}
|
||||
|
||||
if feature.type == sensors.feature.INTRUSION:
|
||||
vals = [sensors.get_value(chip, sf.number) for sf in sfs]
|
||||
# short path for INTRUSION to demonstrate type usage
|
||||
status = "alarm" if int(vals[0]) == 1 else "normal"
|
||||
print_standard(' {:18} {}'.format(label, status))
|
||||
return
|
||||
if feature.type != sensors.feature.TEMP:
|
||||
# Skip non-temperature sensors
|
||||
return None
|
||||
|
||||
for sf in sfs:
|
||||
name = sf.name[skipname:].decode("utf-8").strip()
|
||||
|
|
@ -81,7 +78,7 @@ def get_feature_string(chip, feature):
|
|||
data[name] = ' {}°C'.format(val)
|
||||
else:
|
||||
data[name] = '{}{:2.0f}°C'.format(
|
||||
'+' if temp>0 else '-',
|
||||
'-' if temp < 0 else '',
|
||||
temp)
|
||||
else:
|
||||
data[name] = color_temp(val)
|
||||
|
|
@ -94,7 +91,7 @@ def get_feature_string(chip, feature):
|
|||
list_data.append('{}: {}'.format(item, data.pop(item)))
|
||||
list_data.extend(
|
||||
['{}: {}'.format(k, v) for k, v in sorted(data.items())])
|
||||
data_str = '{:18} {} ({})'.format(
|
||||
data_str = '{:18} {} {}'.format(
|
||||
label, main_temp, ', '.join(list_data))
|
||||
else:
|
||||
list_data.extend(sorted(data.items()))
|
||||
|
|
@ -119,10 +116,13 @@ if __name__ == '__main__':
|
|||
chip_name = '{} ({})'.format(
|
||||
sensors.chip_snprintf_name(chip),
|
||||
sensors.get_adapter_name(chip.bus))
|
||||
chip_temps[chip_name] = [chip_name]
|
||||
for feature in sensors.FeatureIterator(chip):
|
||||
chip_temps[chip_name].append(get_feature_string(chip, feature))
|
||||
chip_temps[chip_name].append('')
|
||||
chip_feats = [get_feature_string(chip, feature)
|
||||
for feature in sensors.FeatureIterator(chip)]
|
||||
# Strip empty/None items
|
||||
chip_feats = [f for f in chip_feats if f]
|
||||
|
||||
if chip_feats:
|
||||
chip_temps[chip_name] = [chip_name, *chip_feats, '']
|
||||
|
||||
# Sort chips
|
||||
sensor_temps = []
|
||||
|
|
|
|||
|
|
@ -441,6 +441,12 @@ LAUNCHERS = {
|
|||
},
|
||||
},
|
||||
r'Misc': {
|
||||
'Cleanup CBS Temp Files': {
|
||||
'L_TYPE': 'PyScript',
|
||||
'L_PATH': 'Scripts',
|
||||
'L_ITEM': 'cbs_fix.py',
|
||||
'L_ELEV': 'True',
|
||||
},
|
||||
'ConEmu (as ADMIN)': {
|
||||
'L_TYPE': 'Executable',
|
||||
'L_PATH': 'ConEmu',
|
||||
|
|
|
|||
|
|
@ -11,14 +11,10 @@ KIT_NAME_FULL='Wizard Kit'
|
|||
KIT_NAME_SHORT='WK'
|
||||
SUPPORT_MESSAGE='Please let 2Shirt know by opening an issue on GitHub'
|
||||
# Live Linux
|
||||
DIAG_SHARE='/srv/ClientInfo'
|
||||
DIAG_USER='wkdiag'
|
||||
MPRIME_LIMIT='7' # of minutes to run Prime95 during hw-diags
|
||||
ROOT_PASSWORD='Abracadabra'
|
||||
SKIP_UPLOAD='False'
|
||||
TECH_PASSWORD='Abracadabra'
|
||||
# Server IP addresses
|
||||
DIAG_SERVER='10.0.0.10'
|
||||
OFFICE_SERVER_IP='10.0.0.10'
|
||||
QUICKBOOKS_SERVER_IP='10.0.0.10'
|
||||
# Time Zones
|
||||
|
|
@ -39,6 +35,8 @@ BACKUP_SERVERS = [
|
|||
'Share': 'Backups',
|
||||
'User': 'restore',
|
||||
'Pass': 'Abracadabra',
|
||||
'RW-User': 'backup',
|
||||
'RW-Pass': 'Abracadabra',
|
||||
},
|
||||
{ 'IP': '10.0.0.11',
|
||||
'Name': 'ServerTwo',
|
||||
|
|
@ -46,13 +44,15 @@ BACKUP_SERVERS = [
|
|||
'Share': 'Backups',
|
||||
'User': 'restore',
|
||||
'Pass': 'Abracadabra',
|
||||
'RW-User': 'backup',
|
||||
'RW-Pass': 'Abracadabra',
|
||||
},
|
||||
]
|
||||
CLIENT_INFO_SERVER = {
|
||||
'IP': '10.0.0.10',
|
||||
'RegEntry': r'0x10001,0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
|
||||
'Share': '/srv/ClientInfo',
|
||||
'User': 'upload',
|
||||
CRASH_SERVER = {
|
||||
'Name': 'CrashServer',
|
||||
'Url': '',
|
||||
'User': '',
|
||||
'Pass': '',
|
||||
}
|
||||
OFFICE_SERVER = {
|
||||
'IP': OFFICE_SERVER_IP,
|
||||
|
|
@ -61,6 +61,8 @@ OFFICE_SERVER = {
|
|||
'Share': 'Office',
|
||||
'User': 'restore',
|
||||
'Pass': 'Abracadabra',
|
||||
'RW-User': 'backup',
|
||||
'RW-Pass': 'Abracadabra',
|
||||
}
|
||||
QUICKBOOKS_SERVER = {
|
||||
'IP': QUICKBOOKS_SERVER_IP,
|
||||
|
|
@ -69,6 +71,8 @@ QUICKBOOKS_SERVER = {
|
|||
'Share': 'QuickBooks',
|
||||
'User': 'restore',
|
||||
'Pass': 'Abracadabra',
|
||||
'RW-User': 'backup',
|
||||
'RW-Pass': 'Abracadabra',
|
||||
}
|
||||
WINDOWS_SERVER = {
|
||||
'IP': '10.0.0.10',
|
||||
|
|
@ -77,6 +81,8 @@ WINDOWS_SERVER = {
|
|||
'Share': 'Windows',
|
||||
'User': 'restore',
|
||||
'Pass': 'Abracadabra',
|
||||
'RW-User': 'backup',
|
||||
'RW-Pass': 'Abracadabra',
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -35,8 +35,6 @@ TOOLS = {
|
|||
'ProduKey': {
|
||||
'32': r'ProduKey\ProduKey.exe',
|
||||
'64': r'ProduKey\ProduKey64.exe'},
|
||||
'PuTTY-PSFTP': {
|
||||
'32': r'PuTTY\PSFTP.EXE'},
|
||||
'RKill': {
|
||||
'32': r'RKill\RKill.exe'},
|
||||
'SevenZip': {
|
||||
|
|
|
|||
|
|
@ -80,14 +80,6 @@ if __name__ == '__main__':
|
|||
try_and_print(message='Installed RAM:',
|
||||
function=show_installed_ram, ns='Unknown', silent_function=False)
|
||||
|
||||
# Upload info
|
||||
if ENABLED_UPLOAD_DATA:
|
||||
print_info('Finalizing')
|
||||
try_and_print(message='Compressing Info...',
|
||||
function=compress_info, cs='Done')
|
||||
try_and_print(message='Uploading to NAS...',
|
||||
function=upload_info, cs='Done')
|
||||
|
||||
# Play audio, show devices, open Windows updates, and open Activation
|
||||
popen_program(['mmc', 'devmgmt.msc'])
|
||||
run_hwinfo_sensors()
|
||||
|
|
|
|||
|
|
@ -106,14 +106,6 @@ if __name__ == '__main__':
|
|||
except Exception:
|
||||
print_error(' Unknown error.')
|
||||
|
||||
# Upload info
|
||||
if ENABLED_UPLOAD_DATA:
|
||||
print_info('Finalizing')
|
||||
try_and_print(message='Compressing Info...',
|
||||
function=compress_info, cs='Done')
|
||||
try_and_print(message='Uploading to NAS...',
|
||||
function=upload_info, cs='Done')
|
||||
|
||||
# Done
|
||||
print_standard('\nDone.')
|
||||
pause('Press Enter to exit...')
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ if __name__ == '__main__':
|
|||
# Transfer
|
||||
clear_screen()
|
||||
print_info('Transfer Details:\n')
|
||||
show_info('Ticket:', ticket_number)
|
||||
show_info('Source:', source.path)
|
||||
show_info('Destination:', dest)
|
||||
show_data('Ticket:', ticket_number)
|
||||
show_data('Source:', source.path)
|
||||
show_data('Destination:', dest)
|
||||
|
||||
if (not ask('Proceed with transfer?')):
|
||||
umount_backup_shares()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ order += "tztime local"
|
|||
#order += "tztime utc"
|
||||
|
||||
cpu_usage {
|
||||
format = ". %usage"
|
||||
format = " %usage"
|
||||
max_threshold = 90
|
||||
#format_above_threshold = " %usage"
|
||||
degraded_threshold = 75
|
||||
|
|
@ -29,19 +29,19 @@ cpu_usage {
|
|||
}
|
||||
|
||||
wireless _first_ {
|
||||
format_up = ". (%quality at %essid) %ip"
|
||||
format_down = ". Down"
|
||||
format_up = " (%quality at %essid) %ip"
|
||||
format_down = " Down"
|
||||
}
|
||||
|
||||
ethernet _first_ {
|
||||
# if you use %speed, i3status requires root privileges
|
||||
format_up = ". %ip"
|
||||
format_down = ". Down"
|
||||
format_up = " %ip"
|
||||
format_down = " Down"
|
||||
}
|
||||
|
||||
battery all {
|
||||
integer_battery_capacity = true
|
||||
format = "%status. %percentage"
|
||||
format = "%status %percentage"
|
||||
format_down = ""
|
||||
status_chr = ""
|
||||
status_bat = ""
|
||||
|
|
@ -53,7 +53,7 @@ battery all {
|
|||
}
|
||||
|
||||
volume master {
|
||||
format = ". %volume"
|
||||
format = " %volume"
|
||||
format_muted = " muted"
|
||||
device = "pulse"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,13 @@ aic94xx-firmware
|
|||
bash-pipes
|
||||
gtk-theme-arc-git
|
||||
hfsprogs
|
||||
i3-gaps
|
||||
i3lock-fancy-git
|
||||
mprime-bin
|
||||
mprime
|
||||
nvme-cli
|
||||
openbox-patched
|
||||
papirus-icon-theme
|
||||
pasystray
|
||||
smartmontools-svn
|
||||
testdisk-wip
|
||||
ttf-font-awesome
|
||||
ttf-font-awesome-4
|
||||
wd719x-firmware
|
||||
wimlib
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ base-devel
|
|||
curl
|
||||
dos2unix
|
||||
git
|
||||
hwloc
|
||||
libewf
|
||||
openssh
|
||||
p7zip
|
||||
progsreiserfs
|
||||
refind-efi
|
||||
rsync
|
||||
syslinux
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ mdadm
|
|||
mediainfo
|
||||
mesa-demos
|
||||
mkvtoolnix-cli
|
||||
mprime-bin
|
||||
mprime
|
||||
mpv
|
||||
mupdf
|
||||
ncdu
|
||||
|
|
@ -50,6 +50,7 @@ networkmanager
|
|||
nvme-cli
|
||||
oblogout
|
||||
openbox-patched
|
||||
otf-font-awesome-4
|
||||
p7zip
|
||||
papirus-icon-theme
|
||||
pasystray
|
||||
|
|
@ -75,7 +76,7 @@ tint2
|
|||
tk
|
||||
tmux
|
||||
tree
|
||||
ttf-font-awesome
|
||||
ttf-font-awesome-4
|
||||
ttf-inconsolata
|
||||
udevil
|
||||
udisks2
|
||||
|
|
|
|||
34
Build Linux
34
Build Linux
|
|
@ -2,6 +2,11 @@
|
|||
#
|
||||
## Wizard Kit: Live Linux Build Tool
|
||||
|
||||
set -o errexit
|
||||
set -o errtrace
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
# Prep
|
||||
DATE="$(date +%F)"
|
||||
DATETIME="$(date +%F_%H%M)"
|
||||
|
|
@ -21,7 +26,7 @@ elif which vim >/dev/null 2>&1; then
|
|||
else
|
||||
EDITOR=vi
|
||||
fi
|
||||
if which sudo >/dev/null 2>&1; then
|
||||
if [ ! -z ${SUDO_USER+x} ]; then
|
||||
REAL_USER="$SUDO_USER"
|
||||
fi
|
||||
|
||||
|
|
@ -110,7 +115,7 @@ function copy_live_env() {
|
|||
rsync -aI "$ROOT_DIR/.linux_items/include/" "$LIVE_DIR/"
|
||||
mkdir -p "$LIVE_DIR/airootfs/usr/local/bin"
|
||||
rsync -aI "$ROOT_DIR/.bin/Scripts/" "$LIVE_DIR/airootfs/usr/local/bin/"
|
||||
cp -a "$BUILD_DIR/main.py" "$LIVE_DIR/airootfs/usr/local/bin/"
|
||||
cp -a "$BUILD_DIR/main.py" "$LIVE_DIR/airootfs/usr/local/bin/settings/"
|
||||
}
|
||||
|
||||
function run_elevated() {
|
||||
|
|
@ -184,12 +189,12 @@ function update_live_env() {
|
|||
sed -i -r "s/_+/$KIT_NAME_FULL Linux Environment/" "$LIVE_DIR/airootfs/etc/motd"
|
||||
|
||||
# Oh My ZSH
|
||||
git clone --depth=1 git://github.com/robbyrussell/oh-my-zsh.git "$SKEL_DIR/.oh-my-zsh"
|
||||
git clone --depth=1 https://github.com/robbyrussell/oh-my-zsh.git "$SKEL_DIR/.oh-my-zsh"
|
||||
rm -Rf "$SKEL_DIR/.oh-my-zsh/.git"
|
||||
curl -o "$SKEL_DIR/.oh-my-zsh/themes/lean.zsh-theme" https://raw.githubusercontent.com/miekg/lean/master/prompt_lean_setup
|
||||
|
||||
# Openbox theme
|
||||
git clone --depth=1 git@github.com:addy-dclxvi/Openbox-Theme-Collections.git "$TEMP_DIR/ob-themes"
|
||||
git clone --depth=1 https://github.com/addy-dclxvi/Openbox-Theme-Collections.git "$TEMP_DIR/ob-themes"
|
||||
mkdir -p "$LIVE_DIR/airootfs/usr/share/themes"
|
||||
cp -a "$TEMP_DIR/ob-themes/Triste-Orange" "$LIVE_DIR/airootfs/usr/share/themes/"
|
||||
|
||||
|
|
@ -243,15 +248,15 @@ function update_repo() {
|
|||
|
||||
# Archive current files
|
||||
if [[ -d "$REPO_DIR" ]]; then
|
||||
mkdir "$BUILD_DIR/Archive" 2>/dev/null
|
||||
mkdir -p "$BUILD_DIR/Archive" 2>/dev/null
|
||||
archive="$BUILD_DIR/Archive/$(date "+%F_%H%M%S")"
|
||||
mv -bv "$REPO_DIR" "$archive"
|
||||
fi
|
||||
sleep 1s
|
||||
|
||||
# Build custom repo packages
|
||||
mkdir "$REPO_DIR" 2>/dev/null
|
||||
mkdir "$TEMP_DIR" 2>/dev/null
|
||||
mkdir -p "$REPO_DIR" 2>/dev/null
|
||||
mkdir -p "$TEMP_DIR" 2>/dev/null
|
||||
pushd "$TEMP_DIR" >/dev/null
|
||||
while read -r p; do
|
||||
echo "Building: $p"
|
||||
|
|
@ -292,6 +297,13 @@ function build_iso() {
|
|||
chmod 700 "$LIVE_DIR/airootfs/etc/skel/.ssh"
|
||||
chmod 600 "$LIVE_DIR/airootfs/etc/skel/.ssh/id_rsa"
|
||||
|
||||
# Removing cached (and possibly outdated) custom repo packages
|
||||
for package in $(cat "$ROOT_DIR/.linux_items/packages/aur"); do
|
||||
if [[ -f /var/cache/pacman/pkg/${package}* ]]; then
|
||||
rm /var/cache/pacman/pkg/${package}*
|
||||
fi
|
||||
done
|
||||
|
||||
# Build ISO
|
||||
prefix="${KIT_NAME_SHORT}-Linux"
|
||||
label="${KIT_NAME_SHORT}_LINUX"
|
||||
|
|
@ -325,32 +337,38 @@ function build_full() {
|
|||
}
|
||||
|
||||
# Check input
|
||||
case $1 in
|
||||
case ${1:-} in
|
||||
-b|--build-full)
|
||||
build_full
|
||||
echo Done
|
||||
;;
|
||||
|
||||
-f|--fix-perms)
|
||||
fix_kit_permissions
|
||||
echo Done
|
||||
;;
|
||||
|
||||
-i|--install-deps)
|
||||
install_deps
|
||||
echo Done
|
||||
;;
|
||||
|
||||
-o|--build-iso)
|
||||
load_settings
|
||||
build_iso
|
||||
echo Done
|
||||
;;
|
||||
|
||||
-p|--prep-live-env)
|
||||
load_settings
|
||||
copy_live_env
|
||||
update_live_env
|
||||
echo Done
|
||||
;;
|
||||
|
||||
-u|--update-repo)
|
||||
update_repo
|
||||
echo Done
|
||||
;;
|
||||
|
||||
*)
|
||||
|
|
|
|||
85
README.md
85
README.md
|
|
@ -7,7 +7,7 @@ A collection of scripts to help technicians service Windows systems.
|
|||
### Build Requirements ###
|
||||
|
||||
* PowerShell 3.0 or newer<sup>1</sup>
|
||||
* 6 Gb disk space
|
||||
* 10 Gb disk space
|
||||
|
||||
### Initial Setup ###
|
||||
|
||||
|
|
@ -49,19 +49,39 @@ A collection of scripts to help technicians service Windows systems.
|
|||
### Initial Setup ###
|
||||
|
||||
* Replace artwork as desired
|
||||
* Run `Build_Linux` which will do the following:
|
||||
* Install missing dependancies with pacman
|
||||
* Open `main.py` in nano for configuration
|
||||
* Build the local repo for the AUR packages
|
||||
* Build the live Linux environment (exported as an ISO file)
|
||||
* Install Arch Linux in a virtual machine ([VirtualBox](https://www.virtualbox.org/) is a good option for Windows systems).
|
||||
* See the [installation guide](https://wiki.archlinux.org/index.php/Installation_guide) for details.
|
||||
* Add a standard user to the Arch Linux installation.
|
||||
* See the [wiki page](https://wiki.archlinux.org/index.php/Users_and_groups#User_management) for details.
|
||||
* Install git # `pacman -Syu git`
|
||||
* _(Recommended)_ Install and configure `sudo`
|
||||
* See the [wiki page](https://wiki.archlinux.org/index.php/Sudo) for details.
|
||||
* Login to the user added above
|
||||
* Download the Github repo $ `git clone https://github.com/2Shirt/WizardKit.git`
|
||||
* Run the build script
|
||||
* $ `cd WizardKit`
|
||||
* $ `./Build\ Linux -b`
|
||||
* The build script does the following:
|
||||
* Installs missing dependencies via `pacman`
|
||||
* Opens `main.py` in `nano` for configuration
|
||||
* Downloads, builds, and adds AUR packages to a local repo
|
||||
* Builds the Live Linux ISO
|
||||
|
||||
### Notes ###
|
||||
|
||||
* The WinPE boot options require files to be copied from a completed WinPE build.
|
||||
* This is done below for the Combined UFD
|
||||
|
||||
## Windows PE ##
|
||||
|
||||
### Build Requirements ###
|
||||
|
||||
* Windows Assessment and Deployment Kit for Windows 10
|
||||
* Deployment Tools
|
||||
* Windows Preinstallation Environment (Windows PE)
|
||||
* _All other features are not required_
|
||||
* PowerShell 3.0 or newer
|
||||
* 2 Gb disk space
|
||||
* 8 Gb disk space
|
||||
|
||||
### Initial Setup ###
|
||||
|
||||
|
|
@ -72,5 +92,56 @@ A collection of scripts to help technicians service Windows systems.
|
|||
* Download all tools
|
||||
* Build both 32-bit & 64-bit PE images (exported as ISO files)
|
||||
|
||||
## Combined Wizard Kit ##
|
||||
|
||||
### Build Requirements ###
|
||||
|
||||
* 64-bit system or virtual machine
|
||||
* 4 Gb RAM
|
||||
* 8 Gb USB flash drive _(16 Gb or larger recommended)_
|
||||
|
||||
### Overview ###
|
||||
|
||||
There's a `build-ufd` script which does the following:
|
||||
|
||||
* Checks for the presence if the Linux ISO and the (64-bit) WinPE ISO.
|
||||
* Formats the selected UFD using FAT32.
|
||||
* All data will be deleted from the UFD resulting in **DATA LOSS**.
|
||||
* Copies the required files from the Linux ISO, WinPE ISO, and Main Kit folder to the UFD.
|
||||
* Installs Syslinux to the UFD making it bootable on legacy systems.
|
||||
* Sets the boot files/folders to be hidden under Windows.
|
||||
|
||||
### Setup ###
|
||||
|
||||
* Boot to a Live Linux ISO built following the instructions above.
|
||||
* You can apply it to a UFD using [rufus](https://rufus.akeo.ie/) for physical systems.
|
||||
* Virtual machines should be able to use the Linux ISO directly.
|
||||
* Put the Linux ISO, the WinPE ISO, and the Main Kit folder _(usually "OUT_KIT")_ in the same directory.
|
||||
* "OUT_KIT" will be renamed on the UFD using `$KIT_NAME_FULL`
|
||||
* `$KIT_NAME_FULL` defaults to "Wizard Kit" but can be changed in `main.py`
|
||||
* "OUT_KIT" can be renamed in the source folder.
|
||||
* The script searched for the ".bin" folder and uses it's parent folder as the Main Kit source.
|
||||
* Additional files/folders can be included by putting them in a folder named "Extras".
|
||||
* These files/folders will be copied to the root of the UFD.
|
||||
* To include images for the WinPE Setup section, put the files in "Extras/images".
|
||||
* WinPE Setup will recognize ESD, WIM, and SWM<sup>2</sup> images.
|
||||
* The filenames should be "Win7", "Win8", or "Win10"
|
||||
* The final layout should be similar to this: _(assuming it's mounted to "/Sources")_
|
||||
* **(Required)** `/Sources/OUT_KIT`
|
||||
* **(Required)** `/Sources/WK-Linux-2018-01-01-x86_64.iso`
|
||||
* **(Required)** `/Sources/WK-WinPE-2018-01-01-amd64.iso`
|
||||
* _(Optional)_ `/Sources/Extras/Essential Windows Updates`
|
||||
* _(Optional)_ `/Sources/Extras/images/Win7.wim`
|
||||
* _(Optional)_ `/Sources/Extras/images/Win8.wim`
|
||||
* _(Optional)_ `/Sources/Extras/images/Win10.esd`
|
||||
* Connect the UFD but don't mount it.
|
||||
* Mount the device, or connect to the share, with the ISOs and Main Kit folder.
|
||||
* $ `cd /Sources` _(replace with real path to source files)_
|
||||
* Get the device name of the UFD.
|
||||
* You can use $ `lsblk --fs` or $ `inxi -Dxx` to help.
|
||||
* $ `sudo build-ufd /dev/sdX` _(replace `/dev/sdX` with the desired device)_
|
||||
* **2nd Warning**: All data will be erased from the UFD resulting in **DATA LOSS**.
|
||||
|
||||
## Notes ##
|
||||
1. PowerShell 6.0 on Windows 7 is not supported by the build script.
|
||||
2. See [wimlib-imagex](https://wimlib.net/) for details about split WIM images.
|
||||
|
|
|
|||
Loading…
Reference in a new issue