Main Kit
* Added Win10 v1903 support
* System Setup script
  * Replaces Install SW Bundle, New System Setup, System Checklists, and User Checklists
  * Actions performed are based on the mode selected and answers given
  * Improved LibreOffice installations
    * Installed from a local source
    * Registers all MS Office types†
    * Sets default save-as options unless a config file is already present†
    * † Depending on setup mode selected
  * Improved performance when exporting system info
    * NOTE: Event logs are no longer saved
  * New 4K alignment check
  * New Win10 1903 theme handling
    * If updating Explorer (user) settings the Light theme is disabled
      * Set to the <= 1809 default to match Classic Start theme
* Fixed issue #102
* Various other bug fixes

Linux
* Auto-connects to known WiFi networks
* ddrescue-tui (AKA WKClone and WKImage)
  * Improved recovery state detection
    * NOTE: percentages may be off by 0.01% due to the rounding method used
* HW-Diagnostics
  * Added option to upload results for review
  * Added RAM info to CPU results screen
  * Added warning if one or more partitions are not 4K aligned
  * Improved AMD CPU support (k8, k10, etc)
  * Improved SMART detection and reporting
* New build-ufd script
  * Now written in Python
  * Only Linux (Full) is required, all other sources optional
  * Boot options are enabled based on files present on the UFD
  * Can update in place without reformatting
* Fixed issues #101-#108, & #113
* Various other bug fixes
This commit is contained in:
2Shirt 2019-06-11 19:22:29 -06:00
commit df5a3081b9
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
73 changed files with 2665 additions and 1826 deletions

View file

@ -55,8 +55,8 @@ if __name__ == '__main__':
print_success('\nDone.') print_success('\nDone.')
pause("Press Enter to exit...") pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -0,0 +1,81 @@
#!/bin/env python3
#
## Convert saved WiFi connections for NetworkManager
import os
import re
import sys
import uuid
KNOWN_NETWORKS = '/root/known_networks'
TEMPLATE = '''[connection]
id={ssid}
uuid={uuid}
type=wifi
permissions=user:{user}:;
[wifi]
mac-address-blacklist=
mode=infrastructure
ssid={ssid}
[wifi-security]
auth-alg=open
key-mgmt=wpa-psk
psk={password}
[ipv4]
dns-search=
method=auto
[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=auto
'''
def get_user_name():
"""Get user name, returns str."""
user = None
# Get running user
if 'SUDO_USER' in os.environ:
user = os.environ.get('SUDO_USER')
else:
user = os.environ.get('USER')
# Check if user manually specified
for a in sys.argv:
a = a.strip().lower()
if a.startswith('--user='):
user = a.replace('--user=', '')
return user
if __name__ == '__main__':
known_networks = {}
#try:
with open('/root/known_networks', 'r') as f:
for line in f.readlines():
r = re.search(r"^'(.*)':\s+'(.*)'", line.strip())
if r:
known_networks[r.group(1)] = r.group(2)
for ssid, password in known_networks.items():
out_path = '{}/{}.nmconnection'.format(
'/etc/NetworkManager/system-connections',
ssid,
)
if not os.path.exists(out_path):
with open(out_path, 'w') as f:
f.write(TEMPLATE.format(
user=get_user_name(),
ssid=ssid,
password=password,
uuid=uuid.uuid4(),
))
os.chmod(out_path, 0o600)
#except:
# # Meh
# pass
# vim: sts=2 sw=2 ts=2

View file

@ -1,671 +1,149 @@
#!/usr/bin/env bash #!/bin/env python3
# #
## Wizard Kit: UFD Build Tool # pylint: disable=no-name-in-module,wildcard-import,wrong-import-position
# # vim: sts=2 sw=2 ts=2
# Based on a template by BASH3 Boilerplate v2.3.0 """Wizard Kit: UFD build tool"""
# http://bash3boilerplate.sh/#authors
# import os
# The MIT License (MIT) import sys
# Copyright (c) 2013 Kevin van Zonneveld and contributors
# You are not obligated to bundle the LICENSE file with your b3bp projects as long # Init
# as you leave these references intact in the header comments of your source files. sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from docopt import docopt
# Exit on error. Append "|| true" if you expect an error. from functions.common import *
set -o errexit from functions.ufd import *
# Exit on error inside any functions or subshells. from settings.ufd import *
set -o errtrace init_global_vars(silent=True)
# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR
set -o nounset # Main section
# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip` if __name__ == '__main__':
set -o pipefail # pylint: disable=invalid-name
# Turn on traces, useful while debugging but commented out by default # Set log
# set -o xtrace try:
global_vars['LogDir'] = '{}/Logs'.format(
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then get_user_home(get_user_name()))
__i_am_main_script="0" # false set_log_file('Build UFD ({Date-Time}).log'.format(**global_vars))
except: # pylint: disable=bare-except
if [[ "${__usage+x}" ]]; then major_exception()
if [[ "${BASH_SOURCE[1]}" = "${0}" ]]; then
__i_am_main_script="1" # true # Header
fi print_success(KIT_NAME_FULL)
print_standard('UFD Build Tool')
__b3bp_external_usage="true" print_standard(' ')
__b3bp_tmp_source_idx=1
fi # Check if running as root
else if not running_as_root():
__i_am_main_script="1" # true print_error('ERROR: This script is meant to be run as root.')
[[ "${__usage+x}" ]] && unset -v __usage abort(False)
[[ "${__helptext+x}" ]] && unset -v __helptext
fi # Docopt
try:
# Set magic variables for current file, directory, os, etc. args = docopt(DOCSTRING)
__dir="$(cd "$(dirname "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" && pwd)" except SystemExit as sys_exit:
__file="${__dir}/$(basename "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" # Catch docopt exits
__base="$(basename "${__file}" .sh)" exit_script(sys_exit.code)
__wd="$(pwd)" except: # pylint: disable=bare-except
__usage_example="Usage: sudo $(basename "${0}") --ufd-device [device] --linux-iso [path] --main-kit [path] --winpe-iso [path]" major_exception()
__all_args=""
for a in "${@}"; do try:
if [[ "${a:0:1}" == "-" ]]; then # Verify selections
__all_args="${__all_args} ${a}" ufd_dev = verify_ufd(args['--ufd-device'])
else sources = verify_sources(args, UFD_SOURCES)
__all_args="${__all_args} \"${a}\"" show_selections(args, sources, ufd_dev, UFD_SOURCES)
fi if not args['--force']:
done confirm_selections(args)
# Prep UFD
# Define the environment variables (and their defaults) that this script depends on if not args['--update']:
LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency print_info('Prep UFD')
NO_COLOR="${NO_COLOR:-}" # true = disable color. otherwise autodetected prep_device(ufd_dev, UFD_LABEL, use_mbr=args['--use-mbr'])
# Mount UFD
### Functions try_and_print(
############################################################################## indent=2,
message='Mounting UFD...',
function __b3bp_log () { function=mount,
local log_level="${1}" mount_source=find_first_partition(ufd_dev),
shift mount_point='/mnt/UFD',
read_write=True,
# shellcheck disable=SC2034 )
local color_debug="\x1b[35m"
# shellcheck disable=SC2034 # Remove Arch folder
local color_info="\x1b[32m" if args['--update']:
# shellcheck disable=SC2034 try_and_print(
local color_notice="\x1b[34m" indent=2,
# shellcheck disable=SC2034 message='Removing Linux...',
local color_warning="\x1b[33m" function=remove_arch,
# shellcheck disable=SC2034 )
local color_error="\x1b[31m"
# shellcheck disable=SC2034 # Copy sources
local color_critical="\x1b[1;31m" print_standard(' ')
# shellcheck disable=SC2034 print_info('Copy Sources')
local color_alert="\x1b[1;33;41m" for s_label, s_path in sources.items():
# shellcheck disable=SC2034 try_and_print(
local color_emergency="\x1b[1;4;5;33;41m" indent=2,
message='Copying {}...'.format(s_label),
local colorvar="color_${log_level}" function=copy_source,
source=s_path,
local color="${!colorvar:-${color_error}}" items=ITEMS[s_label],
local color_reset="\x1b[0m" overwrite=True,
)
if [[ "${NO_COLOR:-}" = "true" ]] || ( [[ "${TERM:-}" != *"256color"* ]] && [[ "${TERM:-}" != "xterm"* ]] && [[ "${TERM:-}" != "screen"* ]] ) || [[ ! -t 2 ]]; then
if [[ "${NO_COLOR:-}" != "false" ]]; then # Update boot entries
# Don't use colors on pipes or non-recognized terminals print_standard(' ')
color=""; color_reset="" print_info('Boot Setup')
fi try_and_print(
fi indent=2,
message='Updating boot entries...',
# all remaining arguments are to be printed function=update_boot_entries,
local log_line="" boot_entries=BOOT_ENTRIES,
boot_files=BOOT_FILES,
while IFS=$'\n' read -r log_line; do iso_label=ISO_LABEL,
echo -e "$(date -u +"%Y-%m-%d %H:%M:%S UTC") ${color}$(printf "[%9s]" "${log_level}")${color_reset} ${log_line}" 1>&2 ufd_label=UFD_LABEL,
done <<< "${@:-}" )
}
# Install syslinux (to partition)
function emergency () { __b3bp_log emergency "${@}"; exit 1; } try_and_print(
function alert () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && __b3bp_log alert "${@}"; true; } indent=2,
function critical () { [[ "${LOG_LEVEL:-0}" -ge 2 ]] && __b3bp_log critical "${@}"; true; } message='Syslinux (partition)...',
function error () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && __b3bp_log error "${@}"; true; } function=install_syslinux_to_partition,
function warning () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && __b3bp_log warning "${@}"; true; } partition=find_first_partition(ufd_dev),
function notice () { [[ "${LOG_LEVEL:-0}" -ge 5 ]] && __b3bp_log notice "${@}"; true; } )
function info () { [[ "${LOG_LEVEL:-0}" -ge 6 ]] && __b3bp_log info "${@}"; true; }
function debug () { [[ "${LOG_LEVEL:-0}" -ge 7 ]] && __b3bp_log debug "${@}"; true; } # Unmount UFD
try_and_print(
function help () { indent=2,
echo "" 1>&2 message='Unmounting UFD...',
echo " ${*}" 1>&2 function=unmount,
echo "" 1>&2 mount_point='/mnt/UFD',
echo " ${__usage:-No usage available}" 1>&2 )
echo "" 1>&2
# Install syslinux (to device)
if [[ "${__helptext:-}" ]]; then try_and_print(
echo " ${__helptext}" 1>&2 indent=2,
echo "" 1>&2 message='Syslinux (device)...',
fi function=install_syslinux_to_dev,
ufd_dev=ufd_dev,
exit 1 use_mbr=args['--use-mbr'],
} )
# Hide items
### Parse commandline options print_standard(' ')
############################################################################## print_info('Final Touches')
try_and_print(
# Commandline options. This defines the usage page, and is used to parse cli indent=2,
# opts & defaults from. The parsing is unforgiving so be precise in your syntax message='Hiding items...',
# - A short option must be preset for every long option; but every short option function=hide_items,
# need not have a long option ufd_dev=ufd_dev,
# - `--` is respected as the separator between options and arguments items=ITEMS_HIDDEN,
# - We do not bash-expand defaults, so setting '~/app' as a default will not resolve to ${HOME}. )
# you can use bash variables to work around this (so use ${HOME} instead)
# Done
# shellcheck disable=SC2015 if not args['--force']:
[[ "${__usage+x}" ]] || read -r -d '' __usage <<-'EOF' || true # exits non-zero when EOF encountered print_standard('\nDone.')
OPTIONS: pause('Press Enter to exit...')
-u --ufd-device [arg] Device to which the kit will be applied exit_script()
-l --linux-iso [arg] Path to the Linux ISO except SystemExit as sys_exit:
exit_script(sys_exit.code)
-e --extra-dir [arg] Path to the Extra folder (optional) except: # pylint: disable=bare-except
-m --main-kit [arg] Path to the Main Kit (optional) major_exception()
-w --winpe-iso [arg] Path to the WinPE ISO (optional)
-h --help This page
ADVANCED:
-d --debug Enable debug mode
-v --verbose Enable verbose mode
-M --use-mbr Use real MBR instead of GPT w/ Protective MBR
-F --force Bypass all confirmation messages. USE WITH EXTREME CAUTION!
EOF
# shellcheck disable=SC2015
[[ "${__helptext+x}" ]] || read -r -d '' __helptext <<-'EOF' || true # exits non-zero when EOF encountered
Paths can be relative to the current working directory or absolute
EOF
# Translate usage string -> getopts arguments, and set $arg_<flag> defaults
while read -r __b3bp_tmp_line; do
if [[ "${__b3bp_tmp_line}" =~ ^- ]]; then
# fetch single character version of option string
__b3bp_tmp_opt="${__b3bp_tmp_line%% *}"
__b3bp_tmp_opt="${__b3bp_tmp_opt:1}"
# fetch long version if present
__b3bp_tmp_long_opt=""
if [[ "${__b3bp_tmp_line}" = *"--"* ]]; then
__b3bp_tmp_long_opt="${__b3bp_tmp_line#*--}"
__b3bp_tmp_long_opt="${__b3bp_tmp_long_opt%% *}"
fi
# map opt long name to+from opt short name
printf -v "__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" '%s' "${__b3bp_tmp_opt}"
printf -v "__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt}" '%s' "${__b3bp_tmp_long_opt//-/_}"
# check if option takes an argument
if [[ "${__b3bp_tmp_line}" =~ \[.*\] ]]; then
__b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg
__b3bp_tmp_init="" # it has an arg. init with ""
printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "1"
elif [[ "${__b3bp_tmp_line}" =~ \{.*\} ]]; then
__b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg
__b3bp_tmp_init="" # it has an arg. init with ""
# remember that this option requires an argument
printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2"
else
__b3bp_tmp_init="0" # it's a flag. init with 0
printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "0"
fi
__b3bp_tmp_opts="${__b3bp_tmp_opts:-}${__b3bp_tmp_opt}"
fi
[[ "${__b3bp_tmp_opt:-}" ]] || continue
if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Default= ]]; then
# ignore default value if option does not have an argument
__b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}"
if [[ "${!__b3bp_tmp_varname}" != "0" ]]; then
__b3bp_tmp_init="${__b3bp_tmp_line##*Default=}"
__b3bp_tmp_re='^"(.*)"$'
if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then
__b3bp_tmp_init="${BASH_REMATCH[1]}"
else
__b3bp_tmp_re="^'(.*)'$"
if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then
__b3bp_tmp_init="${BASH_REMATCH[1]}"
fi
fi
fi
fi
if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Required\. ]]; then
# remember that this option requires an argument
printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2"
fi
printf -v "arg_${__b3bp_tmp_opt:0:1}" '%s' "${__b3bp_tmp_init}"
done <<< "${__usage:-}"
# run getopts only if options were specified in __usage
if [[ "${__b3bp_tmp_opts:-}" ]]; then
# Allow long options like --this
__b3bp_tmp_opts="${__b3bp_tmp_opts}-:"
# Reset in case getopts has been used previously in the shell.
OPTIND=1
# start parsing command line
set +o nounset # unexpected arguments will cause unbound variables
# to be dereferenced
# Overwrite $arg_<flag> defaults with the actual CLI options
while getopts "${__b3bp_tmp_opts}" __b3bp_tmp_opt; do
[[ "${__b3bp_tmp_opt}" = "?" ]] && help "Invalid use of script: ${*} "
if [[ "${__b3bp_tmp_opt}" = "-" ]]; then
# OPTARG is long-option-name or long-option=value
if [[ "${OPTARG}" =~ .*=.* ]]; then
# --key=value format
__b3bp_tmp_long_opt=${OPTARG/=*/}
# Set opt to the short option corresponding to the long option
__b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}"
printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}"
OPTARG=${OPTARG#*=}
else
# --key value format
# Map long name to short version of option
__b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${OPTARG//-/_}"
printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}"
# Only assign OPTARG if option takes an argument
__b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt}"
printf -v "OPTARG" '%s' "${@:OPTIND:${!__b3bp_tmp_varname}}"
# shift over the argument if argument is expected
((OPTIND+=__b3bp_tmp_has_arg_${__b3bp_tmp_opt}))
fi
# we have set opt/OPTARG to the short value and the argument as OPTARG if it exists
fi
__b3bp_tmp_varname="arg_${__b3bp_tmp_opt:0:1}"
__b3bp_tmp_default="${!__b3bp_tmp_varname}"
__b3bp_tmp_value="${OPTARG}"
if [[ -z "${OPTARG}" ]] && [[ "${__b3bp_tmp_default}" = "0" ]]; then
__b3bp_tmp_value="1"
fi
printf -v "${__b3bp_tmp_varname}" '%s' "${__b3bp_tmp_value}"
debug "cli arg ${__b3bp_tmp_varname} = (${__b3bp_tmp_default}) -> ${!__b3bp_tmp_varname}"
done
set -o nounset # no more unbound variable references expected
shift $((OPTIND-1))
if [[ "${1:-}" = "--" ]] ; then
shift
fi
fi
### Automatic validation of required option arguments
##############################################################################
for __b3bp_tmp_varname in ${!__b3bp_tmp_has_arg_*}; do
# validate only options which required an argument
[[ "${!__b3bp_tmp_varname}" = "2" ]] || continue
__b3bp_tmp_opt_short="${__b3bp_tmp_varname##*_}"
__b3bp_tmp_varname="arg_${__b3bp_tmp_opt_short}"
[[ "${!__b3bp_tmp_varname}" ]] && continue
__b3bp_tmp_varname="__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt_short}"
printf -v "__b3bp_tmp_opt_long" '%s' "${!__b3bp_tmp_varname}"
[[ "${__b3bp_tmp_opt_long:-}" ]] && __b3bp_tmp_opt_long=" (--${__b3bp_tmp_opt_long//_/-})"
help "Option -${__b3bp_tmp_opt_short}${__b3bp_tmp_opt_long:-} requires an argument"
done
### Cleanup Environment variables
##############################################################################
for __tmp_varname in ${!__b3bp_tmp_*}; do
unset -v "${__tmp_varname}"
done
unset -v __tmp_varname
### Externally supplied __usage. Nothing else to do here
##############################################################################
if [[ "${__b3bp_external_usage:-}" = "true" ]]; then
unset -v __b3bp_external_usage
return
fi
### Signal trapping and backtracing
##############################################################################
function __b3bp_cleanup_before_exit () {
if [[ "$EUID" -eq 0 ]]; then
for d in Dest Linux WinPE; do
if [[ -d "/mnt/${d}" ]]; then
umount "/mnt/${d}" || true
rmdir "/mnt/${d}" || true
fi
done
fi
if [[ "${?}" != "0" ]]; then
info "Sources unmounted"
fi
if [[ ${arg_F:-} == 0 && "${SILENT:-False}" == "False" ]]; then
read -r -p "Press Enter to exit... " ignored_var 2>&1
fi
}
trap __b3bp_cleanup_before_exit EXIT
# requires `set -o errtrace`
__b3bp_err_report() {
local error_code
error_code=${?}
error "Error in ${__file} in function ${1} on line ${2}"
exit ${error_code}
}
# Uncomment the following line for always providing an error backtrace
trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR
### Command-line argument switches (like -d for debugmode, -h for showing helppage)
##############################################################################
# debug mode
if [[ "${arg_d:?}" = "1" ]]; then
set -o xtrace
LOG_LEVEL="7"
# Enable error backtracing
trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR
fi
# verbose mode
if [[ "${arg_v:?}" = "1" ]]; then
set -o verbose
fi
### Validation. Error out if the things required for your script are not present
##############################################################################
if [[ "${arg_F:?}" == 1 ]]; then
SILENT="True"
else
SILENT="False"
fi
if [[ "${arg_M:?}" == 1 ]]; then
USE_MBR="True"
else
USE_MBR="False"
fi
if [[ "${arg_h:?}" == 1 ]]; then
help "${__usage_example}"
else
# Print warning line
[[ "${arg_u:-}" ]] || echo " -u or --ufd-device is required"
[[ "${arg_l:-}" ]] || echo " -l or --linux-iso is required"
# Bail if necessary
[[ "${arg_u:-}" ]] || help "${__usage_example}"
[[ "${arg_l:-}" ]] || help "${__usage_example}"
fi
[[ "${LOG_LEVEL:-}" ]] || emergency "Cannot continue without LOG_LEVEL. "
### More functions
##############################################################################
function abort () {
local abort_message="Aborted"
[[ "${1:-}" ]] && abort_message="${1}" || true
error "${abort_message}"
#echo -e "${YELLOW}${abort_message}${CLEAR}"
exit 1
}
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
}
### Runtime
##############################################################################
# VARIABLES
DEST_DEV="${arg_u}"
DEST_PAR="${DEST_DEV}1"
LOG_FILE="$(getent passwd "$SUDO_USER" | cut -d: -f6)/Logs/build-ufd_${DEST_DEV##*/}_$(date +%Y-%m-%d_%H%M_%z).log"
MAIN_PY="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/settings/main.py"
RSYNC_ARGS="-hrtuvS --modify-window=1 --progress"
MAIN_KIT="$(realpath "${arg_m:-}" 2>/dev/null || true)"
LINUX_ISO="$(realpath "${arg_l:-}" 2>/dev/null || true)"
WINPE_ISO="$(realpath "${arg_w:-}" 2>/dev/null || true)"
EXTRA_DIR="$(realpath "${arg_e:-}" 2>/dev/null || true)"
mkdir -p "$(dirname "$LOG_FILE")"
chown "$SUDO_USER:$SUDO_USER" -R "$(dirname "$LOG_FILE")"
# COLORS
CLEAR="\e[0m"
RED="\e[31m"
GREEN="\e[32m"
YELLOW="\e[33m"
BLUE="\e[34m"
# Load main.py settings
if [ ! -f "${MAIN_PY}" ]; then
echo -e "${RED}ERROR${CLEAR}: ${MAIN_PY} not found."
abort
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}"
abort
fi
ISO_LABEL="${KIT_NAME_SHORT}_LINUX"
UFD_LABEL="${KIT_NAME_SHORT}_UFD"
# Check if root
if [[ "$EUID" -ne 0 ]]; then
echo -e "${RED}ERROR${CLEAR}: This script must be run as root."
abort
fi
# Check if in tmux
if ! tmux list-session 2>/dev/null | grep -q "build-ufd"; then
# Reload in tmux
eval tmux new-session -s "build-ufd" "${0:-}" ${__all_args}
SILENT="True" # avoid two "Press Enter to exit..." prompts
exit 0
fi
# Header
echo -e "${GREEN}${KIT_NAME_FULL}${CLEAR}: UFD Build Tool"
echo ""
# Verify sources
[[ -b "${DEST_DEV}" ]] || abort "${DEST_DEV} is not a valid device."
[[ -e "${LINUX_ISO}" ]] || abort "Linux ISO not found."
if [[ ! -z "${arg_m:-}" ]]; then
[[ -d "${MAIN_KIT}/.bin" ]] || abort "Invalid Main Kit, ${MAIN_KIT}/.bin not found."
fi
if [[ ! -z "${arg_w:-}" ]]; then
[[ -e "${WINPE_ISO}" ]] || abort "WinPE ISO not found."
fi
if [[ ! -z "${arg_e:-}" ]]; then
[[ -d "${EXTRA_DIR}" ]] || abort "Extra Dir not found."
fi
# Print Info
echo -e "${BLUE}Sources${CLEAR}" | tee -a "${LOG_FILE}"
echo "Main Kit: ${MAIN_KIT}" | tee -a "${LOG_FILE}"
echo "Linux ISO: ${LINUX_ISO}" | tee -a "${LOG_FILE}"
echo "WinPE ISO: ${WINPE_ISO}" | tee -a "${LOG_FILE}"
echo "Extra Dir: ${EXTRA_DIR:-(Not Specified)}" | tee -a "${LOG_FILE}"
echo "" | tee -a "${LOG_FILE}"
echo -e "${BLUE}Destination${CLEAR}" | tee -a "${LOG_FILE}"
lsblk -n -o NAME,LABEL,SIZE,MODEL,SERIAL "${DEST_DEV}" | tee -a "${LOG_FILE}"
if [[ "${USE_MBR}" == "True" ]]; then
echo -e "${YELLOW}Formatting using legacy MBR${CLEAR}" | tee -a "${LOG_FILE}"
fi
echo "" | tee -a "${LOG_FILE}"
# 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
abort
fi
else
abort
fi
# Start Build
echo "" | tee -a "${LOG_FILE}"
echo -e "${GREEN}Building Kit${CLEAR}" | tee -a "${LOG_FILE}"
touch "${LOG_FILE}"
tmux split-window -dl 10 tail -f "${LOG_FILE}"
# Zero beginning of device
dd bs=4M count=16 if=/dev/zero of="${DEST_DEV}" >> "${LOG_FILE}" 2>&1
# Format
echo "Formatting drive..." | tee -a "${LOG_FILE}"
if [[ "${USE_MBR}" == "True" ]]; then
parted "${DEST_DEV}" --script -- mklabel msdos mkpart primary fat32 4MiB -1s >> "${LOG_FILE}" 2>&1
parted "${DEST_DEV}" set 1 boot on >> "${LOG_FILE}" 2>&1
else
parted "${DEST_DEV}" --script -- mklabel gpt mkpart primary fat32 4MiB -4MiB >> "${LOG_FILE}" 2>&1
parted "${DEST_DEV}" set 1 legacy_boot on >> "${LOG_FILE}" 2>&1
#parted "${DEST_DEV}" disk_set pmbr_boot on >> "${LOG_FILE}" 2>&1
# pmbr_boot breaks detection on some UEFI MOBOs
fi
mkfs.vfat -F 32 -n "${UFD_LABEL}" "${DEST_PAR}" >> "${LOG_FILE}" 2>&1
# Mount sources and dest
echo "Mounting sources and destination..." | tee -a "${LOG_FILE}"
mkdir /mnt/{Dest,Linux,WinPE} -p >> "${LOG_FILE}" 2>&1
mount ${DEST_PAR} /mnt/Dest >> "${LOG_FILE}" 2>&1
mount "${LINUX_ISO}" /mnt/Linux -r >> "${LOG_FILE}" 2>&1
if [[ ! -z "${arg_w:-}" ]]; then
mount "${WINPE_ISO}" /mnt/WinPE -r >> "${LOG_FILE}" 2>&1
fi
# Find WinPE source
w_boot="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot")"
w_boot_bcd="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot/BCD")"
w_boot_sdi="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot/boot.sdi")"
w_bootmgr="$(find /mnt/WinPE -iwholename "/mnt/WinPE/bootmgr")"
w_bootmgr_efi="$(find /mnt/WinPE -iwholename "/mnt/WinPE/bootmgr.efi")"
w_efi_boot="$(find /mnt/WinPE -iwholename "/mnt/WinPE/EFI/Boot")"
w_efi_microsoft="$(find /mnt/WinPE -iwholename "/mnt/WinPE/EFI/Microsoft")"
w_en_us="$(find /mnt/WinPE -iwholename "/mnt/WinPE/en-us")"
w_sources="$(find /mnt/WinPE -iwholename "/mnt/WinPE/sources")"
# Copy files
echo "Copying Linux files..." | tee -a "${LOG_FILE}"
rsync ${RSYNC_ARGS} /mnt/Linux/* /mnt/Dest/ >> "${LOG_FILE}" 2>&1
sed -i "s/${ISO_LABEL}/${UFD_LABEL}/" /mnt/Dest/EFI/boot/refind.conf
sed -i "s/${ISO_LABEL}/${UFD_LABEL}/" /mnt/Dest/arch/boot/syslinux/*cfg
echo "Copying WinPE files..." | tee -a "${LOG_FILE}"
if [[ ! -z "${arg_w:-}" ]]; then
if [[ ! -z "${w_bootmgr:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_bootmgr}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_bootmgr_efi:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_bootmgr_efi}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_en_us:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_en_us}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_boot:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_boot}"/* /mnt/Dest/Boot/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_efi_boot:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_efi_boot}"/* /mnt/Dest/EFI/Microsoft/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_efi_microsoft:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_efi_microsoft}"/* /mnt/Dest/EFI/Microsoft/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_boot_bcd:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_boot_bcd}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_boot_sdi:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_boot_sdi}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_bootmgr:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_bootmgr}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${w_sources:-}" ]]; then
rsync ${RSYNC_ARGS} "${w_sources}"/* /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1
fi
# Uncomment boot entries
sed -i "s/#UFD-WINPE#//" /mnt/Dest/EFI/boot/refind.conf
sed -i "s/#UFD-WINPE#//" /mnt/Dest/arch/boot/syslinux/*cfg
fi
echo "Copying Main Kit..." | tee -a "${LOG_FILE}"
if [[ ! -z "${arg_m:-}" ]]; then
rsync ${RSYNC_ARGS} \
"${MAIN_KIT}/" \
"/mnt/Dest/${KIT_NAME_FULL}/" >> "${LOG_FILE}" 2>&1
fi
if [[ ! -z "${EXTRA_DIR:-}" ]]; then
echo "Copying Extra files..." | tee -a "${LOG_FILE}"
rsync ${RSYNC_ARGS} \
"${EXTRA_DIR}"/ \
/mnt/Dest/ >> "${LOG_FILE}" 2>&1
fi
# Install syslinux
echo "Copying Syslinux files..." | tee -a "${LOG_FILE}"
rsync ${RSYNC_ARGS} /usr/lib/syslinux/bios/*.c32 /mnt/Dest/arch/boot/syslinux/ >> "${LOG_FILE}" 2>&1
syslinux --install -d /arch/boot/syslinux/ ${DEST_PAR} >> "${LOG_FILE}" 2>&1
echo "Unmounting destination..." | tee -a "${LOG_FILE}"
umount /mnt/Dest >> "${LOG_FILE}" 2>&1
rmdir /mnt/Dest >> "${LOG_FILE}" 2>&1
sync
echo "Installing Syslinux MBR..." | tee -a "${LOG_FILE}"
if [[ "${USE_MBR}" == "True" ]]; then
dd bs=440 count=1 if=/usr/lib/syslinux/bios/mbr.bin of=${DEST_DEV} >> "${LOG_FILE}" 2>&1
else
dd bs=440 count=1 if=/usr/lib/syslinux/bios/gptmbr.bin of=${DEST_DEV} >> "${LOG_FILE}" 2>&1
fi
sync
# Cleanup
echo "Hiding boot files..." | tee -a "${LOG_FILE}"
echo "drive s: file=\"${DEST_PAR}\"" > /root/.mtoolsrc
echo 'mtools_skip_check=1' >> /root/.mtoolsrc
for item in arch Boot bootmgr{,.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..." | tee -a "${LOG_FILE}"
for d in Linux WinPE; do
umount "/mnt/${d}" >> "${LOG_FILE}" 2>&1 || true
rmdir "/mnt/${d}" >> "${LOG_FILE}" 2>&1 || true
done
# Close progress pane
pkill -f "tail.*${LOG_FILE}"
# Done
echo "" | tee -a "${LOG_FILE}"
echo "Done." | tee -a "${LOG_FILE}"
echo ""
exit 0

View file

@ -35,8 +35,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
pause("Press Enter to exit...") pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -38,7 +38,7 @@ if __name__ == '__main__':
if repair: if repair:
cs = 'Scheduled' cs = 'Scheduled'
else: else:
cs = 'CS' cs = 'No issues'
message = 'CHKDSK ({SYSTEMDRIVE})...'.format(**global_vars['Env']) message = 'CHKDSK ({SYSTEMDRIVE})...'.format(**global_vars['Env'])
try_and_print(message=message, function=run_chkdsk, try_and_print(message=message, function=run_chkdsk,
cs=cs, other_results=other_results, repair=repair) cs=cs, other_results=other_results, repair=repair)
@ -49,8 +49,8 @@ if __name__ == '__main__':
print_success('Done.') print_success('Done.')
pause("Press Enter to exit...") pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -1,30 +0,0 @@
#!/bin/python3
#
## Wizard Kit: Network connection tool
import os
import sys
# Init
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from functions.network import *
init_global_vars()
if __name__ == '__main__':
try:
# Prep
clear_screen()
# Connect
connect_to_network()
# Done
print_standard('\nDone.')
#pause("Press Enter to exit...")
exit_script()
except SystemExit:
pass
except:
major_exception()
# vim: sts=2 sw=2 ts=2

View file

@ -54,8 +54,8 @@ if __name__ == '__main__':
msg = str(ge) msg = str(ge)
print_error(msg) print_error(msg)
abort() abort()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -149,11 +149,14 @@ def save_debug_reports(state, global_vars):
f.write('{}\n'.format(line)) f.write('{}\n'.format(line))
def upload_logdir(global_vars): def upload_logdir(global_vars, reason='Crash'):
"""Upload compressed LogDir to CRASH_SERVER.""" """Upload compressed LogDir to CRASH_SERVER."""
source = global_vars['LogDir'] source = global_vars['LogDir']
source = source[source.rfind('/')+1:] source = source[source.rfind('/')+1:]
dest = '{}.txz'.format(source) dest = 'HW-Diags_{reason}_{Date-Time}.txz'.format(
reason=reason,
**global_vars,
)
data = None data = None
# Compress LogDir # Compress LogDir
@ -166,7 +169,7 @@ def upload_logdir(global_vars):
data = f.read() data = f.read()
# Upload data # Upload data
url = '{}/Crash_{}.txz'.format(CRASH_SERVER['Url'], source) url = '{}/{}'.format(CRASH_SERVER['Url'], dest)
r = requests.put( r = requests.put(
url, url,
data=data, data=data,

View file

@ -50,8 +50,8 @@ if __name__ == '__main__':
print_success('Done.') print_success('Done.')
pause("Press Enter to exit...") pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -32,8 +32,8 @@ def archive_all_users():
user_path = os.path.join(users_root, user_name) user_path = os.path.join(users_root, user_name)
appdata_local = os.path.join(user_path, r'AppData\Local') appdata_local = os.path.join(user_path, r'AppData\Local')
appdata_roaming = os.path.join(user_path, r'AppData\Roaming') appdata_roaming = os.path.join(user_path, r'AppData\Roaming')
valid_user &= os.path.exists(appdata_local) valid_user = valid_user and os.path.exists(appdata_local)
valid_user &= os.path.exists(appdata_roaming) valid_user = valid_user and os.path.exists(appdata_roaming)
if valid_user: if valid_user:
user_envs.append({ user_envs.append({
'USERNAME': user_name, 'USERNAME': user_name,
@ -325,7 +325,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
if just_firefox and browser_data[browser]['base'] != 'mozilla': if just_firefox and browser_data[browser]['base'] != 'mozilla':
continue continue
exe_path = browser_data[browser].get('exe_path', None) exe_path = browser_data[browser].get('exe_path', None)
function=run_program
if not exe_path: if not exe_path:
if browser_data[browser]['profiles']: if browser_data[browser]['profiles']:
print_standard( print_standard(
@ -375,7 +374,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
elif browser_data[browser]['base'] == 'ie': elif browser_data[browser]['base'] == 'ie':
urls.append(IE_GALLERY) urls.append(IE_GALLERY)
function=popen_program
# By using check=False we're skipping any return codes so # By using check=False we're skipping any return codes so
# it should only fail if the program can't be run # it should only fail if the program can't be run
@ -384,10 +382,16 @@ def install_adblock(indent=8, width=32, just_firefox=False):
# installation status. # installation status.
try_and_print(message='{}...'.format(browser), try_and_print(message='{}...'.format(browser),
indent=indent, width=width, indent=indent, width=width,
cs='Done', function=function, cs='Started', function=popen_program,
cmd=[exe_path, *urls], check=False) cmd=[exe_path, *urls], check=False)
def is_installed(browser_name):
"""Checks if browser is installed based on exe_path, returns bool."""
browser_name = browser_name.replace(' Chromium', '')
return bool(browser_data.get(browser_name, {}).get('exe_path', False))
def list_homepages(indent=8, width=32): def list_homepages(indent=8, width=32):
"""List current homepages for reference.""" """List current homepages for reference."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['exe_path']] browser_list = [k for k, v in sorted(browser_data.items()) if v['exe_path']]
@ -419,6 +423,12 @@ def list_homepages(indent=8, width=32):
indent=' '*indent, width=width, name=name, page=page)) indent=' '*indent, width=width, name=name, page=page))
def profile_present(browser_name):
"""Checks if a profile was detected for browser, returns bool."""
browser_name = browser_name.replace(' Chromium', '')
return bool(browser_data.get(browser_name, {}).get('profiles', False))
def reset_browsers(indent=8, width=32): def reset_browsers(indent=8, width=32):
"""Reset all detected browsers to safe defaults.""" """Reset all detected browsers to safe defaults."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['profiles']] browser_list = [k for k, v in sorted(browser_data.items()) if v['profiles']]
@ -437,14 +447,21 @@ def reset_browsers(indent=8, width=32):
other_results=other_results, profile=profile) other_results=other_results, profile=profile)
def scan_for_browsers(just_firefox=False): def scan_for_browsers(just_firefox=False, silent=False):
"""Scan system for any supported browsers.""" """Scan system for any supported browsers."""
for name, details in sorted(SUPPORTED_BROWSERS.items()): for name, details in sorted(SUPPORTED_BROWSERS.items()):
if just_firefox and details['base'] != 'mozilla': if just_firefox and details['base'] != 'mozilla':
continue continue
try_and_print(message='{}...'.format(name), if silent:
function=get_browser_details, cs='Detected', try:
other_results=other_results, name=name) get_browser_details(name)
except Exception:
# Ignore errors in silent mode
pass
else:
try_and_print(message='{}...'.format(name),
function=get_browser_details, cs='Detected',
other_results=other_results, name=name)
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -1,7 +1,9 @@
# Wizard Kit: Functions - Cleanup '''Wizard Kit: Functions - Cleanup'''
# pylint: disable=no-name-in-module,wildcard-import
from functions.common import * # vim: sts=2 sw=2 ts=2
from functions.setup import *
from settings.cleanup import *
def cleanup_adwcleaner(): def cleanup_adwcleaner():
"""Move AdwCleaner folders into the ClientDir.""" """Move AdwCleaner folders into the ClientDir."""
@ -75,8 +77,7 @@ def cleanup_desktop():
desktop_path = r'{USERPROFILE}\Desktop'.format(**global_vars['Env']) desktop_path = r'{USERPROFILE}\Desktop'.format(**global_vars['Env'])
for entry in os.scandir(desktop_path): for entry in os.scandir(desktop_path):
# JRT, RKill, Shortcut cleaner if DESKTOP_ITEMS.search(entry.name):
if re.search(r'^(JRT|RKill|sc-cleaner)', entry.name, re.IGNORECASE):
dest_name = r'{}\{}'.format(dest_folder, entry.name) dest_name = r'{}\{}'.format(dest_folder, entry.name)
dest_name = non_clobber_rename(dest_name) dest_name = non_clobber_rename(dest_name)
shutil.move(entry.path, dest_name) shutil.move(entry.path, dest_name)
@ -130,7 +131,14 @@ def delete_registry_value(hive, key, value):
winreg.DeleteValue(k, value) winreg.DeleteValue(k, value)
def restore_default_uac():
"""Restores default UAC settings via the registry."""
if global_vars['OS']['Version'] == '10':
write_registry_settings(UAC_DEFAULTS_WIN10, all_users=True)
else:
# Haven't checked Win8 settings, only applying minimum set
write_registry_settings(UAC_DEFAULTS_WIN7, all_users=True)
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -64,10 +64,13 @@ class GenericRepair(Exception):
class MultipleInstallationsError(Exception): class MultipleInstallationsError(Exception):
pass pass
class NotInstalledError(Exception): class NoProfilesError(Exception):
pass pass
class NoProfilesError(Exception): class Not4KAlignedError(Exception):
pass
class NotInstalledError(Exception):
pass pass
class OSInstalledLegacyError(Exception): class OSInstalledLegacyError(Exception):
@ -88,14 +91,21 @@ class SecureBootNotAvailError(Exception):
class SecureBootUnknownError(Exception): class SecureBootUnknownError(Exception):
pass pass
class WindowsOutdatedError(Exception):
pass
class WindowsUnsupportedError(Exception):
pass
# General functions # General functions
def abort(): def abort(show_prompt=True):
"""Abort script.""" """Abort script."""
print_warning('Aborted.') print_warning('Aborted.')
sleep(1) if show_prompt:
pause(prompt='Press Enter to exit... ') sleep(1)
exit_script() pause(prompt='Press Enter to exit... ')
exit_script(1)
def ask(prompt='Kotaero!'): def ask(prompt='Kotaero!'):
@ -163,18 +173,22 @@ def clear_screen():
def convert_to_bytes(size): def convert_to_bytes(size):
"""Convert human-readable size str to bytes and return an int.""" """Convert human-readable size str to bytes and return an int."""
size = str(size) size = str(size)
tmp = re.search(r'(\d+\.?\d*)\s+([KMGT]B)', size.upper()) tmp = re.search(r'(\d+\.?\d*)\s+([PTGMKB])B?', size.upper())
if tmp: if tmp:
size = float(tmp.group(1)) size = float(tmp.group(1))
units = tmp.group(2) units = tmp.group(2)
if units == 'TB': if units == 'P':
size *= 1099511627776 size *= 1024 ** 5
elif units == 'GB': if units == 'T':
size *= 1073741824 size *= 1024 ** 4
elif units == 'MB': elif units == 'G':
size *= 1048576 size *= 1024 ** 3
elif units == 'KB': elif units == 'M':
size *= 1024 size *= 1024 ** 2
elif units == 'K':
size *= 1024 ** 1
elif units == 'B':
size *= 1024 ** 0
size = int(size) size = int(size)
else: else:
return -1 return -1
@ -293,20 +307,24 @@ def human_readable_size(size, decimals=0):
return '{size:>{width}} b'.format(size='???', width=width) return '{size:>{width}} b'.format(size='???', width=width)
# Convert to sensible units # Convert to sensible units
if size >= 1099511627776: if size >= 1024 ** 5:
size /= 1099511627776 size /= 1024 ** 5
units = 'Tb' units = 'PB'
elif size >= 1073741824: elif size >= 1024 ** 4:
size /= 1073741824 size /= 1024 ** 4
units = 'Gb' units = 'TB'
elif size >= 1048576: elif size >= 1024 ** 3:
size /= 1048576 size /= 1024 ** 3
units = 'Mb' units = 'GB'
elif size >= 1024: elif size >= 1024 ** 2:
size /= 1024 size /= 1024 ** 2
units = 'Kb' units = 'MB'
elif size >= 1024 ** 1:
size /= 1024 ** 1
units = 'KB'
else: else:
units = ' b' size /= 1024 ** 0
units = ' B'
# Return # Return
return '{size:>{width}.{decimals}f} {units}'.format( return '{size:>{width}.{decimals}f} {units}'.format(
@ -421,6 +439,8 @@ def non_clobber_rename(full_path):
def pause(prompt='Press Enter to continue... '): def pause(prompt='Press Enter to continue... '):
"""Simple pause implementation.""" """Simple pause implementation."""
if prompt[-1] != ' ':
prompt += ' '
input(prompt) input(prompt)
@ -879,25 +899,19 @@ def make_tmp_dirs():
def set_common_vars(): def set_common_vars():
"""Set common variables.""" """Set common variables."""
global_vars['Date'] = time.strftime("%Y-%m-%d") global_vars['Date'] = time.strftime("%Y-%m-%d")
global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z") global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z")
global_vars['Env'] = os.environ.copy() global_vars['Env'] = os.environ.copy()
global_vars['ArchivePassword'] = ARCHIVE_PASSWORD global_vars['ArchivePassword'] = ARCHIVE_PASSWORD
global_vars['BinDir'] = r'{BaseDir}\.bin'.format( global_vars['BinDir'] = r'{BaseDir}\.bin'.format(**global_vars)
**global_vars) global_vars['CBinDir'] = r'{BaseDir}\.cbin'.format(**global_vars)
global_vars['CBinDir'] = r'{BaseDir}\.cbin'.format( global_vars['ClientDir'] = r'{SYSTEMDRIVE}\{prefix}'.format(
**global_vars) prefix=KIT_NAME_SHORT, **global_vars['Env'])
global_vars['ClientDir'] = r'{SYSTEMDRIVE}\{prefix}'.format( global_vars['BackupDir'] = r'{ClientDir}\Backups'.format(**global_vars)
prefix=KIT_NAME_SHORT, **global_vars['Env']) global_vars['LogDir'] = r'{ClientDir}\Logs\{Date}'.format(**global_vars)
global_vars['BackupDir'] = r'{ClientDir}\Backups'.format( global_vars['QuarantineDir'] = r'{ClientDir}\Quarantine'.format(**global_vars)
**global_vars) global_vars['TmpDir'] = r'{BinDir}\tmp'.format(**global_vars)
global_vars['LogDir'] = r'{ClientDir}\Logs\{Date}'.format(
**global_vars)
global_vars['QuarantineDir'] = r'{ClientDir}\Quarantine'.format(
**global_vars)
global_vars['TmpDir'] = r'{BinDir}\tmp'.format(
**global_vars)
def set_linux_vars(): def set_linux_vars():
@ -905,12 +919,12 @@ def set_linux_vars():
These assume we're running under a WK-Linux build.""" These assume we're running under a WK-Linux build."""
result = run_program(['mktemp', '-d']) result = run_program(['mktemp', '-d'])
global_vars['TmpDir'] = result.stdout.decode().strip() global_vars['TmpDir'] = result.stdout.decode().strip()
global_vars['Date'] = time.strftime("%Y-%m-%d") global_vars['Date'] = time.strftime("%Y-%m-%d")
global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z") global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z")
global_vars['Env'] = os.environ.copy() global_vars['Env'] = os.environ.copy()
global_vars['BinDir'] = '/usr/local/bin' global_vars['BinDir'] = '/usr/local/bin'
global_vars['LogDir'] = global_vars['TmpDir'] global_vars['LogDir'] = '{}/Logs'.format(global_vars['Env']['HOME'])
global_vars['Tools'] = { global_vars['Tools'] = {
'wimlib-imagex': 'wimlib-imagex', 'wimlib-imagex': 'wimlib-imagex',
'SevenZip': '7z', 'SevenZip': '7z',
@ -919,10 +933,13 @@ def set_linux_vars():
def set_log_file(log_name): def set_log_file(log_name):
"""Sets global var LogFile and creates path as needed.""" """Sets global var LogFile and creates path as needed."""
folder_path = '{}{}{}'.format( if psutil.LINUX:
global_vars['LogDir'], folder_path = global_vars['LogDir']
os.sep, else:
KIT_NAME_FULL) folder_path = '{}{}{}'.format(
global_vars['LogDir'],
os.sep,
KIT_NAME_FULL)
log_file = '{}{}{}'.format( log_file = '{}{}{}'.format(
folder_path, folder_path,
os.sep, os.sep,

View file

@ -111,7 +111,7 @@ def find_core_storage_volumes(device_path=None):
# Check log for found volumes # Check log for found volumes
cs_vols = {} cs_vols = {}
with open(log_path, 'r') as f: with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f.readlines(): for line in f.readlines():
r = re.match( r = re.match(
r'^.*echo "([^"]+)" . dmsetup create test(\d)$', r'^.*echo "([^"]+)" . dmsetup create test(\d)$',
@ -151,12 +151,16 @@ def is_valid_wim_file(item):
def get_mounted_volumes(): def get_mounted_volumes():
"""Get mounted volumes, returns dict.""" """Get mounted volumes, returns dict."""
cmd = [ cmd = [
'findmnt', '-J', '-b', '-i', 'findmnt',
'-t', ( '--list',
'--json',
'--bytes',
'--invert',
'--types', (
'autofs,binfmt_misc,bpf,cgroup,cgroup2,configfs,debugfs,devpts,' 'autofs,binfmt_misc,bpf,cgroup,cgroup2,configfs,debugfs,devpts,'
'devtmpfs,hugetlbfs,mqueue,proc,pstore,securityfs,sysfs,tmpfs' 'devtmpfs,hugetlbfs,mqueue,proc,pstore,securityfs,sysfs,tmpfs'
), ),
'-o', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED'] '--output', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED']
json_data = get_json_from_command(cmd) json_data = get_json_from_command(cmd)
mounted_volumes = [] mounted_volumes = []
for item in json_data.get('filesystems', []): for item in json_data.get('filesystems', []):
@ -195,6 +199,8 @@ def mount_volumes(
volumes.update({child['name']: child}) volumes.update({child['name']: child})
for grandchild in child.get('children', []): for grandchild in child.get('children', []):
volumes.update({grandchild['name']: grandchild}) volumes.update({grandchild['name']: grandchild})
for great_grandchild in grandchild.get('children', []):
volumes.update({great_grandchild['name']: great_grandchild})
# Get list of mounted volumes # Get list of mounted volumes
mounted_volumes = get_mounted_volumes() mounted_volumes = get_mounted_volumes()
@ -212,7 +218,7 @@ def mount_volumes(
report[vol_path] = vol_data report[vol_path] = vol_data
elif 'children' in vol_data: elif 'children' in vol_data:
# Skip LVM/RAID partitions (the real volume is mounted separately) # Skip LVM/RAID partitions (the real volume is mounted separately)
vol_data['show_data']['data'] = vol_data.get('fstype', 'UNKNOWN') vol_data['show_data']['data'] = vol_data.get('fstype', 'Unknown')
if vol_data.get('label', None): if vol_data.get('label', None):
vol_data['show_data']['data'] += ' "{}"'.format(vol_data['label']) vol_data['show_data']['data'] += ' "{}"'.format(vol_data['label'])
vol_data['show_data']['info'] = True vol_data['show_data']['info'] = True
@ -237,17 +243,23 @@ def mount_volumes(
if vol_data['show_data']['data'] == 'Failed to mount': if vol_data['show_data']['data'] == 'Failed to mount':
vol_data['mount_point'] = None vol_data['mount_point'] = None
else: else:
fstype = vol_data.get('fstype', 'Unknown FS')
size_used = human_readable_size( size_used = human_readable_size(
mounted_volumes[vol_path]['used']) mounted_volumes[vol_path]['used'],
decimals=1,
)
size_avail = human_readable_size( size_avail = human_readable_size(
mounted_volumes[vol_path]['avail']) mounted_volumes[vol_path]['avail'],
decimals=1,
)
vol_data['size_avail'] = size_avail vol_data['size_avail'] = size_avail
vol_data['size_used'] = size_used vol_data['size_used'] = size_used
vol_data['mount_point'] = mounted_volumes[vol_path]['target'] vol_data['mount_point'] = mounted_volumes[vol_path]['target']
vol_data['show_data']['data'] = 'Mounted on {}'.format( vol_data['show_data']['data'] = 'Mounted on {}'.format(
mounted_volumes[vol_path]['target']) mounted_volumes[vol_path]['target'])
vol_data['show_data']['data'] = '{:40} ({} used, {} free)'.format( vol_data['show_data']['data'] = '{:40} ({}, {} used, {} free)'.format(
vol_data['show_data']['data'], vol_data['show_data']['data'],
fstype,
size_used, size_used,
size_avail) size_avail)
@ -281,6 +293,14 @@ def mount_backup_shares(read_write=False):
def mount_network_share(server, read_write=False): def mount_network_share(server, read_write=False):
"""Mount a network share defined by server.""" """Mount a network share defined by server."""
uid = '1000'
# Get UID
cmd = ['id', '--user', 'tech']
result = run_program(cmd, check=False, encoding='utf-8', errors='ignore')
if result.stdout.strip().isnumeric():
uid = result.stdout.strip()
if read_write: if read_write:
username = server['RW-User'] username = server['RW-User']
password = server['RW-Pass'] password = server['RW-Pass']
@ -296,18 +316,35 @@ def mount_network_share(server, read_write=False):
error = r'Failed to mount \\{Name}\{Share} ({IP})'.format(**server) error = r'Failed to mount \\{Name}\{Share} ({IP})'.format(**server)
success = 'Mounted {Name}'.format(**server) success = 'Mounted {Name}'.format(**server)
elif psutil.LINUX: elif psutil.LINUX:
# Make mountpoint
cmd = [ cmd = [
'sudo', 'mkdir', '-p', 'sudo', 'mkdir', '-p',
'/Backups/{Name}'.format(**server)] '/Backups/{Name}'.format(**server)]
run_program(cmd) run_program(cmd)
# Set mount options
cmd_options = [
# Assuming GID matches UID
'gid={}'.format(uid),
'uid={}'.format(uid),
]
cmd_options.append('rw' if read_write else 'ro')
cmd_options.append('username={}'.format(username))
if password:
cmd_options.append('password={}'.format(password))
else:
# Skip password check
cmd_options.append('guest')
# Set mount command
cmd = [ cmd = [
'sudo', 'mount', 'sudo', 'mount',
'//{IP}/{Share}'.format(**server), '//{IP}/{Share}'.format(**server).replace('\\', '/'),
'/Backups/{Name}'.format(**server), '/Backups/{Name}'.format(**server),
'-o', '{}username={},password={}'.format( '-o', ','.join(cmd_options),
'' if read_write else 'ro,', ]
username,
password)] # Set result messages
warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format( warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format(
**server) **server)
error = 'Failed to mount /Backups/{Name}'.format(**server) error = 'Failed to mount /Backups/{Name}'.format(**server)

View file

@ -1,25 +1,25 @@
# Wizard Kit: Functions - ddrescue-tui # pylint: disable=no-name-in-module,too-many-lines,wildcard-import
# vim: sts=2 sw=2 ts=2
'''Wizard Kit: Functions - ddrescue-tui'''
import datetime import datetime
import pathlib import pathlib
import psutil
import pytz
import re import re
import signal
import stat import stat
import time import time
from operator import itemgetter
from collections import OrderedDict import pytz
from functions.data import * from functions.data import *
from functions.hw_diags import * from functions.hw_diags import *
from functions.json import * from functions.json import *
from functions.tmux import * from functions.tmux import *
from operator import itemgetter
from settings.ddrescue import * from settings.ddrescue import *
# Clases # Clases
class BaseObj(): class BaseObj():
# pylint: disable=missing-docstring
"""Base object used by DevObj, DirObj, and ImageObj.""" """Base object used by DevObj, DirObj, and ImageObj."""
def __init__(self, path): def __init__(self, path):
self.type = 'base' self.type = 'base'
@ -44,6 +44,7 @@ class BaseObj():
class BlockPair(): class BlockPair():
# pylint: disable=too-many-instance-attributes
"""Object to track data and methods together for source and dest.""" """Object to track data and methods together for source and dest."""
def __init__(self, mode, source, dest): def __init__(self, mode, source, dest):
self.mode = mode self.mode = mode
@ -60,9 +61,10 @@ class BlockPair():
if self.mode == 'clone': if self.mode == 'clone':
# Cloning # Cloning
self.dest_path = dest.path self.dest_path = dest.path
self.map_path = '{pwd}/Clone_{prefix}.map'.format( self.map_path = '{cwd}/Clone_{prefix}.map'.format(
pwd=os.path.realpath(global_vars['Env']['PWD']), cwd=os.path.realpath(os.getcwd()),
prefix=source.prefix) prefix=source.prefix,
)
else: else:
# Imaging # Imaging
self.dest_path = '{path}/{prefix}.dd'.format( self.dest_path = '{path}/{prefix}.dd'.format(
@ -105,19 +107,19 @@ class BlockPair():
def load_map_data(self): def load_map_data(self):
"""Load data from map file and set progress.""" """Load data from map file and set progress."""
map_data = read_map_file(self.map_path) map_data = read_map_file(self.map_path)
self.rescued_percent = map_data['rescued'] self.rescued = map_data.get('rescued', 0)
self.rescued = (self.rescued_percent * self.size) / 100 self.rescued_percent = (self.rescued / self.size) * 100
if map_data['full recovery']: if map_data['full recovery']:
self.pass_done = [True, True, True] self.pass_done = [True, True, True]
self.rescued = self.size self.rescued = self.size
self.status = ['Skipped', 'Skipped', 'Skipped'] self.status = ['Skipped', 'Skipped', 'Skipped']
elif map_data['non-tried'] > 0: elif map_data.get('non-tried', 0) > 0:
# Initial pass incomplete # Initial pass incomplete
pass pass
elif map_data['non-trimmed'] > 0: elif map_data.get('non-trimmed', 0) > 0:
self.pass_done = [True, False, False] self.pass_done = [True, False, False]
self.status = ['Skipped', 'Pending', 'Pending'] self.status = ['Skipped', 'Pending', 'Pending']
elif map_data['non-scraped'] > 0: elif map_data.get('non-scraped', 0) > 0:
self.pass_done = [True, True, False] self.pass_done = [True, True, False]
self.status = ['Skipped', 'Skipped', 'Pending'] self.status = ['Skipped', 'Skipped', 'Pending']
else: else:
@ -145,14 +147,15 @@ class BlockPair():
"""Update progress using map file.""" """Update progress using map file."""
if os.path.exists(self.map_path): if os.path.exists(self.map_path):
map_data = read_map_file(self.map_path) map_data = read_map_file(self.map_path)
self.rescued_percent = map_data.get('rescued', 0) self.rescued = map_data.get('rescued', 0)
self.rescued = (self.rescued_percent * self.size) / 100 self.rescued_percent = (self.rescued / self.size) * 100
self.status[pass_num] = get_formatted_status( self.status[pass_num] = get_formatted_status(
label='Pass {}'.format(pass_num+1), label='Pass {}'.format(pass_num+1),
data=(self.rescued/self.size)*100) data=(self.rescued/self.size)*100)
class DevObj(BaseObj): class DevObj(BaseObj):
# pylint: disable=too-many-instance-attributes
"""Block device object.""" """Block device object."""
def self_check(self): def self_check(self):
"""Verify that self.path points to a block device.""" """Verify that self.path points to a block device."""
@ -186,6 +189,7 @@ class DevObj(BaseObj):
self.update_filename_prefix() self.update_filename_prefix()
def update_filename_prefix(self): def update_filename_prefix(self):
# pylint: disable=attribute-defined-outside-init
"""Set filename prefix based on details.""" """Set filename prefix based on details."""
self.prefix = '{m_size}_{model}'.format( self.prefix = '{m_size}_{model}'.format(
m_size=self.model_size, m_size=self.model_size,
@ -205,6 +209,7 @@ class DevObj(BaseObj):
class DirObj(BaseObj): class DirObj(BaseObj):
"""Directory object."""
def self_check(self): def self_check(self):
"""Verify that self.path points to a directory.""" """Verify that self.path points to a directory."""
if not pathlib.Path(self.path).is_dir(): if not pathlib.Path(self.path).is_dir():
@ -222,6 +227,7 @@ class DirObj(BaseObj):
class ImageObj(BaseObj): class ImageObj(BaseObj):
"""Image file object."""
def self_check(self): def self_check(self):
"""Verify that self.path points to a file.""" """Verify that self.path points to a file."""
if not pathlib.Path(self.path).is_file(): if not pathlib.Path(self.path).is_file():
@ -243,10 +249,11 @@ class ImageObj(BaseObj):
self.report = get_device_report(self.loop_dev) self.report = get_device_report(self.loop_dev)
self.report = self.report.replace( self.report = self.report.replace(
self.loop_dev[self.loop_dev.rfind('/')+1:], '(Img)') self.loop_dev[self.loop_dev.rfind('/')+1:], '(Img)')
run_program(['losetup', '--detach', self.loop_dev], check=False) run_program(['sudo', 'losetup', '--detach', self.loop_dev], check=False)
class RecoveryState(): class RecoveryState():
# pylint: disable=too-many-instance-attributes
"""Object to track BlockPair objects and overall state.""" """Object to track BlockPair objects and overall state."""
def __init__(self, mode, source, dest): def __init__(self, mode, source, dest):
self.mode = mode.lower() self.mode = mode.lower()
@ -270,6 +277,7 @@ class RecoveryState():
if mode not in ('clone', 'image'): if mode not in ('clone', 'image'):
raise GenericError('Unsupported mode') raise GenericError('Unsupported mode')
self.get_smart_source() self.get_smart_source()
self.set_working_dir()
def add_block_pair(self, source, dest): def add_block_pair(self, source, dest):
"""Run safety checks and append new BlockPair to internal list.""" """Run safety checks and append new BlockPair to internal list."""
@ -314,20 +322,134 @@ class RecoveryState():
# Safety checks passed # Safety checks passed
self.block_pairs.append(BlockPair(self.mode, source, dest)) self.block_pairs.append(BlockPair(self.mode, source, dest))
def build_outer_panes(self):
"""Build top and side panes."""
clear_screen()
# Top
self.panes['Source'] = tmux_split_window(
behind=True, vertical=True, lines=2,
text='{BLUE}Source{CLEAR}'.format(**COLORS))
# Started
self.panes['Started'] = tmux_split_window(
lines=SIDE_PANE_WIDTH, target_pane=self.panes['Source'],
text='{BLUE}Started{CLEAR}\n{s}'.format(
s=time.strftime("%Y-%m-%d %H:%M %Z"),
**COLORS))
# Destination
self.panes['Destination'] = tmux_split_window(
percent=50, target_pane=self.panes['Source'],
text='{BLUE}Destination{CLEAR}'.format(**COLORS))
# Progress
update_sidepane(self)
self.panes['Progress'] = tmux_split_window(
lines=SIDE_PANE_WIDTH, watch=self.progress_out)
def current_pass_done(self): def current_pass_done(self):
"""Checks if pass is done for all block-pairs, returns bool.""" """Checks if pass is done for all block-pairs, returns bool."""
done = True done = True
for bp in self.block_pairs: for b_pair in self.block_pairs:
done &= bp.pass_done[self.current_pass] done = done and b_pair.pass_done[self.current_pass]
return done return done
def current_pass_min(self): def current_pass_min(self):
"""Gets minimum pass rescued percentage, returns float.""" """Gets minimum pass rescued percentage, returns float."""
min_percent = 100 min_percent = 100
for bp in self.block_pairs: for b_pair in self.block_pairs:
min_percent = min(min_percent, bp.rescued_percent) min_percent = min(min_percent, b_pair.rescued_percent)
return min_percent return min_percent
def fix_tmux_panes(self, forced=False):
# pylint: disable=too-many-branches,too-many-locals
"""Fix pane sizes if the winodw has been resized."""
needs_fixed = False
# Check layout
for pane, pane_data in TMUX_LAYOUT.items():
if not pane_data.get('Check'):
# Not concerned with the size of this pane
continue
# Get target
target = None
if pane != 'Current':
if pane not in self.panes:
# Skip missing panes
continue
else:
target = self.panes[pane]
# Check pane size
size_x, size_y = tmux_get_pane_size(pane_id=target)
if pane_data.get('x', False) and pane_data['x'] != size_x:
needs_fixed = True
if pane_data.get('y', False) and pane_data['y'] != size_y:
needs_fixed = True
# Bail?
if not needs_fixed and not forced:
return
# Remove Destination pane (temporarily)
tmux_kill_pane(self.panes['Destination'])
# Update layout
for pane, pane_data in TMUX_LAYOUT.items():
# Get target
target = None
if pane != 'Current':
if pane not in self.panes:
# Skip missing panes
continue
else:
target = self.panes[pane]
# Resize pane
tmux_resize_pane(pane_id=target, **pane_data)
# Calc Source/Destination pane sizes
width, height = tmux_get_pane_size()
width = int(width / 2) - 1
# Update Source string
source_str = self.source.name
if len(source_str) > width:
source_str = '{}...'.format(source_str[:width-3])
# Update Destination string
dest_str = self.dest.name
if len(dest_str) > width:
if self.mode == 'clone':
dest_str = '{}...'.format(dest_str[:width-3])
else:
dest_str = '...{}'.format(dest_str[-width+3:])
# Rebuild Source/Destination panes
tmux_update_pane(
pane_id=self.panes['Source'],
text='{BLUE}Source{CLEAR}\n{s}'.format(
s=source_str, **COLORS))
self.panes['Destination'] = tmux_split_window(
percent=50, target_pane=self.panes['Source'],
text='{BLUE}Destination{CLEAR}\n{s}'.format(
s=dest_str, **COLORS))
if 'SMART' in self.panes:
# Calc SMART/ddrescue/Journal panes sizes
ratio = [12, 22, 4]
width, height = tmux_get_pane_size(pane_id=self.panes['Progress'])
height -= 2
total = sum(ratio)
p_ratio = [int((x/total) * height) for x in ratio]
p_ratio[1] = height - p_ratio[0] - p_ratio[2]
# Resize SMART/Journal panes
tmux_resize_pane(self.panes['SMART'], y=ratio[0])
tmux_resize_pane(y=ratio[1])
tmux_resize_pane(self.panes['Journal'], y=ratio[2])
def get_smart_source(self): def get_smart_source(self):
"""Get source for SMART dispay.""" """Get source for SMART dispay."""
disk_path = self.source.path disk_path = self.source.path
@ -339,18 +461,15 @@ class RecoveryState():
def retry_all_passes(self): def retry_all_passes(self):
"""Mark all passes as pending for all block-pairs.""" """Mark all passes as pending for all block-pairs."""
self.finished = False self.finished = False
for bp in self.block_pairs: for b_pair in self.block_pairs:
bp.pass_done = [False, False, False] b_pair.pass_done = [False, False, False]
bp.status = ['Pending', 'Pending', 'Pending'] b_pair.status = ['Pending', 'Pending', 'Pending']
bp.fix_status_strings() b_pair.fix_status_strings()
self.set_pass_num() self.set_pass_num()
def self_checks(self): def self_checks(self):
"""Run self-checks and update state values.""" """Run self-checks and update state values."""
cmd = ['findmnt', '--json', '--target', os.getcwd()] cmd = ['findmnt', '--json', '--target', os.getcwd()]
map_allowed_fstypes = RECOMMENDED_FSTYPES.copy()
map_allowed_fstypes.extend(['cifs', 'ext2', 'vfat'])
map_allowed_fstypes.sort()
json_data = get_json_from_command(cmd) json_data = get_json_from_command(cmd)
# Abort if json_data is empty # Abort if json_data is empty
@ -361,23 +480,24 @@ class RecoveryState():
# Avoid saving map to non-persistent filesystem # Avoid saving map to non-persistent filesystem
fstype = json_data.get( fstype = json_data.get(
'filesystems', [{}])[0].get( 'filesystems', [{}])[0].get(
'fstype', 'unknown') 'fstype', 'unknown')
if fstype not in map_allowed_fstypes: if fstype not in RECOMMENDED_MAP_FSTYPES:
print_error( print_error(
"Map isn't being saved to a recommended filesystem ({})".format( "Map isn't being saved to a recommended filesystem ({})".format(
fstype.upper())) fstype.upper()))
print_info('Recommended types are: {}'.format( print_info('Recommended types are: {}'.format(
' / '.join(map_allowed_fstypes).upper())) ' / '.join(RECOMMENDED_MAP_FSTYPES).upper()))
print_standard(' ') print_standard(' ')
if not ask('Proceed anyways? (Strongly discouraged)'): if not ask('Proceed anyways? (Strongly discouraged)'):
raise GenericAbort() raise GenericAbort()
# Run BlockPair self checks and get total size # Run BlockPair self checks and get total size
self.total_size = 0 self.total_size = 0
for bp in self.block_pairs: for b_pair in self.block_pairs:
bp.self_check() b_pair.self_check()
self.resumed |= bp.resumed if b_pair.resumed:
self.total_size += bp.size self.resumed = True
self.total_size += b_pair.size
def set_pass_num(self): def set_pass_num(self):
"""Set current pass based on all block-pair's progress.""" """Set current pass based on all block-pair's progress."""
@ -385,8 +505,8 @@ class RecoveryState():
for pass_num in (2, 1, 0): for pass_num in (2, 1, 0):
# Iterate backwards through passes # Iterate backwards through passes
pass_done = True pass_done = True
for bp in self.block_pairs: for b_pair in self.block_pairs:
pass_done &= bp.pass_done[pass_num] pass_done = pass_done and b_pair.pass_done[pass_num]
if pass_done: if pass_done:
# All block-pairs reported being done # All block-pairs reported being done
# Set to next pass, unless we're on the last pass (2) # Set to next pass, unless we're on the last pass (2)
@ -404,6 +524,34 @@ class RecoveryState():
elif self.current_pass == 2: elif self.current_pass == 2:
self.current_pass_str = '3 "Scraping bad areas"' self.current_pass_str = '3 "Scraping bad areas"'
def set_working_dir(self):
# pylint: disable=no-self-use
"""Set working dir to MAP_DIR if possible.
NOTE: This is to help ensure the map file
is saved to non-volatile storage."""
map_dir = '{}/{}'.format(MAP_DIR, global_vars['Date-Time'])
# Mount backup shares
mount_backup_shares(read_write=True)
# Get MAP_DIR filesystem type
# NOTE: If the backup share fails to mount then this will
# likely be the type of /
cmd = [
'findmnt',
'--noheadings',
'--target', MAP_DIR,
'--output', 'FSTYPE',
]
result = run_program(cmd, check=False, encoding='utf-8', errors='ingnore')
map_dir_type = result.stdout.strip().lower()
# Change working dir if map_dir_type is acceptable
if map_dir_type in RECOMMENDED_MAP_FSTYPES:
os.makedirs(map_dir, exist_ok=True)
os.chdir(map_dir)
def update_etoc(self): def update_etoc(self):
"""Search ddrescue output for the current EToC, returns str.""" """Search ddrescue output for the current EToC, returns str."""
now = datetime.datetime.now(tz=self.timezone) now = datetime.datetime.now(tz=self.timezone)
@ -413,7 +561,7 @@ class RecoveryState():
# Just set to N/A (NOTE: this overrules the refresh rate below) # Just set to N/A (NOTE: this overrules the refresh rate below)
self.etoc = 'N/A' self.etoc = 'N/A'
return return
elif 'In Progress' not in self.status: if 'In Progress' not in self.status:
# Don't update when EToC is hidden # Don't update when EToC is hidden
return return
if now.second % ETOC_REFRESH_RATE != 0: if now.second % ETOC_REFRESH_RATE != 0:
@ -427,13 +575,14 @@ class RecoveryState():
# Capture main tmux pane # Capture main tmux pane
try: try:
text = tmux_capture_pane() text = tmux_capture_pane()
except Exception: except Exception: # pylint: disable=broad-except
# Ignore # Ignore
pass pass
# Search for EToC delta # Search for EToC delta
matches = re.findall(r'remaining time:.*$', text, re.MULTILINE) matches = re.findall(r'remaining time:.*$', text, re.MULTILINE)
if matches: if matches:
# pylint: disable=invalid-name
r = REGEX_REMAINING_TIME.search(matches[-1]) r = REGEX_REMAINING_TIME.search(matches[-1])
if r.group('na'): if r.group('na'):
self.etoc = 'N/A' self.etoc = 'N/A'
@ -450,7 +599,7 @@ class RecoveryState():
minutes=int(minutes), minutes=int(minutes),
seconds=int(seconds), seconds=int(seconds),
) )
except Exception: except Exception: # pylint: disable=broad-except
# Ignore and leave as raw string # Ignore and leave as raw string
pass pass
@ -460,15 +609,16 @@ class RecoveryState():
now = datetime.datetime.now(tz=self.timezone) now = datetime.datetime.now(tz=self.timezone)
_etoc = now + etoc_delta _etoc = now + etoc_delta
self.etoc = _etoc.strftime('%Y-%m-%d %H:%M %Z') self.etoc = _etoc.strftime('%Y-%m-%d %H:%M %Z')
except Exception: except Exception: # pylint: disable=broad-except
# Ignore and leave as current string # Ignore and leave as current string
pass pass
def update_progress(self): def update_progress(self):
# pylint: disable=attribute-defined-outside-init
"""Update overall progress using block_pairs.""" """Update overall progress using block_pairs."""
self.rescued = 0 self.rescued = 0
for bp in self.block_pairs: for b_pair in self.block_pairs:
self.rescued += bp.rescued self.rescued += b_pair.rescued
self.rescued_percent = (self.rescued / self.total_size) * 100 self.rescued_percent = (self.rescued / self.total_size) * 100
self.status_percent = get_formatted_status( self.status_percent = get_formatted_status(
label='Recovered:', data=self.rescued_percent) label='Recovered:', data=self.rescued_percent)
@ -477,26 +627,6 @@ class RecoveryState():
# Functions # Functions
def build_outer_panes(state):
"""Build top and side panes."""
state.panes['Source'] = tmux_split_window(
behind=True, vertical=True, lines=2,
text='{BLUE}Source{CLEAR}'.format(**COLORS))
state.panes['Started'] = tmux_split_window(
lines=SIDE_PANE_WIDTH, target_pane=state.panes['Source'],
text='{BLUE}Started{CLEAR}\n{s}'.format(
s=time.strftime("%Y-%m-%d %H:%M %Z"),
**COLORS))
state.panes['Destination'] = tmux_split_window(
percent=50, target_pane=state.panes['Source'],
text='{BLUE}Destination{CLEAR}'.format(**COLORS))
# Side pane
update_sidepane(state)
state.panes['Progress'] = tmux_split_window(
lines=SIDE_PANE_WIDTH, watch=state.progress_out)
def create_path_obj(path): def create_path_obj(path):
"""Create Dev, Dir, or Image obj based on path given.""" """Create Dev, Dir, or Image obj based on path given."""
obj = None obj = None
@ -514,101 +644,16 @@ def create_path_obj(path):
def double_confirm_clone(): def double_confirm_clone():
"""Display warning and get 2nd confirmation, returns bool.""" """Display warning and get 2nd confirmation, returns bool."""
print_standard('\nSAFETY CHECK') print_standard('\nSAFETY CHECK')
print_warning('All data will be DELETED from the ' print_warning(
'destination device and partition(s) listed above.') 'All data will be DELETED from the '
print_warning('This is irreversible and will lead ' 'destination device and partition(s) listed above.'
'to {CLEAR}{RED}DATA LOSS.'.format(**COLORS)) )
print_warning(
'This is irreversible and will lead to {CLEAR}{RED}DATA LOSS.'.format(
**COLORS))
return ask('Asking again to confirm, is this correct?') return ask('Asking again to confirm, is this correct?')
def fix_tmux_panes(state, forced=False):
"""Fix pane sizes if the winodw has been resized."""
needs_fixed = False
# Check layout
for k, v in TMUX_LAYOUT.items():
if not v.get('Check'):
# Not concerned with the size of this pane
continue
# Get target
target = None
if k != 'Current':
if k not in state.panes:
# Skip missing panes
continue
else:
target = state.panes[k]
# Check pane size
x, y = tmux_get_pane_size(pane_id=target)
if v.get('x', False) and v['x'] != x:
needs_fixed = True
if v.get('y', False) and v['y'] != y:
needs_fixed = True
# Bail?
if not needs_fixed and not forced:
return
# Remove Destination pane (temporarily)
tmux_kill_pane(state.panes['Destination'])
# Update layout
for k, v in TMUX_LAYOUT.items():
# Get target
target = None
if k != 'Current':
if k not in state.panes:
# Skip missing panes
continue
else:
target = state.panes[k]
# Resize pane
tmux_resize_pane(pane_id=target, **v)
# Calc Source/Destination pane sizes
width, height = tmux_get_pane_size()
width = int(width / 2) - 1
# Update Source string
source_str = state.source.name
if len(source_str) > width:
source_str = '{}...'.format(source_str[:width-3])
# Update Destination string
dest_str = state.dest.name
if len(dest_str) > width:
if state.mode == 'clone':
dest_str = '{}...'.format(dest_str[:width-3])
else:
dest_str = '...{}'.format(dest_str[-width+3:])
# Rebuild Source/Destination panes
tmux_update_pane(
pane_id=state.panes['Source'],
text='{BLUE}Source{CLEAR}\n{s}'.format(
s=source_str, **COLORS))
state.panes['Destination'] = tmux_split_window(
percent=50, target_pane=state.panes['Source'],
text='{BLUE}Destination{CLEAR}\n{s}'.format(
s=dest_str, **COLORS))
if 'SMART' in state.panes:
# Calc SMART/ddrescue/Journal panes sizes
ratio = [12, 22, 4]
width, height = tmux_get_pane_size(pane_id=state.panes['Progress'])
height -= 2
total = sum(ratio)
p_ratio = [int((x/total) * height) for x in ratio]
p_ratio[1] = height - p_ratio[0] - p_ratio[2]
# Resize SMART/Journal panes
tmux_resize_pane(state.panes['SMART'], y=ratio[0])
tmux_resize_pane(y=ratio[1])
tmux_resize_pane(state.panes['Journal'], y=ratio[2])
def get_device_details(dev_path): def get_device_details(dev_path):
"""Get device details via lsblk, returns JSON dict.""" """Get device details via lsblk, returns JSON dict."""
cmd = ['lsblk', '--json', '--output-all', '--paths', dev_path] cmd = ['lsblk', '--json', '--output-all', '--paths', dev_path]
@ -677,22 +722,22 @@ def get_dir_report(dir_path):
output.append('{BLUE}{label:<{width}}{line}{CLEAR}'.format( output.append('{BLUE}{label:<{width}}{line}{CLEAR}'.format(
label='PATH', label='PATH',
width=width, width=width,
line=line.replace('\n',''), line=line.replace('\n', ''),
**COLORS)) **COLORS))
else: else:
output.append('{path:<{width}}{line}'.format( output.append('{path:<{width}}{line}'.format(
path=dir_path, path=dir_path,
width=width, width=width,
line=line.replace('\n',''))) line=line.replace('\n', '')))
# Done # Done
return '\n'.join(output) return '\n'.join(output)
def get_size_in_bytes(s): def get_size_in_bytes(size):
"""Convert size string from lsblk string to bytes, returns int.""" """Convert size string from lsblk string to bytes, returns int."""
s = re.sub(r'(\d+\.?\d*)\s*([KMGTB])B?', r'\1 \2B', s, re.IGNORECASE) size = re.sub(r'(\d+\.?\d*)\s*([KMGTB])B?', r'\1 \2B', size, re.IGNORECASE)
return convert_to_bytes(s) return convert_to_bytes(size)
def get_formatted_status(label, data): def get_formatted_status(label, data):
@ -700,13 +745,15 @@ def get_formatted_status(label, data):
data_width = SIDE_PANE_WIDTH - len(label) data_width = SIDE_PANE_WIDTH - len(label)
try: try:
data_str = '{data:>{data_width}.2f} %'.format( data_str = '{data:>{data_width}.2f} %'.format(
data=data, data=data,
data_width=data_width-2) data_width=data_width-2,
)
except ValueError: except ValueError:
# Assuming non-numeric data # Assuming non-numeric data
data_str = '{data:>{data_width}}'.format( data_str = '{data:>{data_width}}'.format(
data=data, data=data,
data_width=data_width) data_width=data_width,
)
status = '{label}{s_color}{data_str}{CLEAR}'.format( status = '{label}{s_color}{data_str}{CLEAR}'.format(
label=label, label=label,
s_color=get_status_color(data), s_color=get_status_color(data),
@ -715,19 +762,19 @@ def get_formatted_status(label, data):
return status return status
def get_status_color(s, t_success=99, t_warn=90): def get_status_color(status, t_success=99, t_warn=90):
"""Get color based on status, returns str.""" """Get color based on status, returns str."""
color = COLORS['CLEAR'] color = COLORS['CLEAR']
p_recovered = -1 p_recovered = -1
try: try:
p_recovered = float(s) p_recovered = float(status)
except ValueError: except ValueError:
# Status is either in lists below or will default to red # Status is either in lists below or will default to red
pass pass
if s in ('Pending',) or str(s)[-2:] in (' b', 'Kb', 'Mb', 'Gb', 'Tb'): if status == 'Pending' or str(status)[-2:] in (' b', 'Kb', 'Mb', 'Gb', 'Tb'):
color = COLORS['CLEAR'] color = COLORS['CLEAR']
elif s in ('Skipped', 'Unknown'): elif status in ('Skipped', 'Unknown'):
color = COLORS['YELLOW'] color = COLORS['YELLOW']
elif p_recovered >= t_success: elif p_recovered >= t_success:
color = COLORS['GREEN'] color = COLORS['GREEN']
@ -742,9 +789,9 @@ def is_writable_dir(dir_obj):
"""Check if we have read-write-execute permissions, returns bool.""" """Check if we have read-write-execute permissions, returns bool."""
is_ok = True is_ok = True
path_st_mode = os.stat(dir_obj.path).st_mode path_st_mode = os.stat(dir_obj.path).st_mode
is_ok == is_ok and path_st_mode & stat.S_IRUSR is_ok = is_ok and path_st_mode & stat.S_IRUSR
is_ok == is_ok and path_st_mode & stat.S_IWUSR is_ok = is_ok and path_st_mode & stat.S_IWUSR
is_ok == is_ok and path_st_mode & stat.S_IXUSR is_ok = is_ok and path_st_mode & stat.S_IXUSR
return is_ok return is_ok
@ -754,6 +801,7 @@ def is_writable_filesystem(dir_obj):
def menu_ddrescue(source_path, dest_path, run_mode): def menu_ddrescue(source_path, dest_path, run_mode):
# pylint: disable=too-many-branches
"""ddrescue menu.""" """ddrescue menu."""
source = None source = None
dest = None dest = None
@ -797,9 +845,8 @@ def menu_ddrescue(source_path, dest_path, run_mode):
raise GenericAbort() raise GenericAbort()
# Main menu # Main menu
clear_screen() state.build_outer_panes()
build_outer_panes(state) state.fix_tmux_panes(forced=True)
fix_tmux_panes(state, forced=True)
menu_main(state) menu_main(state)
# Done # Done
@ -808,6 +855,7 @@ def menu_ddrescue(source_path, dest_path, run_mode):
def menu_main(state): def menu_main(state):
# pylint: disable=too-many-branches,too-many-statements
"""Main menu is used to set ddrescue settings.""" """Main menu is used to set ddrescue settings."""
checkmark = '*' checkmark = '*'
if 'DISPLAY' in global_vars['Env']: if 'DISPLAY' in global_vars['Env']:
@ -818,16 +866,15 @@ def menu_main(state):
# Build menu # Build menu
main_options = [ main_options = [
{'Base Name': 'Auto continue (if recovery % over threshold)', {'Base Name': 'Auto continue (if recovery % over threshold)',
'Enabled': True}, 'Enabled': True},
{'Base Name': 'Retry (mark non-rescued sectors "non-tried")', {'Base Name': 'Retry (mark non-rescued sectors "non-tried")',
'Enabled': False}, 'Enabled': False},
{'Base Name': 'Reverse direction', 'Enabled': False}, {'Base Name': 'Reverse direction', 'Enabled': False},
] ]
actions = [ actions = [
{'Name': 'Start', 'Letter': 'S'}, {'Name': 'Start', 'Letter': 'S'},
{'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format( {'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format(**COLORS),
**COLORS), 'Letter': 'C'},
'Letter': 'C'},
{'Name': 'Quit', 'Letter': 'Q', 'CRLF': True}, {'Name': 'Quit', 'Letter': 'Q', 'CRLF': True},
] ]
@ -858,13 +905,13 @@ def menu_main(state):
elif selection == 'S': elif selection == 'S':
# Set settings for pass # Set settings for pass
pass_settings = [] pass_settings = []
for k, v in state.settings.items(): for option, option_data in state.settings.items():
if not v['Enabled']: if not option_data['Enabled']:
continue continue
if 'Value' in v: if 'Value' in option_data:
pass_settings.append('{}={}'.format(k, v['Value'])) pass_settings.append('{}={}'.format(option, option_data['Value']))
else: else:
pass_settings.append(k) pass_settings.append(option)
for opt in main_options: for opt in main_options:
if 'Auto' in opt['Base Name']: if 'Auto' in opt['Base Name']:
auto_run = opt['Enabled'] auto_run = opt['Enabled']
@ -887,7 +934,7 @@ def menu_main(state):
state.current_pass_min() < AUTO_PASS_1_THRESHOLD): state.current_pass_min() < AUTO_PASS_1_THRESHOLD):
auto_run = False auto_run = False
elif (state.current_pass == 1 and elif (state.current_pass == 1 and
state.current_pass_min() < AUTO_PASS_2_THRESHOLD): state.current_pass_min() < AUTO_PASS_2_THRESHOLD):
auto_run = False auto_run = False
else: else:
auto_run = False auto_run = False
@ -916,13 +963,15 @@ def menu_settings(state):
# Build menu # Build menu
settings = [] settings = []
for k, v in sorted(state.settings.items()): for option, option_data in sorted(state.settings.items()):
if not v.get('Hidden', False): if not option_data.get('Hidden', False):
settings.append({'Base Name': k, 'Flag': k}) settings.append({'Base Name': option, 'Flag': option})
actions = [{'Name': 'Main Menu', 'Letter': 'M'}] actions = [{'Name': 'Main Menu', 'Letter': 'M'}]
# Show menu # Show menu
while True: while True:
# pylint: disable=invalid-name
# TODO: Clean up and/or replace with new menu-select function
for s in settings: for s in settings:
s['Name'] = '{}{}{}'.format( s['Name'] = '{}{}{}'.format(
s['Base Name'], s['Base Name'],
@ -959,25 +1008,27 @@ def menu_settings(state):
def read_map_file(map_path): def read_map_file(map_path):
"""Read map file with ddrescuelog and return data as dict.""" """Read map file with ddrescuelog and return data as dict."""
map_data = {'full recovery': False} cmd = [
'ddrescuelog',
'--binary-prefixes',
'--show-status',
map_path,
]
map_data = {'full recovery': False, 'pass completed': False}
try: try:
result = run_program(['ddrescuelog', '-t', map_path]) result = run_program(cmd, encoding='utf-8', errors='ignore')
except CalledProcessError: except CalledProcessError:
# (Grossly) assuming map_data hasn't been saved yet, return empty dict # (Grossly) assuming map_data hasn't been saved yet, return empty dict
return map_data return map_data
# Parse output # Parse output
for line in result.stdout.decode().splitlines(): for line in result.stdout.splitlines():
m = re.match( line = line.strip()
r'^\s*(?P<key>\S+):.*\(\s*(?P<value>\d+\.?\d*)%.*', line.strip()) _r = REGEX_DDRESCUE_LOG.search(line)
if m: if _r:
try: map_data[_r.group('key')] = convert_to_bytes('{size} {unit}B'.format(
map_data[m.group('key')] = float(m.group('value')) **_r.groupdict()))
except ValueError: map_data['pass completed'] = 'current status: finished' in line
raise GenericError('Failed to read map data')
m = re.match(r'.*current status:\s+(?P<status>.*)', line.strip())
if m:
map_data['pass completed'] = bool(m.group('status') == 'finished')
# Check if 100% done # Check if 100% done
try: try:
@ -991,6 +1042,7 @@ def read_map_file(map_path):
def run_ddrescue(state, pass_settings): def run_ddrescue(state, pass_settings):
# pylint: disable=too-many-branches,too-many-statements
"""Run ddrescue pass.""" """Run ddrescue pass."""
return_code = -1 return_code = -1
aborted = False aborted = False
@ -1005,8 +1057,8 @@ def run_ddrescue(state, pass_settings):
# Create SMART monitor pane # Create SMART monitor pane
state.smart_out = '{}/smart_{}.out'.format( state.smart_out = '{}/smart_{}.out'.format(
global_vars['TmpDir'], state.smart_source.name) global_vars['TmpDir'], state.smart_source.name)
with open(state.smart_out, 'w') as f: with open(state.smart_out, 'w') as _f:
f.write('Initializing...') _f.write('Initializing...')
state.panes['SMART'] = tmux_split_window( state.panes['SMART'] = tmux_split_window(
behind=True, lines=12, vertical=True, watch=state.smart_out) behind=True, lines=12, vertical=True, watch=state.smart_out)
@ -1016,19 +1068,19 @@ def run_ddrescue(state, pass_settings):
command=['sudo', 'journalctl', '-f']) command=['sudo', 'journalctl', '-f'])
# Fix layout # Fix layout
fix_tmux_panes(state, forced=True) state.fix_tmux_panes(forced=True)
# Run pass for each block-pair # Run pass for each block-pair
for bp in state.block_pairs: for b_pair in state.block_pairs:
if bp.pass_done[state.current_pass]: if b_pair.pass_done[state.current_pass]:
# Skip to next block-pair # Skip to next block-pair
continue continue
update_sidepane(state) update_sidepane(state)
# Set ddrescue cmd # Set ddrescue cmd
cmd = [ cmd = [
'ddrescue', *pass_settings, 'sudo', 'ddrescue', *pass_settings,
bp.source_path, bp.dest_path, bp.map_path] b_pair.source_path, b_pair.dest_path, b_pair.map_path]
if state.mode == 'clone': if state.mode == 'clone':
cmd.append('--force') cmd.append('--force')
if state.current_pass == 0: if state.current_pass == 0:
@ -1043,36 +1095,36 @@ def run_ddrescue(state, pass_settings):
# Start ddrescue # Start ddrescue
try: try:
clear_screen() clear_screen()
print_info('Current dev: {}'.format(bp.source_path)) print_info('Current dev: {}'.format(b_pair.source_path))
ddrescue_proc = popen_program(cmd) ddrescue_proc = popen_program(cmd)
i = 0 i = 0
while True: while True:
# Update SMART display (every 30 seconds) # Update SMART display (every 30 seconds)
if i % 30 == 0: if i % 30 == 0:
state.smart_source.get_smart_details() state.smart_source.get_smart_details()
with open(state.smart_out, 'w') as f: with open(state.smart_out, 'w') as _f:
report = state.smart_source.generate_attribute_report( report = state.smart_source.generate_attribute_report(
timestamp=True) timestamp=True)
for line in report: for line in report:
f.write('{}\n'.format(line)) _f.write('{}\n'.format(line))
i += 1 i += 1
# Update progress # Update progress
bp.update_progress(state.current_pass) b_pair.update_progress(state.current_pass)
update_sidepane(state) update_sidepane(state)
# Fix panes # Fix panes
fix_tmux_panes(state) state.fix_tmux_panes()
# Check if ddrescue has finished # Check if ddrescue has finished
try: try:
ddrescue_proc.wait(timeout=1) ddrescue_proc.wait(timeout=1)
sleep(2) sleep(2)
bp.update_progress(state.current_pass) b_pair.update_progress(state.current_pass)
update_sidepane(state) update_sidepane(state)
break break
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
# Catch to update smart/bp/sidepane # Catch to update smart/b_pair/sidepane
pass pass
except KeyboardInterrupt: except KeyboardInterrupt:
@ -1081,7 +1133,7 @@ def run_ddrescue(state, pass_settings):
ddrescue_proc.wait(timeout=10) ddrescue_proc.wait(timeout=10)
# Update progress/sidepane again # Update progress/sidepane again
bp.update_progress(state.current_pass) b_pair.update_progress(state.current_pass)
update_sidepane(state) update_sidepane(state)
# Was ddrescue aborted? # Was ddrescue aborted?
@ -1103,7 +1155,7 @@ def run_ddrescue(state, pass_settings):
break break
else: else:
# Mark pass finished # Mark pass finished
bp.finish_pass(state.current_pass) b_pair.finish_pass(state.current_pass)
update_sidepane(state) update_sidepane(state)
# Done # Done
@ -1119,6 +1171,8 @@ def run_ddrescue(state, pass_settings):
def select_parts(source_device): def select_parts(source_device):
# pylint: disable=too-many-branches
# TODO: Clean up and/or replace with new menu-select function
"""Select partition(s) or whole device, returns list of DevObj()s.""" """Select partition(s) or whole device, returns list of DevObj()s."""
selected_parts = [] selected_parts = []
children = source_device.details.get('children', []) children = source_device.details.get('children', [])
@ -1180,24 +1234,26 @@ def select_parts(source_device):
raise GenericAbort() raise GenericAbort()
# Build list of selected parts # Build list of selected parts
for d in dev_options: for _d in dev_options:
if d['Selected']: if _d['Selected']:
d['Dev'].model = source_device.model _d['Dev'].model = source_device.model
d['Dev'].model_size = source_device.model_size _d['Dev'].model_size = source_device.model_size
d['Dev'].update_filename_prefix() _d['Dev'].update_filename_prefix()
selected_parts.append(d['Dev']) selected_parts.append(_d['Dev'])
return selected_parts return selected_parts
def select_path(skip_device=None): def select_path(skip_device=None):
# pylint: disable=too-many-branches,too-many-locals
# TODO: Clean up and/or replace with new menu-select function
"""Optionally mount local dev and select path, returns DirObj.""" """Optionally mount local dev and select path, returns DirObj."""
wd = os.path.realpath(global_vars['Env']['PWD']) work_dir = os.path.realpath(global_vars['Env']['PWD'])
selected_path = None selected_path = None
# Build menu # Build menu
path_options = [ path_options = [
{'Name': 'Current directory: {}'.format(wd), 'Path': wd}, {'Name': 'Current directory: {}'.format(work_dir), 'Path': work_dir},
{'Name': 'Local device', 'Path': None}, {'Name': 'Local device', 'Path': None},
{'Name': 'Enter manually', 'Path': None}] {'Name': 'Enter manually', 'Path': None}]
actions = [{'Name': 'Quit', 'Letter': 'Q'}] actions = [{'Name': 'Quit', 'Letter': 'Q'}]
@ -1212,9 +1268,9 @@ def select_path(skip_device=None):
raise GenericAbort() raise GenericAbort()
elif selection.isnumeric(): elif selection.isnumeric():
index = int(selection) - 1 index = int(selection) - 1
if path_options[index]['Path'] == wd: if path_options[index]['Path'] == work_dir:
# Current directory # Current directory
selected_path = DirObj(wd) selected_path = DirObj(work_dir)
elif path_options[index]['Name'] == 'Local device': elif path_options[index]['Name'] == 'Local device':
# Local device # Local device
@ -1230,15 +1286,15 @@ def select_path(skip_device=None):
# Select volume # Select volume
vol_options = [] vol_options = []
for k, v in sorted(report.items()): for _k, _v in sorted(report.items()):
disabled = v['show_data']['data'] == 'Failed to mount' disabled = _v['show_data']['data'] == 'Failed to mount'
if disabled: if disabled:
name = '{name} (Failed to mount)'.format(**v) name = '{name} (Failed to mount)'.format(**_v)
else: else:
name = '{name} (mounted on "{mount_point}")'.format(**v) name = '{name} (mounted on "{mount_point}")'.format(**_v)
vol_options.append({ vol_options.append({
'Name': name, 'Name': name,
'Path': v['mount_point'], 'Path': _v['mount_point'],
'Disabled': disabled}) 'Disabled': disabled})
selection = menu_select( selection = menu_select(
title='Please select a volume', title='Please select a volume',
@ -1313,15 +1369,17 @@ def select_device(description='device', skip_device=None):
action_entries=actions, action_entries=actions,
disabled_label='ALREADY SELECTED') disabled_label='ALREADY SELECTED')
if selection == 'Q':
raise GenericAbort()
if selection.isnumeric(): if selection.isnumeric():
return dev_options[int(selection)-1]['Dev'] return dev_options[int(selection)-1]['Dev']
elif selection == 'Q':
raise GenericAbort()
def setup_loopback_device(source_path): def setup_loopback_device(source_path):
"""Setup loopback device for source_path, returns dev_path as str.""" """Setup loopback device for source_path, returns dev_path as str."""
cmd = ( cmd = (
'sudo',
'losetup', 'losetup',
'--find', '--find',
'--partscan', '--partscan',
@ -1355,6 +1413,7 @@ def show_selection_details(state):
def show_usage(script_name): def show_usage(script_name):
"""Show usage."""
print_info('Usage:') print_info('Usage:')
print_standard(USAGE.format(script_name=script_name)) print_standard(USAGE.format(script_name=script_name))
pause() pause()
@ -1378,14 +1437,14 @@ def update_sidepane(state):
output.append('─────────────────────') output.append('─────────────────────')
# Source(s) progress # Source(s) progress
for bp in state.block_pairs: for b_pair in state.block_pairs:
if state.source.is_image(): if state.source.is_image():
output.append('{BLUE}Image File{CLEAR}'.format(**COLORS)) output.append('{BLUE}Image File{CLEAR}'.format(**COLORS))
else: else:
output.append('{BLUE}{source}{CLEAR}'.format( output.append('{BLUE}{source}{CLEAR}'.format(
source=bp.source_path, source=b_pair.source_path,
**COLORS)) **COLORS))
output.extend(bp.status) output.extend(b_pair.status)
output.append(' ') output.append(' ')
# EToC # EToC
@ -1404,11 +1463,9 @@ def update_sidepane(state):
# Add line-endings # Add line-endings
output = ['{}\n'.format(line) for line in output] output = ['{}\n'.format(line) for line in output]
with open(state.progress_out, 'w') as f: with open(state.progress_out, 'w') as _f:
f.writelines(output) _f.writelines(output)
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -36,6 +36,7 @@ class CpuObj():
self.tests = OrderedDict() self.tests = OrderedDict()
self.get_details() self.get_details()
self.name = self.lscpu.get('Model name', 'Unknown CPU') self.name = self.lscpu.get('Model name', 'Unknown CPU')
self.description = self.name
def get_details(self): def get_details(self):
"""Get CPU details from lscpu.""" """Get CPU details from lscpu."""
@ -57,6 +58,13 @@ class CpuObj():
report.append('{BLUE}Device{CLEAR}'.format(**COLORS)) report.append('{BLUE}Device{CLEAR}'.format(**COLORS))
report.append(' {}'.format(self.name)) report.append(' {}'.format(self.name))
# Include RAM details
ram_details = get_ram_details()
ram_total = human_readable_size(ram_details.pop('Total', 0)).strip()
ram_dimms = ['{}x {}'.format(v, k) for k, v in sorted(ram_details.items())]
report.append('{BLUE}RAM{CLEAR}'.format(**COLORS))
report.append(' {} ({})'.format(ram_total, ', '.join(ram_dimms)))
# Tests # Tests
for test in self.tests.values(): for test in self.tests.values():
report.extend(test.report) report.extend(test.report)
@ -84,7 +92,15 @@ class DiskObj():
self.get_size() self.get_size()
# Try enabling SMART # Try enabling SMART
run_program(['sudo', 'smartctl', '--smart=on', self.path], check=False) run_program(
cmd=[
'sudo',
'smartctl',
'--tolerance=permissive',
'--smart=on',
self.path,
],
check=False)
# Get NVMe/SMART data and set description # Get NVMe/SMART data and set description
self.get_smart_details() self.get_smart_details()
@ -178,8 +194,8 @@ class DiskObj():
disk_ok = False disk_ok = False
# Disable override if necessary # Disable override if necessary
self.override_disabled |= ATTRIBUTES[attr_type][k].get( if ATTRIBUTES[attr_type][k].get('Critical', False):
'Critical', False) self.override_disabled = True
# SMART overall assessment # SMART overall assessment
## NOTE: Only fail drives if the overall value exists and reports failed ## NOTE: Only fail drives if the overall value exists and reports failed
@ -212,11 +228,12 @@ class DiskObj():
# Done # Done
return test_running return test_running
def disable_test(self, name, status): def disable_test(self, name, status, test_failed=False):
"""Disable test by name and update status.""" """Disable test by name and update status."""
if name in self.tests: if name in self.tests:
self.tests[name].update_status(status) self.tests[name].update_status(status)
self.tests[name].disabled = True self.tests[name].disabled = True
self.tests[name].failed = test_failed
def generate_attribute_report( def generate_attribute_report(
self, description=False, timestamp=False): self, description=False, timestamp=False):
@ -299,6 +316,11 @@ class DiskObj():
attr_type=self.attr_type, **COLORS)) attr_type=self.attr_type, **COLORS))
report.extend(sorted(self.nvme_smart_notes.keys())) report.extend(sorted(self.nvme_smart_notes.keys()))
# 4K alignment check
if not self.is_4k_aligned():
report.append('{YELLOW}Warning{CLEAR}'.format(**COLORS))
report.append(' One or more partitions are not 4K aligned')
# Tests # Tests
for test in self.tests.values(): for test in self.tests.values():
report.extend(test.report) report.extend(test.report)
@ -349,8 +371,15 @@ class DiskObj():
def get_smart_details(self): def get_smart_details(self):
"""Get data from smartctl.""" """Get data from smartctl."""
cmd = ['sudo', 'smartctl', '--all', '--json', self.path] cmd = [
self.smartctl = get_json_from_command(cmd) 'sudo',
'smartctl',
'--tolerance=verypermissive',
'--all',
'--json',
self.path,
]
self.smartctl = get_json_from_command(cmd, check=False)
# Check for attributes # Check for attributes
if KEY_NVME in self.smartctl: if KEY_NVME in self.smartctl:
@ -395,6 +424,26 @@ class DiskObj():
'self_test', {}).get( 'self_test', {}).get(
k, {}) k, {})
def is_4k_aligned(self):
"""Check if partitions are 4K aligned, returns bool."""
cmd = [
'sudo',
'sfdisk',
'--json',
self.path,
]
aligned = True
# Get partition details
json_data = get_json_from_command(cmd)
# Check partitions
for part in json_data.get('partitiontable', {}).get('partitions', []):
aligned = aligned and part.get('start', -1) % 4096 == 0
# Done
return aligned
def safety_check(self, silent=False): def safety_check(self, silent=False):
"""Run safety checks and disable tests if necessary.""" """Run safety checks and disable tests if necessary."""
test_running = False test_running = False
@ -439,7 +488,6 @@ class DiskObj():
disk_ok = OVERRIDES_FORCED or ask('Run tests on this device anyway?') disk_ok = OVERRIDES_FORCED or ask('Run tests on this device anyway?')
print_standard(' ') print_standard(' ')
# Disable tests if necessary (statuses won't be overwritten) # Disable tests if necessary (statuses won't be overwritten)
if test_running: if test_running:
if not silent: if not silent:
@ -448,7 +496,7 @@ class DiskObj():
for t in ['badblocks', 'I/O Benchmark']: for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied') self.disable_test(t, 'Denied')
elif not disk_ok: elif not disk_ok:
self.disable_test('NVMe / SMART', 'NS') self.disable_test('NVMe / SMART', 'NS', test_failed=True)
for t in ['badblocks', 'I/O Benchmark']: for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied') self.disable_test(t, 'Denied')
@ -456,6 +504,7 @@ class DiskObj():
class State(): class State():
"""Object to track device objects and overall state.""" """Object to track device objects and overall state."""
def __init__(self): def __init__(self):
self.args = None
self.cpu = None self.cpu = None
self.disks = [] self.disks = []
self.panes = {} self.panes = {}
@ -483,6 +532,83 @@ class State():
}, },
}) })
def build_outer_panes(self):
"""Build top and side panes."""
clear_screen()
# Top
self.panes['Top'] = tmux_split_window(
behind=True, lines=2, vertical=True,
text=TOP_PANE_TEXT)
# Started
self.panes['Started'] = tmux_split_window(
lines=SIDE_PANE_WIDTH, target_pane=self.panes['Top'],
text='{BLUE}Started{CLEAR}\n{s}'.format(
s=time.strftime("%Y-%m-%d %H:%M %Z"),
**COLORS))
# Progress
self.panes['Progress'] = tmux_split_window(
lines=SIDE_PANE_WIDTH,
watch=self.progress_out)
def fix_tmux_panes(self):
"""Fix pane sizes if the window has been resized."""
needs_fixed = False
# Bail?
if not self.panes:
return
# Check layout
for k, v in self.tmux_layout.items():
if not v.get('Check'):
# Not concerned with the size of this pane
continue
# Get target
target = None
if k != 'Current':
if k not in self.panes:
# Skip missing panes
continue
else:
target = self.panes[k]
# Check pane size
x, y = tmux_get_pane_size(pane_id=target)
if v.get('x', False) and v['x'] != x:
needs_fixed = True
if v.get('y', False) and v['y'] != y:
needs_fixed = True
# Bail?
if not needs_fixed:
return
# Update layout
for k, v in self.tmux_layout.items():
# Get target
target = None
if k != 'Current':
if k not in self.panes:
# Skip missing panes
continue
else:
target = self.panes[k]
# Resize pane
tmux_resize_pane(pane_id=target, **v)
def fix_tmux_panes_loop(self):
while True:
try:
self.fix_tmux_panes()
sleep(1)
except RuntimeError:
# Assuming layout definitions changes mid-run, ignoring
pass
def init(self): def init(self):
"""Remove test objects, set log, and add devices.""" """Remove test objects, set log, and add devices."""
self.disks = [] self.disks = []
@ -490,14 +616,18 @@ class State():
v['Objects'] = [] v['Objects'] = []
# Update LogDir # Update LogDir
if not self.quick_mode: if self.quick_mode:
global_vars['LogDir'] = '{}/Logs/{}'.format(
global_vars['Env']['HOME'],
time.strftime('%Y-%m-%d_%H%M_%z'))
else:
global_vars['LogDir'] = '{}/Logs/{}_{}'.format( global_vars['LogDir'] = '{}/Logs/{}_{}'.format(
global_vars['Env']['HOME'], global_vars['Env']['HOME'],
get_ticket_number(), get_ticket_number(),
time.strftime('%Y-%m-%d_%H%M_%z')) time.strftime('%Y-%m-%d_%H%M_%z'))
os.makedirs(global_vars['LogDir'], exist_ok=True) os.makedirs(global_vars['LogDir'], exist_ok=True)
global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format( global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
global_vars['LogDir']) global_vars['LogDir'])
self.progress_out = '{}/progress.out'.format(global_vars['LogDir']) self.progress_out = '{}/progress.out'.format(global_vars['LogDir'])
# Add CPU # Add CPU
@ -526,7 +656,13 @@ class State():
# Start tmux thread # Start tmux thread
self.tmux_layout = TMUX_LAYOUT.copy() self.tmux_layout = TMUX_LAYOUT.copy()
start_thread(fix_tmux_panes_loop, args=[self]) start_thread(self.fix_tmux_panes_loop)
def set_top_pane_text(self, text):
"""Set top pane text using TOP_PANE_TEXT and provided text."""
tmux_update_pane(
self.panes['Top'],
text='{}\n{}'.format(TOP_PANE_TEXT, text))
class TestObj(): class TestObj():
@ -561,28 +697,6 @@ class TestObj():
# Functions # Functions
def build_outer_panes(state):
"""Build top and side panes."""
clear_screen()
# Top
state.panes['Top'] = tmux_split_window(
behind=True, lines=2, vertical=True,
text=TOP_PANE_TEXT)
# Started
state.panes['Started'] = tmux_split_window(
lines=SIDE_PANE_WIDTH, target_pane=state.panes['Top'],
text='{BLUE}Started{CLEAR}\n{s}'.format(
s=time.strftime("%Y-%m-%d %H:%M %Z"),
**COLORS))
# Progress
state.panes['Progress'] = tmux_split_window(
lines=SIDE_PANE_WIDTH,
watch=state.progress_out)
def build_status_string(label, status, info_label=False): def build_status_string(label, status, info_label=False):
"""Build status string with appropriate colors.""" """Build status string with appropriate colors."""
status_color = COLORS['CLEAR'] status_color = COLORS['CLEAR']
@ -599,64 +713,6 @@ def build_status_string(label, status, info_label=False):
**COLORS) **COLORS)
def fix_tmux_panes_loop(state):
while True:
try:
fix_tmux_panes(state)
sleep(1)
except RuntimeError:
# Assuming layout definitions changes mid-run, ignoring
pass
def fix_tmux_panes(state):
"""Fix pane sizes if the window has been resized."""
needs_fixed = False
# Bail?
if not state.panes:
return
# Check layout
for k, v in state.tmux_layout.items():
if not v.get('Check'):
# Not concerned with the size of this pane
continue
# Get target
target = None
if k != 'Current':
if k not in state.panes:
# Skip missing panes
continue
else:
target = state.panes[k]
# Check pane size
x, y = tmux_get_pane_size(pane_id=target)
if v.get('x', False) and v['x'] != x:
needs_fixed = True
if v.get('y', False) and v['y'] != y:
needs_fixed = True
# Bail?
if not needs_fixed:
return
# Update layout
for k, v in state.tmux_layout.items():
# Get target
target = None
if k != 'Current':
if k not in state.panes:
# Skip missing panes
continue
else:
target = state.panes[k]
# Resize pane
tmux_resize_pane(pane_id=target, **v)
def generate_horizontal_graph(rates, oneline=False): def generate_horizontal_graph(rates, oneline=False):
"""Generate horizontal graph from rates, returns list.""" """Generate horizontal graph from rates, returns list."""
graph = ['', '', '', ''] graph = ['', '', '', '']
@ -716,6 +772,44 @@ def get_graph_step(rate, scale=16):
return step return step
def get_ram_details():
"""Get RAM details via dmidecode, returns dict."""
cmd = ['sudo', 'dmidecode', '--type', 'memory']
manufacturer = 'UNKNOWN'
ram_details = {'Total': 0}
size = 0
# Get DMI data
result = run_program(cmd, encoding='utf-8', errors='ignore')
dmi_data = result.stdout.splitlines()
# Parse data
for line in dmi_data:
line = line.strip()
if line == 'Memory Device':
# Reset vars
manufacturer = 'UNKNOWN'
size = 0
elif line.startswith('Size:'):
size = convert_to_bytes(line.replace('Size: ', ''))
elif line.startswith('Manufacturer:'):
manufacturer = line.replace('Manufacturer: ', '')
if size > 0:
# Add RAM to list if slot populated
ram_str = '{} {}'.format(
human_readable_size(size).strip(),
manufacturer,
)
ram_details['Total'] += size
if ram_str in ram_details:
ram_details[ram_str] += 1
else:
ram_details[ram_str] = 1
# Done
return ram_details
def get_read_rate(s): def get_read_rate(s):
"""Get read rate in bytes/s from dd progress output.""" """Get read rate in bytes/s from dd progress output."""
real_rate = None real_rate = None
@ -728,6 +822,7 @@ def get_read_rate(s):
def menu_diags(state, args): def menu_diags(state, args):
"""Main menu to select and run HW tests.""" """Main menu to select and run HW tests."""
args = [a.lower() for a in args] args = [a.lower() for a in args]
state.args = args
checkmark = '*' checkmark = '*'
if 'DISPLAY' in global_vars['Env']: if 'DISPLAY' in global_vars['Env']:
checkmark = '' checkmark = ''
@ -773,7 +868,7 @@ def menu_diags(state, args):
# If so, verify no other tests are enabled and set quick_mode # If so, verify no other tests are enabled and set quick_mode
state.quick_mode = True state.quick_mode = True
for opt in main_options[3:4] + main_options[5:]: for opt in main_options[3:4] + main_options[5:]:
state.quick_mode &= not opt['Enabled'] state.quick_mode = state.quick_mode and not opt['Enabled']
else: else:
state.quick_mode = False state.quick_mode = False
@ -869,10 +964,7 @@ def run_badblocks_test(state, test):
update_progress_pane(state) update_progress_pane(state)
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.description)
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
# Create monitor pane # Create monitor pane
test.badblocks_out = '{}/badblocks_{}.out'.format( test.badblocks_out = '{}/badblocks_{}.out'.format(
@ -955,10 +1047,11 @@ def run_hw_tests(state):
"""Run enabled hardware tests.""" """Run enabled hardware tests."""
print_standard('Scanning devices...') print_standard('Scanning devices...')
state.init() state.init()
tests_enabled = False
# Build Panes # Build Panes
update_progress_pane(state) update_progress_pane(state)
build_outer_panes(state) state.build_outer_panes()
# Show selected tests and create TestObj()s # Show selected tests and create TestObj()s
print_info('Selected Tests:') print_info('Selected Tests:')
@ -970,6 +1063,8 @@ def run_hw_tests(state):
COLORS['CLEAR'], COLORS['CLEAR'],
QUICK_LABEL if state.quick_mode and 'NVMe' in k else '')) QUICK_LABEL if state.quick_mode and 'NVMe' in k else ''))
if v['Enabled']: if v['Enabled']:
tests_enabled = True
# Create TestObj and track under both CpuObj/DiskObj and State # Create TestObj and track under both CpuObj/DiskObj and State
if k in TESTS_CPU: if k in TESTS_CPU:
test_obj = TestObj( test_obj = TestObj(
@ -983,10 +1078,16 @@ def run_hw_tests(state):
v['Objects'].append(test_obj) v['Objects'].append(test_obj)
print_standard('') print_standard('')
# Bail if no tests selected
if not tests_enabled:
tmux_kill_pane(*state.panes.values())
return
# Run disk safety checks (if necessary) # Run disk safety checks (if necessary)
_disk_tests_enabled = False _disk_tests_enabled = False
for k in TESTS_DISK: for k in TESTS_DISK:
_disk_tests_enabled |= state.tests[k]['Enabled'] if state.tests[k]['Enabled']:
_disk_tests_enabled = True
if _disk_tests_enabled: if _disk_tests_enabled:
for disk in state.disks: for disk in state.disks:
try: try:
@ -1024,7 +1125,7 @@ def run_hw_tests(state):
# Rebuild panes # Rebuild panes
update_progress_pane(state) update_progress_pane(state)
build_outer_panes(state) state.build_outer_panes()
# Mark unfinished tests as aborted # Mark unfinished tests as aborted
for k, v in state.tests.items(): for k, v in state.tests.items():
@ -1036,8 +1137,22 @@ def run_hw_tests(state):
# Update side pane # Update side pane
update_progress_pane(state) update_progress_pane(state)
# Done # Show results
show_results(state) show_results(state)
# Upload for review
if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
try_and_print(
message='Saving debug reports...',
function=save_debug_reports,
state=state, global_vars=global_vars)
try_and_print(
message='Uploading Data...',
function=upload_logdir,
global_vars=global_vars,
reason='Review')
# Done
sleep(1) sleep(1)
if state.quick_mode: if state.quick_mode:
pause('Press Enter to exit... ') pause('Press Enter to exit... ')
@ -1064,10 +1179,7 @@ def run_io_benchmark(state, test):
update_progress_pane(state) update_progress_pane(state)
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.description)
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
state.tmux_layout['Current'] = {'y': 15, 'Check': True} state.tmux_layout['Current'] = {'y': 15, 'Check': True}
# Create monitor pane # Create monitor pane
@ -1226,9 +1338,7 @@ def run_mprime_test(state, test):
test.thermal_abort = False test.thermal_abort = False
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.name)
state.panes['Top'],
text='{}\n{}'.format(TOP_PANE_TEXT, dev.name))
# Start live sensor monitor # Start live sensor monitor
test.sensors_out = '{}/sensors.out'.format(global_vars['TmpDir']) test.sensors_out = '{}/sensors.out'.format(global_vars['TmpDir'])
@ -1391,7 +1501,7 @@ def run_mprime_test(state, test):
# Add temps to report # Add temps to report
test.report.append('{BLUE}Temps{CLEAR}'.format(**COLORS)) test.report.append('{BLUE}Temps{CLEAR}'.format(**COLORS))
for line in generate_sensor_report( for line in generate_sensor_report(
test.sensor_data, 'Idle', 'Max', 'Cooldown', core_only=True): test.sensor_data, 'Idle', 'Max', 'Cooldown', cpu_only=True):
test.report.append(' {}'.format(line)) test.report.append(' {}'.format(line))
# Add abort message(s) # Add abort message(s)
@ -1441,10 +1551,7 @@ def run_nvme_smart_tests(state, test, update_mode=False):
update_progress_pane(state) update_progress_pane(state)
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.description)
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
# SMART short self-test # SMART short self-test
if dev.smart_attributes and not (state.quick_mode or update_mode): if dev.smart_attributes and not (state.quick_mode or update_mode):
@ -1514,7 +1621,13 @@ def run_smart_short_test(state, test):
# Start short test # Start short test
print_standard('Running self-test...') print_standard('Running self-test...')
cmd = ['sudo', 'smartctl', '--test=short', dev.path] cmd = [
'sudo',
'smartctl',
'--tolerance=normal',
'--test=short',
dev.path,
]
run_program(cmd, check=False) run_program(cmd, check=False)
# Monitor progress # Monitor progress
@ -1583,14 +1696,13 @@ def show_report(report, log_report=False):
def show_results(state): def show_results(state):
"""Show results for all tests.""" """Show results for all tests."""
clear_screen() clear_screen()
tmux_update_pane( state.set_top_pane_text('Results')
state.panes['Top'],
text='{}\nResults'.format(TOP_PANE_TEXT))
# CPU tests # CPU tests
_enabled = False _enabled = False
for k in TESTS_CPU: for k in TESTS_CPU:
_enabled |= state.tests[k]['Enabled'] if state.tests[k]['Enabled']:
_enabled = True
if _enabled: if _enabled:
print_success('CPU:'.format(k)) print_success('CPU:'.format(k))
show_report(state.cpu.generate_cpu_report(), log_report=True) show_report(state.cpu.generate_cpu_report(), log_report=True)
@ -1599,7 +1711,8 @@ def show_results(state):
# Disk tests # Disk tests
_enabled = False _enabled = False
for k in TESTS_DISK: for k in TESTS_DISK:
_enabled |= state.tests[k]['Enabled'] if state.tests[k]['Enabled']:
_enabled = True
if _enabled: if _enabled:
print_success('Disk{}:'.format( print_success('Disk{}:'.format(
'' if len(state.disks) == 1 else 's')) '' if len(state.disks) == 1 else 's'))

View file

@ -95,7 +95,7 @@ def get_installed_antivirus():
out = out.stdout.decode().strip() out = out.stdout.decode().strip()
state = out.split('=')[1] state = out.split('=')[1]
state = hex(int(state)) state = hex(int(state))
if str(state)[3:5] != '10': if str(state)[3:5] not in ['10', '11']:
programs.append('[Disabled] {}'.format(prod)) programs.append('[Disabled] {}'.format(prod))
else: else:
programs.append(prod) programs.append(prod)
@ -446,16 +446,19 @@ def show_os_name():
def show_temp_files_size(): def show_temp_files_size():
"""Show total size of temp files identified by BleachBit.""" """Show total size of temp files identified by BleachBit."""
size = None size_str = None
total = 0
with open(r'{LogDir}\Tools\BleachBit.log'.format(**global_vars), 'r') as f: with open(r'{LogDir}\Tools\BleachBit.log'.format(**global_vars), 'r') as f:
for line in f.readlines(): for line in f.readlines():
if re.search(r'^disk space to be recovered:', line, re.IGNORECASE): if re.search(r'^Disk space (to be |)recovered:', line, re.IGNORECASE):
size = re.sub(r'.*: ', '', line.strip()) size = re.sub(r'.*: ', '', line.strip())
size = re.sub(r'(\w)iB$', r' \1b', size) size = re.sub(r'(\w)iB$', r' \1b', size)
if size is None: total += convert_to_bytes(size)
print_warning(size, timestamp=False) size_str = human_readable_size(total, decimals=1)
if size_str is None:
print_warning('UNKNOWN', timestamp=False)
else: else:
print_standard(size, timestamp=False) print_standard(size_str, timestamp=False)
def show_user_data_summary(indent=8, width=32): def show_user_data_summary(indent=8, width=32):

View file

@ -4,7 +4,7 @@ import json
from functions.common import * from functions.common import *
def get_json_from_command(cmd, ignore_errors=True): def get_json_from_command(cmd, check=True, ignore_errors=True):
"""Capture JSON content from cmd output, returns dict. """Capture JSON content from cmd output, returns dict.
If the data can't be decoded then either an exception is raised If the data can't be decoded then either an exception is raised
@ -17,7 +17,7 @@ def get_json_from_command(cmd, ignore_errors=True):
errors = 'ignore' errors = 'ignore'
try: try:
result = run_program(cmd, encoding='utf-8', errors=errors) result = run_program(cmd, check=check, encoding='utf-8', errors=errors)
json_data = json.loads(result.stdout) json_data = json.loads(result.stdout)
except (subprocess.CalledProcessError, json.decoder.JSONDecodeError): except (subprocess.CalledProcessError, json.decoder.JSONDecodeError):
if not ignore_errors: if not ignore_errors:

View file

@ -15,27 +15,6 @@ REGEX_VALID_IP = re.compile(
re.IGNORECASE) re.IGNORECASE)
def connect_to_network():
"""Connect to network if not already connected."""
net_ifs = psutil.net_if_addrs()
net_ifs = [i[:2] for i in net_ifs.keys()]
# Bail if currently connected
if is_connected():
return
# WiFi
if 'wl' in net_ifs:
cmd = [
'nmcli', 'dev', 'wifi',
'connect', WIFI_SSID,
'password', WIFI_PASSWORD]
try_and_print(
message = 'Connecting to {}...'.format(WIFI_SSID),
function = run_program,
cmd = cmd)
def is_connected(): def is_connected():
"""Check for a valid private IP.""" """Check for a valid private IP."""
devs = psutil.net_if_addrs() devs = psutil.net_if_addrs()

View file

@ -1,4 +1,6 @@
# Wizard Kit: Functions - Sensors '''Wizard Kit: Functions - Sensors'''
# pylint: disable=no-name-in-module,wildcard-import
# vim: sts=2 sw=2 ts=2
import json import json
import re import re
@ -9,7 +11,7 @@ from settings.sensors import *
# Error Classes # Error Classes
class ThermalLimitReachedError(Exception): class ThermalLimitReachedError(Exception):
pass '''Thermal limit reached error.'''
def clear_temps(sensor_data): def clear_temps(sensor_data):
@ -20,28 +22,30 @@ def clear_temps(sensor_data):
_data['Temps'] = [] _data['Temps'] = []
def fix_sensor_str(s): def fix_sensor_str(_s):
"""Cleanup string and return str.""" """Cleanup string and return str."""
s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', s, re.IGNORECASE) _s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', _s, re.IGNORECASE)
s = s.title() _s = _s.title()
s = s.replace('Coretemp', 'CoreTemp') _s = _s.replace('Coretemp', 'CPUTemp')
s = s.replace('Acpi', 'ACPI') _s = _s.replace('Acpi', 'ACPI')
s = s.replace('ACPItz', 'ACPI TZ') _s = _s.replace('ACPItz', 'ACPI TZ')
s = s.replace('Isa ', 'ISA ') _s = _s.replace('Isa ', 'ISA ')
s = s.replace('Id ', 'ID ') _s = _s.replace('Pci ', 'PCI ')
s = re.sub(r'(\D+)(\d+)', r'\1 \2', s, re.IGNORECASE) _s = _s.replace('Id ', 'ID ')
s = s.replace(' ', ' ') _s = re.sub(r'(\D+)(\d+)', r'\1 \2', _s, re.IGNORECASE)
return s _s = re.sub(r'^K (\d+)Temp', r'AMD K\1 Temps', _s, re.IGNORECASE)
_s = re.sub(r'T(ctl|die)', r'CPU (T\1)', _s, re.IGNORECASE)
return _s
def generate_sensor_report( def generate_sensor_report(
sensor_data, *temp_labels, sensor_data, *temp_labels,
colors=True, core_only=False): colors=True, cpu_only=False):
"""Generate report based on temp_labels, returns list if str.""" """Generate report based on temp_labels, returns list if str."""
report = [] report = []
for _section, _adapters in sorted(sensor_data.items()): for _section, _adapters in sorted(sensor_data.items()):
# CoreTemps then Other temps # CPU temps then Other temps
if core_only and 'Core' not in _section: if cpu_only and 'CPU' not in _section:
continue continue
for _adapter, _sources in sorted(_adapters.items()): for _adapter, _sources in sorted(_adapters.items()):
# Adapter # Adapter
@ -56,7 +60,7 @@ def generate_sensor_report(
': ' if _label != 'Current' else '', ': ' if _label != 'Current' else '',
get_temp_str(_data.get(_label, '???'), colors=colors)) get_temp_str(_data.get(_label, '???'), colors=colors))
report.append(_line) report.append(_line)
if not core_only: if not cpu_only:
report.append(' ') report.append(' ')
# Handle empty reports (i.e. no sensors detected) # Handle empty reports (i.e. no sensors detected)
@ -91,17 +95,17 @@ def get_colored_temp_str(temp):
else: else:
color = COLORS['CLEAR'] color = COLORS['CLEAR']
return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format( return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format(
color = color, color=color,
prefix = '-' if temp < 0 else '', prefix='-' if temp < 0 else '',
temp = temp, temp=temp,
**COLORS) **COLORS)
def get_raw_sensor_data(): def get_raw_sensor_data():
"""Read sensor data and return dict.""" """Read sensor data and return dict."""
data = {} json_data = {}
cmd = ['sensors', '-j'] cmd = ['sensors', '-j']
# Get raw data # Get raw data
try: try:
result = run_program(cmd) result = run_program(cmd)
@ -122,8 +126,8 @@ def get_raw_sensor_data():
try: try:
json_data = json.loads('\n'.join(raw_data)) json_data = json.loads('\n'.join(raw_data))
except json.JSONDecodeError: except json.JSONDecodeError:
# Still broken, just set to empty dict # Still broken, just return the empty dict
json_data = {} pass
# Done # Done
return json_data return json_data
@ -132,10 +136,10 @@ def get_raw_sensor_data():
def get_sensor_data(): def get_sensor_data():
"""Parse raw sensor data and return new dict.""" """Parse raw sensor data and return new dict."""
json_data = get_raw_sensor_data() json_data = get_raw_sensor_data()
sensor_data = {'CoreTemps': {}, 'Other': {}} sensor_data = {'CPUTemps': {}, 'Other': {}}
for _adapter, _sources in json_data.items(): for _adapter, _sources in json_data.items():
if 'coretemp' in _adapter: if is_cpu_adapter(_adapter):
_section = 'CoreTemps' _section = 'CPUTemps'
else: else:
_section = 'Other' _section = 'Other'
sensor_data[_section][_adapter] = {} sensor_data[_section][_adapter] = {}
@ -157,8 +161,8 @@ def get_sensor_data():
} }
# Remove empty sections # Remove empty sections
for k, v in sensor_data.items(): for _k, _v in sensor_data.items():
v = {k2: v2 for k2, v2 in v.items() if v2} _v = {_k2: _v2 for _k2, _v2 in _v.items() if _v2}
# Done # Done
return sensor_data return sensor_data
@ -178,14 +182,20 @@ def get_temp_str(temp, colors=True):
temp) temp)
def is_cpu_adapter(adapter):
"""Checks if adapter is a known CPU adapter, returns bool."""
is_cpu = re.search(r'(core|k\d+)temp', adapter, re.IGNORECASE)
return bool(is_cpu)
def monitor_sensors(monitor_pane, monitor_file): def monitor_sensors(monitor_pane, monitor_file):
"""Continually update sensor data and report to screen.""" """Continually update sensor data and report to screen."""
sensor_data = get_sensor_data() sensor_data = get_sensor_data()
while True: while True:
update_sensor_data(sensor_data) update_sensor_data(sensor_data)
with open(monitor_file, 'w') as f: with open(monitor_file, 'w') as _f:
report = generate_sensor_report(sensor_data, 'Current', 'Max') report = generate_sensor_report(sensor_data, 'Current', 'Max')
f.write('\n'.join(report)) _f.write('\n'.join(report))
sleep(1) sleep(1)
if monitor_pane and not tmux_poll_pane(monitor_pane): if monitor_pane and not tmux_poll_pane(monitor_pane):
break break
@ -196,7 +206,7 @@ def save_average_temp(sensor_data, temp_label, seconds=10):
clear_temps(sensor_data) clear_temps(sensor_data)
# Get temps # Get temps
for i in range(seconds): for _i in range(seconds): # pylint: disable=unused-variable
update_sensor_data(sensor_data) update_sensor_data(sensor_data)
sleep(1) sleep(1)
@ -219,24 +229,15 @@ def update_sensor_data(sensor_data, thermal_limit=None):
_data['Current'] = _temp _data['Current'] = _temp
_data['Max'] = max(_temp, _data['Max']) _data['Max'] = max(_temp, _data['Max'])
_data['Temps'].append(_temp) _data['Temps'].append(_temp)
except Exception: except Exception: # pylint: disable=broad-except
# Dumb workound for Dell sensors with changing source names # Dumb workound for Dell sensors with changing source names
pass pass
# Check if thermal limit reached # Check if thermal limit reached
if thermal_limit and _section == 'CoreTemps': if thermal_limit and _section == 'CPUTemps':
if max(_data['Current'], _data['Max']) >= thermal_limit: if max(_data['Current'], _data['Max']) >= thermal_limit:
raise ThermalLimitReachedError('CoreTemps reached limit') raise ThermalLimitReachedError('CPU temps reached limit')
def join_columns(column1, column2, width=55):
return '{:<{}}{}'.format(
column1,
55+len(column1)-len(REGEX_COLORS.sub('', column1)),
column2)
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -1,7 +1,10 @@
# Wizard Kit: Functions - Setup # Wizard Kit: Functions - Setup
from functions.browsers import *
from functions.json import *
from functions.update import * from functions.update import *
from settings.setup import * from settings.setup import *
from settings.sources import *
# Configuration # Configuration
@ -63,9 +66,13 @@ def config_explorer_system():
write_registry_settings(SETTINGS_EXPLORER_SYSTEM, all_users=True) write_registry_settings(SETTINGS_EXPLORER_SYSTEM, all_users=True)
def config_explorer_user(): def config_explorer_user(setup_mode='All'):
"""Configure Windows Explorer for current user.""" """Configure Windows Explorer for current user per setup_mode."""
write_registry_settings(SETTINGS_EXPLORER_USER, all_users=False) settings_explorer_user = {
k: v for k, v in SETTINGS_EXPLORER_USER.items()
if setup_mode not in v.get('Invalid modes', [])
}
write_registry_settings(settings_explorer_user, all_users=False)
def config_windows_updates(): def config_windows_updates():
@ -73,19 +80,9 @@ def config_windows_updates():
write_registry_settings(SETTINGS_WINDOWS_UPDATES, all_users=True) write_registry_settings(SETTINGS_WINDOWS_UPDATES, all_users=True)
def disable_windows_telemetry():
"""Disable Windows 10 telemetry settings with O&O ShutUp10."""
extract_item('ShutUp10', silent=True)
cmd = [
r'{BinDir}\ShutUp10\OOSU10.exe'.format(**global_vars),
r'{BinDir}\ShutUp10\1201.cfg'.format(**global_vars),
'/quiet']
run_program(cmd)
def update_clock(): def update_clock():
"""Set Timezone and sync clock.""" """Set Timezone and sync clock."""
run_program(['tzutil' ,'/s', WINDOWS_TIME_ZONE], check=False) run_program(['tzutil', '/s', WINDOWS_TIME_ZONE], check=False)
run_program(['net', 'stop', 'w32ime'], check=False) run_program(['net', 'stop', 'w32ime'], check=False)
run_program( run_program(
['w32tm', '/config', '/syncfromflags:manual', ['w32tm', '/config', '/syncfromflags:manual',
@ -117,6 +114,39 @@ def write_registry_settings(settings, all_users=False):
# Installations # Installations
def find_current_software():
"""Find currently installed software, returns list."""
ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
installers = []
# Browsers
scan_for_browsers(silent=True)
for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
if is_installed(browser):
installers.append(
r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
# TODO: Add more sections
return installers
def find_missing_software():
"""Find missing software based on dirs/files present, returns list."""
ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
installers = []
# Browsers
scan_for_browsers(silent=True)
for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
if profile_present(browser):
installers.append(
r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
# TODO: Add more sections
return installers
def install_adobe_reader(): def install_adobe_reader():
"""Install Adobe Reader.""" """Install Adobe Reader."""
cmd = [ cmd = [
@ -169,29 +199,115 @@ def install_firefox_extensions():
run_program(cmd) run_program(cmd)
def install_ninite_bundle(mse=False, libreoffice=False): def install_libreoffice(
quickstart=True, register_mso_types=True,
use_mso_formats=False, vcredist=False):
"""Install LibreOffice using specified settings."""
cmd = [
'msiexec', '/passive', '/norestart',
'/i', r'{}\Installers\Extras\Office\LibreOffice.msi'.format(
global_vars['BaseDir']),
'REBOOTYESNO=No',
'ISCHECKFORPRODUCTUPDATES=0',
'QUICKSTART={}'.format(1 if quickstart else 0),
'UI_LANGS=en_US',
'VC_REDIST={}'.format(1 if vcredist else 0),
]
if register_mso_types:
cmd.append('REGISTER_ALL_MSO_TYPES=1')
else:
cmd.append('REGISTER_NO_MSO_TYPES=1')
xcu_dir = r'{APPDATA}\LibreOffice\4\user'.format(**global_vars['Env'])
xcu_file = r'{}\registrymodifications.xcu'.format(xcu_dir)
# Set default save format
if use_mso_formats and not os.path.exists(xcu_file):
os.makedirs(xcu_dir, exist_ok=True)
with open(xcu_file, 'w', encoding='utf-8', newline='\n') as f:
f.write(LIBREOFFICE_XCU_DATA)
# Install LibreOffice
run_program(cmd, check=True)
def install_ninite_bundle(
# pylint: disable=too-many-arguments,too-many-branches
base=True,
browsers_only=False,
libreoffice=False,
missing=False,
mse=False,
standard=True,
):
"""Run Ninite installer(s), returns list of Popen objects.""" """Run Ninite installer(s), returns list of Popen objects."""
popen_objects = [] popen_objects = []
if global_vars['OS']['Version'] in ('8', '8.1', '10'): if browsers_only:
# Modern selection # This option is deprecated
popen_objects.append( installer_path = r'{BaseDir}\Installers\Extras\Web Browsers'.format(
popen_program(r'{BaseDir}\Installers\Extras\Bundles\Modern.exe'.format( **global_vars)
**global_vars))) scan_for_browsers(silent=True)
else: for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
# Legacy selection if is_installed(browser):
if mse: cmd = r'{}\{}.exe'.format(installer_path, browser)
cmd = r'{BaseDir}\Installers\Extras\Security'.format(**global_vars) popen_objects.append(popen_program(cmd))
cmd += r'\Microsoft Security Essentials.exe'
popen_objects.append(popen_program(cmd)) # Bail
popen_objects.append( return popen_objects
popen_program(r'{BaseDir}\Installers\Extras\Bundles\Legacy.exe'.format(
**global_vars))) # Main selections
main_selections = []
if base:
main_selections.append('base')
if standard:
if global_vars['OS']['Version'] in ('8', '8.1', '10'):
main_selections.append('standard')
else:
main_selections.append('standard7')
if main_selections:
# Only run if base and/or standard are enabled
cmd = r'{}\Installers\Extras\Bundles\{}.exe'.format(
global_vars['BaseDir'],
'-'.join(main_selections),
)
popen_objects.append(popen_program([cmd]))
# Extra selections
extra_selections = {}
for cmd in find_current_software():
extra_selections[cmd] = True
if missing:
for cmd in find_missing_software():
extra_selections[cmd] = True
# Remove overlapping selections
regex = []
for n_name, n_group in NINITE_REGEX.items():
if n_name in main_selections:
regex.extend(n_group)
regex = '({})'.format('|'.join(regex))
extra_selections = {
cmd: True for cmd in extra_selections
if not re.search(regex, cmd, re.IGNORECASE)
}
# Start extra selections
for cmd in extra_selections:
popen_objects.append(popen_program([cmd]))
# Microsoft Security Essentials
if mse:
cmd = r'{}\Installers\Extras\Security\{}'.format(
global_vars['BaseDir'],
'Microsoft Security Essentials.exe',
)
popen_objects.append(popen_program([cmd]))
# LibreOffice # LibreOffice
if libreoffice: if libreoffice:
cmd = r'{BaseDir}\Installers\Extras\Office'.format(**global_vars) cmd = r'{}\Installers\Extras\Office\{}'.format(
cmd += r'\LibreOffice.exe' global_vars['BaseDir'],
popen_objects.append(popen_program(cmd)) 'LibreOffice.exe',
)
popen_objects.append(popen_program([cmd]))
# Done # Done
return popen_objects return popen_objects
@ -218,6 +334,10 @@ def open_device_manager():
popen_program(['mmc', 'devmgmt.msc']) popen_program(['mmc', 'devmgmt.msc'])
def open_speedtest():
popen_program(['start', '', 'https://fast.com'], shell=True)
def open_windows_activation(): def open_windows_activation():
popen_program(['slui']) popen_program(['slui'])

View file

@ -6,6 +6,35 @@ from functions.common import *
from settings.sw_diags import * from settings.sw_diags import *
def check_4k_alignment(show_alert=False):
"""Check that all partitions are 4K aligned."""
aligned = True
cmd = ['WMIC', 'partition', 'get', 'StartingOffset']
offsets = []
# Get offsets
result = run_program(cmd, encoding='utf-8', errors='ignore', check=False)
offsets = result.stdout.splitlines()
# Check offsets
for off in offsets:
off = off.strip()
if not off.isnumeric():
# Skip
continue
try:
aligned = aligned and int(off) % 4096 == 0
except ValueError:
# Ignore, this check is low priority
pass
# Show alert
if show_alert:
show_alert_box('One or more partitions are not 4K aligned')
raise Not4KAlignedError
def check_connection(): def check_connection():
"""Check if the system is online and optionally abort the script.""" """Check if the system is online and optionally abort the script."""
while True: while True:
@ -19,6 +48,37 @@ def check_connection():
abort() abort()
def check_os_support_status():
"""Check if current OS is supported."""
msg = ''
outdated = False
unsupported = False
# Check OS version/notes
os_info = global_vars['OS'].copy()
if os_info['Notes'] == 'unsupported':
msg = 'The installed version of Windows is no longer supported'
unsupported = True
elif os_info['Notes'] == 'preview build':
msg = 'Preview builds are not officially supported'
unsupported = True
elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
msg = 'The installed version of Windows is outdated'
outdated = True
if 'Preview' not in msg:
msg += '\n\nPlease consider upgrading before continuing setup.'
# Show alert
if outdated or unsupported:
show_alert_box(msg)
# Raise exception if necessary
if outdated:
raise WindowsOutdatedError
if unsupported:
raise WindowsUnsupportedError
def check_secure_boot_status(show_alert=False): def check_secure_boot_status(show_alert=False):
"""Checks UEFI Secure Boot status via PowerShell.""" """Checks UEFI Secure Boot status via PowerShell."""
boot_mode = get_boot_mode() boot_mode = get_boot_mode()
@ -81,33 +141,6 @@ def get_boot_mode():
return type_str return type_str
def os_is_unsupported(show_alert=False):
"""Checks if the current OS is unsupported, returns bool."""
msg = ''
unsupported = False
# Check OS version/notes
os_info = global_vars['OS'].copy()
if os_info['Notes'] == 'unsupported':
msg = 'The installed version of Windows is no longer supported'
unsupported = True
elif os_info['Notes'] == 'preview build':
msg = 'Preview builds are not officially supported'
unsupported = True
elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
msg = 'The installed version of Windows is outdated'
unsupported = True
if 'Preview' not in msg:
msg += '\n\nPlease consider upgrading before continuing setup.'
# Show alert
if unsupported and show_alert:
show_alert_box(msg)
# Done
return unsupported
def run_autoruns(): def run_autoruns():
"""Run AutoRuns in the background with VirusTotal checks enabled.""" """Run AutoRuns in the background with VirusTotal checks enabled."""
extract_item('Autoruns', filter='autoruns*', silent=True) extract_item('Autoruns', filter='autoruns*', silent=True)
@ -197,8 +230,10 @@ def run_rkill():
shutil.move(item.path, dest) shutil.move(item.path, dest)
def show_alert_box(message, title='Wizard Kit Warning'): def show_alert_box(message, title=None):
"""Show Windows alert box with message.""" """Show Windows alert box with message."""
if not title:
title = '{} Warning'.format(KIT_NAME_FULL)
message_box = ctypes.windll.user32.MessageBoxW message_box = ctypes.windll.user32.MessageBoxW
message_box(None, message, title, 0x00001030) message_box(None, message, title, 0x00001030)

View file

@ -0,0 +1,471 @@
"""Wizard Kit: Functions - UFD"""
# pylint: disable=broad-except,wildcard-import
# vim: sts=2 sw=2 ts=2
import os
import re
import shutil
import pathlib
from collections import OrderedDict
from functions.common import *
def case_insensitive_search(path, item):
"""Search path for item case insensitively, returns str."""
regex_match = '^{}$'.format(item)
real_path = ''
# Quick check first
if os.path.exists('{}/{}'.format(path, item)):
real_path = '{}{}{}'.format(
path,
'' if path == '/' else '/',
item,
)
# Check all items in dir
for entry in os.scandir(path):
if re.match(regex_match, entry.name, re.IGNORECASE):
real_path = '{}{}{}'.format(
path,
'' if path == '/' else '/',
entry.name,
)
# Done
if not real_path:
raise FileNotFoundError('{}/{}'.format(path, item))
return real_path
def confirm_selections(args):
"""Ask tech to confirm selections, twice if necessary."""
if not ask('Is the above information correct?'):
abort(False)
## Safety check
if not args['--update']:
print_standard(' ')
print_warning('SAFETY CHECK')
print_standard(
'All data will be DELETED from the disk and partition(s) listed above.')
print_standard(
'This is irreversible and will lead to {RED}DATA LOSS.{CLEAR}'.format(
**COLORS))
if not ask('Asking again to confirm, is this correct?'):
abort(False)
print_standard(' ')
def copy_source(source, items, overwrite=False):
"""Copy source items to /mnt/UFD."""
is_image = source.is_file()
# Mount source if necessary
if is_image:
mount(source, '/mnt/Source')
# Copy items
for i_source, i_dest in items:
i_source = '{}{}'.format(
'/mnt/Source' if is_image else source,
i_source,
)
i_dest = '/mnt/UFD{}'.format(i_dest)
try:
recursive_copy(i_source, i_dest, overwrite=overwrite)
except FileNotFoundError:
# Going to assume (hope) that this is fine
pass
# Unmount source if necessary
if is_image:
unmount('/mnt/Source')
def find_first_partition(dev_path):
"""Find path to first partition of dev, returns str."""
cmd = [
'lsblk',
'--list',
'--noheadings',
'--output', 'name',
'--paths',
dev_path,
]
result = run_program(cmd, encoding='utf-8', errors='ignore')
part_path = result.stdout.splitlines()[-1].strip()
return part_path
def find_path(path):
"""Find path case-insensitively, returns pathlib.Path obj."""
path_obj = pathlib.Path(path).resolve()
# Quick check first
if path_obj.exists():
return path_obj
# Fix case
parts = path_obj.relative_to('/').parts
real_path = '/'
for part in parts:
try:
real_path = case_insensitive_search(real_path, part)
except NotADirectoryError:
# Reclassify error
raise FileNotFoundError(path)
# Raise error if path doesn't exist
path_obj = pathlib.Path(real_path)
if not path_obj.exists():
raise FileNotFoundError(path_obj)
# Done
return path_obj
def get_user_home(user):
"""Get path to user's home dir, returns str."""
home_dir = None
cmd = ['getent', 'passwd', user]
result = run_program(cmd, encoding='utf-8', errors='ignore', check=False)
try:
home_dir = result.stdout.split(':')[5]
except Exception:
# Just use HOME from ENV (or '/root' if that fails)
home_dir = os.environ.get('HOME', '/root')
return home_dir
def get_user_name():
"""Get real user name, returns str."""
user = None
if 'SUDO_USER' in os.environ:
user = os.environ.get('SUDO_USER', 'Unknown')
else:
user = os.environ.get('USER', 'Unknown')
return user
def hide_items(ufd_dev, items):
"""Set FAT32 hidden flag for items."""
# pylint: disable=invalid-name
with open('/root/.mtoolsrc', 'w') as f:
f.write('drive U: file="{}"\n'.format(
find_first_partition(ufd_dev)))
f.write('mtools_skip_check=1\n')
# Hide items
for item in items:
cmd = ['yes | mattrib +h "U:/{}"'.format(item)]
run_program(cmd, check=False, shell=True)
def install_syslinux_to_dev(ufd_dev, use_mbr):
"""Install Syslinux to UFD (dev)."""
cmd = [
'dd',
'bs=440',
'count=1',
'if=/usr/lib/syslinux/bios/{}.bin'.format(
'mbr' if use_mbr else 'gptmbr',
),
'of={}'.format(ufd_dev),
]
run_program(cmd)
def install_syslinux_to_partition(partition):
"""Install Syslinux to UFD (partition)."""
cmd = [
'syslinux',
'--install',
'--directory',
'/arch/boot/syslinux/',
partition,
]
run_program(cmd)
def is_valid_path(path_obj, path_type):
"""Verify path_obj is valid by type, returns bool."""
valid_path = False
if path_type == 'DIR':
valid_path = path_obj.is_dir()
elif path_type == 'KIT':
valid_path = path_obj.is_dir() and path_obj.joinpath('.bin').exists()
elif path_type == 'IMG':
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.img'
elif path_type == 'ISO':
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso'
elif path_type == 'UFD':
valid_path = path_obj.is_block_device()
return valid_path
def mount(mount_source, mount_point, read_write=False):
"""Mount mount_source on mount_point."""
os.makedirs(mount_point, exist_ok=True)
cmd = [
'mount',
mount_source,
mount_point,
'-o',
'rw' if read_write else 'ro',
]
run_program(cmd)
def prep_device(dev_path, label, use_mbr=False, indent=2):
"""Format device in preparation for applying the WizardKit components
This is done is four steps:
1. Zero-out first 64MB (this deletes the partition table and/or bootloader)
2. Create a new partition table (GPT by default, optionally MBR)
3. Set boot flag
4. Format partition (FAT32, 4K aligned)
"""
# Zero-out first 64MB
cmd = 'dd bs=4M count=16 if=/dev/zero of={}'.format(dev_path).split()
try_and_print(
indent=indent,
message='Zeroing first 64MB...',
function=run_program,
cmd=cmd,
)
# Create partition table
cmd = 'parted {} --script -- mklabel {} mkpart primary fat32 4MiB {}'.format(
dev_path,
'msdos' if use_mbr else 'gpt',
'-1s' if use_mbr else '-4MiB',
).split()
try_and_print(
indent=indent,
message='Creating partition table...',
function=run_program,
cmd=cmd,
)
# Set boot flag
cmd = 'parted {} set 1 {} on'.format(
dev_path,
'boot' if use_mbr else 'legacy_boot',
).split()
try_and_print(
indent=indent,
message='Setting boot flag...',
function=run_program,
cmd=cmd,
)
# Format partition
cmd = [
'mkfs.vfat', '-F', '32',
'-n', label,
find_first_partition(dev_path),
]
try_and_print(
indent=indent,
message='Formatting partition...',
function=run_program,
cmd=cmd,
)
def recursive_copy(source, dest, overwrite=False):
"""Copy source to dest recursively.
NOTE: This uses rsync style source/dest syntax.
If the source has a trailing slash then it's contents are copied,
otherwise the source itself is copied.
Examples assuming "ExDir/ExFile.txt" exists:
recursive_copy("ExDir", "Dest/") results in "Dest/ExDir/ExFile.txt"
recursive_copy("ExDir/", "Dest/") results in "Dest/ExFile.txt"
NOTE 2: dest does not use find_path because it might not exist.
"""
copy_contents = source.endswith('/')
source = find_path(source)
dest = pathlib.Path(dest).resolve().joinpath(source.name)
os.makedirs(dest.parent, exist_ok=True)
if source.is_dir():
if copy_contents:
# Trailing slash syntax
for item in os.scandir(source):
recursive_copy(item.path, dest.parent, overwrite=overwrite)
elif not dest.exists():
# No conflict, copying whole tree (no merging needed)
shutil.copytree(source, dest)
elif not dest.is_dir():
# Refusing to replace file with dir
raise FileExistsError('Refusing to replace file: {}'.format(dest))
else:
# Dest exists and is a dir, merge dirs
for item in os.scandir(source):
recursive_copy(item.path, dest, overwrite=overwrite)
elif source.is_file():
if not dest.exists():
# No conflict, copying file
shutil.copy2(source, dest)
elif not dest.is_file():
# Refusing to replace dir with file
raise FileExistsError('Refusing to replace dir: {}'.format(dest))
elif overwrite:
# Dest file exists, deleting and replacing file
os.remove(dest)
shutil.copy2(source, dest)
else:
# Refusing to delete file when overwrite=False
raise FileExistsError('Refusing to delete file: {}'.format(dest))
def remove_arch():
"""Remove arch dir from UFD.
This ensures a clean installation to the UFD and resets the boot files
"""
shutil.rmtree(find_path('/mnt/UFD/arch'))
def running_as_root():
"""Check if running with effective UID of 0, returns bool."""
return os.geteuid() == 0
def show_selections(args, sources, ufd_dev, ufd_sources):
"""Show selections including non-specified options."""
# Sources
print_info('Sources')
for label in ufd_sources.keys():
if label in sources:
print_standard(' {label:<18} {path}'.format(
label=label+':',
path=sources[label],
))
else:
print_standard(' {label:<18} {YELLOW}Not Specified{CLEAR}'.format(
label=label+':',
**COLORS,
))
print_standard(' ')
# Destination
print_info('Destination')
cmd = [
'lsblk', '--nodeps', '--noheadings', '--paths',
'--output', 'NAME,FSTYPE,TRAN,SIZE,VENDOR,MODEL,SERIAL',
ufd_dev,
]
result = run_program(cmd, check=False, encoding='utf-8', errors='ignore')
print_standard(result.stdout.strip())
cmd = [
'lsblk', '--noheadings', '--paths',
'--output', 'NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT',
ufd_dev,
]
result = run_program(cmd, check=False, encoding='utf-8', errors='ignore')
for line in result.stdout.splitlines()[1:]:
print_standard(line)
# Notes
if args['--update']:
print_warning('Updating kit in-place')
elif args['--use-mbr']:
print_warning('Formatting using legacy MBR')
print_standard(' ')
def unmount(mount_point):
"""Unmount mount_point."""
cmd = ['umount', mount_point]
run_program(cmd)
def update_boot_entries(boot_entries, boot_files, iso_label, ufd_label):
"""Update boot files for UFD usage"""
configs = []
# Find config files
for c_path, c_ext in boot_files.items():
c_path = find_path('/mnt/UFD{}'.format(c_path))
for item in os.scandir(c_path):
if item.name.lower().endswith(c_ext.lower()):
configs.append(item.path)
# Update Linux labels
cmd = [
'sed',
'--in-place',
'--regexp-extended',
's/{}/{}/'.format(iso_label, ufd_label),
*configs,
]
run_program(cmd)
# Uncomment extra entries if present
for b_path, b_comment in boot_entries.items():
try:
find_path('/mnt/UFD{}'.format(b_path))
except (FileNotFoundError, NotADirectoryError):
# Entry not found, continue to next entry
continue
# Entry found, update config files
cmd = [
'sed',
'--in-place',
's/#{}#//'.format(b_comment),
*configs,
]
run_program(cmd, check=False)
def verify_sources(args, ufd_sources):
"""Check all sources and abort if necessary, returns dict."""
sources = OrderedDict()
for label, data in ufd_sources.items():
s_path = args[data['Arg']]
if s_path:
try:
s_path_obj = find_path(s_path)
except FileNotFoundError:
print_error('ERROR: {} not found: {}'.format(label, s_path))
abort(False)
if not is_valid_path(s_path_obj, data['Type']):
print_error('ERROR: Invalid {} source: {}'.format(label, s_path))
abort(False)
sources[label] = s_path_obj
return sources
def verify_ufd(dev_path):
"""Check that dev_path is a valid UFD, returns pathlib.Path obj."""
ufd_dev = None
try:
ufd_dev = find_path(dev_path)
except FileNotFoundError:
print_error('ERROR: UFD device not found: {}'.format(dev_path))
abort(False)
if not is_valid_path(ufd_dev, 'UFD'):
print_error('ERROR: Invalid UFD device: {}'.format(ufd_dev))
abort(False)
return ufd_dev
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -615,6 +615,22 @@ def update_adobe_reader_dc():
dest, 'Adobe Reader DC.exe', SOURCE_URLS['Adobe Reader DC']) dest, 'Adobe Reader DC.exe', SOURCE_URLS['Adobe Reader DC'])
def update_libreoffice():
# Prep
dest = r'{}\Installers\Extras\Office'.format(
global_vars['BaseDir'])
# Remove existing installer
try:
os.remove(r'{}\LibreOffice.msi'.format(dest))
except FileNotFoundError:
pass
# Download
download_generic(
dest, 'LibreOffice.msi', SOURCE_URLS['LibreOffice'])
def update_macs_fan_control(): def update_macs_fan_control():
# Prep # Prep
dest = r'{}\Installers'.format( dest = r'{}\Installers'.format(

View file

@ -0,0 +1,143 @@
# Wizard Kit: Functions - Windows updates
from functions.common import *
# Functions
def delete_folder(folder_path):
"""Near-useless wrapper for shutil.rmtree."""
shutil.rmtree(folder_path)
def disable_service(service_name):
"""Set service startup to disabled."""
run_program(['sc', 'config', service_name, 'start=', 'disabled'])
# Verify service was disabled
start_type = get_service_start_type(service_name)
if not start_type.lower().startswith('disabled'):
raise GenericError('Failed to disable service {}'.format(service_name))
def disable_windows_updates():
"""Disable windows updates and clear SoftwareDistribution folder."""
indent=2
width=52
update_folders = [
r'{WINDIR}\SoftwareDistribution'.format(**global_vars['Env']),
r'{SYSTEMDRIVE}\$WINDOWS.~BT'.format(**global_vars['Env']),
]
for service in ('wuauserv', 'bits'):
# Stop service
result = try_and_print(
'Stopping service {}...'.format(service),
indent=indent, width=width,
function=stop_service, service_name=service)
if not result['CS']:
result = try_and_print(
'Stopping service {}...'.format(service),
indent=indent, width=width,
function=stop_service, service_name=service)
if not result['CS']:
raise GenericError('Service {} could not be stopped.'.format(service))
# Disable service
result = try_and_print(
'Disabling service {}...'.format(service),
indent=indent, width=width,
function=disable_service, service_name=service)
if not result['CS']:
result = try_and_print(
'Disabling service {}...'.format(service),
indent=indent, width=width,
function=disable_service, service_name=service)
if not result['CS']:
raise GenericError('Service {} could not be disabled.'.format(service))
# Delete update folders
for folder_path in update_folders:
if os.path.exists(folder_path):
result = try_and_print(
'Deleting folder {}...'.format(folder_path),
indent=indent, width=width,
function=delete_folder, folder_path=folder_path)
if not result['CS']:
raise GenericError('Failed to remove folder {}'.format(folder_path))
def enable_service(service_name, start_type='auto'):
"""Enable service by setting start type."""
run_program(['sc', 'config', service_name, 'start=', start_type])
def enable_windows_updates(silent=False):
"""Enable windows updates"""
indent=2
width=52
for service in ('bits', 'wuauserv'):
# Enable service
start_type = 'auto'
if service == 'wuauserv':
start_type = 'demand'
if silent:
try:
enable_service(service, start_type=start_type)
except Exception:
# Try again
enable_service(service, start_type=start_type)
else:
result = try_and_print(
'Enabling service {}...'.format(service),
indent=indent, width=width,
function=enable_service, service_name=service, start_type=start_type)
if not result['CS']:
result = try_and_print(
'Enabling service {}...'.format(service),
indent=indent, width=width,
function=enable_service, service_name=service, start_type=start_type)
if not result['CS']:
raise GenericError('Service {} could not be enabled.'.format(service))
def get_service_status(service_name):
"""Get service status using psutil, returns str."""
status = 'Unknown'
try:
service = psutil.win_service_get(service_name)
status = service.status()
except psutil.NoSuchProcess:
# Ignore and return 'Unknown' below
pass
return status
def get_service_start_type(service_name):
"""Get service startup type using psutil, returns str."""
start_type = 'Unknown'
try:
service = psutil.win_service_get(service_name)
start_type = service.start_type()
except psutil.NoSuchProcess:
# Ignore and return 'Unknown' below
pass
return start_type
def stop_service(service_name):
"""Stop service."""
run_program(['net', 'stop', service_name], check=False)
# Verify service was stopped
status = get_service_status(service_name)
if not status.lower().startswith('stopped'):
raise GenericError('Failed to stop service {}'.format(service_name))
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -34,8 +34,8 @@ if __name__ == '__main__':
#print_standard('\nDone.') #print_standard('\nDone.')
#pause("Press Enter to exit...") #pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -22,9 +22,8 @@ if __name__ == '__main__':
print_standard(' ') print_standard(' ')
sleep(1) sleep(1)
pause('Press Enter to exit...') pause('Press Enter to exit...')
except SystemExit: except SystemExit as sys_exit:
# Normal exit exit_script(sys_exit.code)
pass
except: except:
# Cleanup # Cleanup
tmux_kill_all_panes() tmux_kill_all_panes()
@ -50,7 +49,7 @@ if __name__ == '__main__':
global_vars=global_vars) global_vars=global_vars)
# Done # Done
sleep(10) sleep(1)
pause('Press Enter to exit...') pause('Press Enter to exit...')
exit_script(1) exit_script(1)

View file

@ -40,8 +40,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
#pause("Press Enter to exit...") #pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -3,9 +3,9 @@
BLUE='\033[34m' BLUE='\033[34m'
CLEAR='\033[0m' CLEAR='\033[0m'
IFS=$'\n'
# List devices # List devices
IFS=$'\n'
for line in $(lsblk -do NAME,TRAN,SIZE,VENDOR,MODEL,SERIAL); do for line in $(lsblk -do NAME,TRAN,SIZE,VENDOR,MODEL,SERIAL); do
if [[ "${line:0:4}" == "NAME" ]]; then if [[ "${line:0:4}" == "NAME" ]]; then
echo -e "${BLUE}${line}${CLEAR}" echo -e "${BLUE}${line}${CLEAR}"
@ -15,6 +15,18 @@ for line in $(lsblk -do NAME,TRAN,SIZE,VENDOR,MODEL,SERIAL); do
done done
echo "" echo ""
# List loopback devices
if [[ "$(losetup -l | wc -l)" > 0 ]]; then
for line in $(losetup -lO NAME,PARTSCAN,RO,BACK-FILE); do
if [[ "${line:0:4}" == "NAME" ]]; then
echo -e "${BLUE}${line}${CLEAR}"
else
echo "${line}" | sed -r 's#/dev/(loop[0-9]+)#\1 #'
fi
done
echo ""
fi
# List partitions # List partitions
for line in $(lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT); do for line in $(lsblk -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT); do
if [[ "${line:0:4}" == "NAME" ]]; then if [[ "${line:0:4}" == "NAME" ]]; then

View file

@ -28,8 +28,8 @@ if __name__ == '__main__':
run_program(cmd, check=False) run_program(cmd, check=False)
monitor_sensors(monitor_pane, monitor_file) monitor_sensors(monitor_pane, monitor_file)
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -25,7 +25,6 @@ if __name__ == '__main__':
'UnsupportedOSError': 'Unsupported OS', 'UnsupportedOSError': 'Unsupported OS',
}} }}
answer_extensions = ask('Install Extensions?') answer_extensions = ask('Install Extensions?')
answer_adobe_reader = ask('Install Adobe Reader?')
answer_vcr = ask('Install Visual C++ Runtimes?') answer_vcr = ask('Install Visual C++ Runtimes?')
answer_ninite = ask('Install Ninite Bundle?') answer_ninite = ask('Install Ninite Bundle?')
if answer_ninite and global_vars['OS']['Version'] in ['7']: if answer_ninite and global_vars['OS']['Version'] in ['7']:
@ -35,9 +34,6 @@ if __name__ == '__main__':
answer_mse = False answer_mse = False
print_info('Installing Programs') print_info('Installing Programs')
if answer_adobe_reader:
try_and_print(message='Adobe Reader DC...',
function=install_adobe_reader, other_results=other_results)
if answer_vcr: if answer_vcr:
install_vcredists() install_vcredists()
if answer_ninite: if answer_ninite:
@ -59,8 +55,8 @@ if __name__ == '__main__':
other_results=other_results) other_results=other_results)
print_standard('\nDone.') print_standard('\nDone.')
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -27,8 +27,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -30,8 +30,8 @@ if __name__ == '__main__':
pause("Press Enter to exit...") pause("Press Enter to exit...")
popen_program(['nohup', 'thunar', '/media'], pipe=True) popen_program(['nohup', 'thunar', '/media'], pipe=True)
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -16,9 +16,6 @@ if __name__ == '__main__':
# Prep # Prep
clear_screen() clear_screen()
# Connect
connect_to_network()
# Mount # Mount
if is_connected(): if is_connected():
mount_backup_shares(read_write=True) mount_backup_shares(read_write=True)
@ -30,8 +27,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
#pause("Press Enter to exit...") #pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -76,8 +76,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
#pause("Press Enter to exit...") #pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -1,163 +0,0 @@
# Wizard Kit: New system setup
import os
import sys
# Init
os.chdir(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(os.getcwd())
from functions.activation import *
from functions.browsers import *
from functions.cleanup import *
from functions.info import *
from functions.product_keys import *
from functions.setup import *
from functions.sw_diags import *
init_global_vars()
os.system('title {}: New System Setup'.format(KIT_NAME_FULL))
set_log_file('New System Setup.log')
if __name__ == '__main__':
other_results = {
'Error': {
'BIOSKeyNotFoundError': 'BIOS key not found',
'CalledProcessError': 'Unknown Error',
'FileNotFoundError': 'File not found',
'GenericError': 'Unknown Error',
'SecureBootDisabledError': 'Disabled',
},
'Warning': {
'GenericRepair': 'Repaired',
'NoProfilesError': 'No profiles found',
'NotInstalledError': 'Not installed',
'OSInstalledLegacyError': 'OS installed Legacy',
'SecureBootNotAvailError': 'Not available',
'SecureBootUnknownError': 'Unknown',
'UnsupportedOSError': 'Unsupported OS',
}}
try:
stay_awake()
clear_screen()
# Check installed OS
if os_is_unsupported(show_alert=False):
print_warning('OS version not supported by this script')
if not ask('Continue anyway? (NOT RECOMMENDED)'):
abort()
# Install Adobe Reader?
answer_adobe_reader = ask('Install Adobe Reader?')
# Install LibreOffice?
answer_libreoffice = ask('Install LibreOffice?')
# Install MSE?
if global_vars['OS']['Version'] == '7':
answer_mse = ask('Install MSE?')
else:
answer_mse = False
# Install software
print_info('Installing Programs')
install_vcredists()
if answer_adobe_reader:
try_and_print(message='Adobe Reader DC...',
function=install_adobe_reader, other_results=other_results)
result = try_and_print(
message='Ninite bundle...',
function=install_ninite_bundle, cs='Started',
mse=answer_mse, libreoffice=answer_libreoffice,
other_results=other_results)
for proc in result['Out']:
# Wait for all processes to finish
proc.wait()
# Scan for supported browsers
print_info('Scanning for browsers')
scan_for_browsers()
# Install extensions
print_info('Installing Extensions')
try_and_print(message='Classic Shell skin...',
function=install_classicstart_skin,
other_results=other_results)
try_and_print(message='Google Chrome extensions...',
function=install_chrome_extensions)
try_and_print(message='Mozilla Firefox extensions...',
function=install_firefox_extensions,
other_results=other_results)
# Configure software
print_info('Configuring programs')
install_adblock()
if global_vars['OS']['Version'] == '10':
try_and_print(message='ClassicStart...',
function=config_classicstart, cs='Done')
try_and_print(message='Explorer (user)...',
function=config_explorer_user, cs='Done')
# Configure system
print_info('Configuring system')
if global_vars['OS']['Version'] == '10':
try_and_print(message='Explorer (system)...',
function=config_explorer_system, cs='Done')
try_and_print(message='Disabling telemetry...',
function=disable_windows_telemetry, cs='Done')
try_and_print(message='Windows Updates...',
function=config_windows_updates, cs='Done')
try_and_print(message='Updating Clock...',
function=update_clock, cs='Done')
# Restart Explorer
try_and_print(message='Restarting Explorer...',
function=restart_explorer, cs='Done')
# Summary
print_info('Summary')
try_and_print(message='Operating System:',
function=show_os_name, ns='Unknown', silent_function=False)
try_and_print(message='Activation:',
function=show_os_activation, ns='Unknown', silent_function=False)
if (not windows_is_activated()
and global_vars['OS']['Version'] in ('8', '8.1', '10')):
try_and_print(message='BIOS Activation:',
function=activate_with_bios,
other_results=other_results)
try_and_print(message='Secure Boot Status:',
function=check_secure_boot_status, other_results=other_results)
try_and_print(message='Installed RAM:',
function=show_installed_ram, ns='Unknown', silent_function=False)
show_free_space()
try_and_print(message='Installed Antivirus:',
function=get_installed_antivirus, ns='Unknown',
other_results=other_results, print_return=True)
# Play audio, show devices, open Windows updates, and open Activation
try_and_print(message='Opening Device Manager...',
function=open_device_manager, cs='Started')
try_and_print(message='Opening HWiNFO (Sensors)...',
function=run_hwinfo_sensors, cs='Started', other_results=other_results)
try_and_print(message='Opening Windows Updates...',
function=open_windows_updates, cs='Started')
if not windows_is_activated():
try_and_print(message='Opening Windows Activation...',
function=open_windows_activation, cs='Started')
sleep(3)
try_and_print(message='Running XMPlay...',
function=run_xmplay, cs='Started', other_results=other_results)
try:
check_secure_boot_status(show_alert=True)
except:
# Only trying to open alert message boxes
pass
# Done
print_standard('\nDone.')
pause('Press Enter to exit...')
exit_script()
except SystemExit:
pass
except:
major_exception()
# vim: sts=2 sw=2 ts=2

View file

@ -31,8 +31,8 @@ if __name__ == '__main__':
pause('Press Enter to reboot...') pause('Press Enter to reboot...')
reboot() reboot()
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -31,8 +31,8 @@ if __name__ == '__main__':
pause('Press Enter to reboot...') pause('Press Enter to reboot...')
reboot() reboot()
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -0,0 +1,37 @@
'''Wizard Kit: Settings - Cleanup'''
# vim: sts=2 sw=2 ts=2
import re
# Regex
DESKTOP_ITEMS = re.compile(
r'^(JRT|RKill|sc-cleaner)',
re.IGNORECASE,
)
# Registry
UAC_DEFAULTS_WIN7 = {
r'Software\Microsoft\Windows\CurrentVersion\Policies\System': {
'DWORD Items': {
'ConsentPromptBehaviorAdmin': 5,
'EnableLUA': 1,
'PromptOnSecureDesktop': 1,
},
},
}
UAC_DEFAULTS_WIN10 = {
r'Software\Microsoft\Windows\CurrentVersion\Policies\System': {
'DWORD Items': {
'ConsentPromptBehaviorAdmin': 5,
'ConsentPromptBehaviorUser': 3,
'EnableInstallerDetection': 1,
'EnableLUA': 1,
'EnableVirtualization': 1,
'PromptOnSecureDesktop': 1,
},
},
}
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -5,7 +5,9 @@ import re
from collections import OrderedDict from collections import OrderedDict
# General # General
MAP_DIR = '/Backups/ddrescue-tui'
RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs'] RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs']
RECOMMENDED_MAP_FSTYPES = ['cifs', 'ext2', 'ext3', 'ext4', 'vfat', 'xfs']
USAGE = """ {script_name} clone [source [destination]] USAGE = """ {script_name} clone [source [destination]]
{script_name} image [source [destination]] {script_name} image [source [destination]]
(e.g. {script_name} clone /dev/sda /dev/sdb) (e.g. {script_name} clone /dev/sda /dev/sdb)
@ -36,6 +38,12 @@ DDRESCUE_SETTINGS = {
'-vvvv': {'Enabled': True, 'Hidden': True, }, '-vvvv': {'Enabled': True, 'Hidden': True, },
} }
ETOC_REFRESH_RATE = 30 # in seconds ETOC_REFRESH_RATE = 30 # in seconds
REGEX_DDRESCUE_LOG = re.compile(
r'^\s*(?P<key>\S+):\s+'
r'(?P<size>\d+)\s+'
r'(?P<unit>[PTGMKB])i?B?',
re.IGNORECASE,
)
REGEX_REMAINING_TIME = re.compile( REGEX_REMAINING_TIME = re.compile(
r'remaining time:' r'remaining time:'
r'\s*((?P<days>\d+)d)?' r'\s*((?P<days>\d+)d)?'

View file

@ -1,35 +1,20 @@
# Wizard Kit: Settings - Launchers '''Wizard Kit: Settings - Launchers'''
# pylint: disable=line-too-long
# vim: sts=2 sw=2 ts=2
LAUNCHERS = { LAUNCHERS = {
r'(Root)': { r'(Root)': {
'Activate Windows': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'activate.py',
'L_ELEV': 'True',
},
'New System Setup': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'new_system_setup.py',
'L_ELEV': 'True',
},
'System Checklist': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'system_checklist.py',
'L_ELEV': 'True',
},
'System Diagnostics': { 'System Diagnostics': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
'L_ITEM': 'system_diagnostics.py', 'L_ITEM': 'system_diagnostics.py',
'L_ELEV': 'True', 'L_ELEV': 'True',
}, },
'User Checklist': { 'System Setup': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
'L_ITEM': 'user_checklist.py', 'L_ITEM': 'system_setup.py',
'L_ELEV': 'True',
}, },
}, },
r'Data Recovery': { r'Data Recovery': {
@ -55,6 +40,7 @@ LAUNCHERS = {
}, },
}, },
r'Data Transfers': { r'Data Transfers': {
# pylint: disable=bad-continuation
'FastCopy (as ADMIN)': { 'FastCopy (as ADMIN)': {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'FastCopy', 'L_PATH': 'FastCopy',
@ -257,7 +243,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'erunt', 'L_PATH': 'erunt',
'L_ITEM': 'ERUNT.EXE', 'L_ITEM': 'ERUNT.EXE',
'L_ARGS': '%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers', 'L_ARGS': r'%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers',
'L_ELEV': 'True', 'L_ELEV': 'True',
'Extra Code': [ 'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs', r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@ -287,13 +273,13 @@ LAUNCHERS = {
r'Drivers': { r'Drivers': {
'Intel RST (Current Release)': { 'Intel RST (Current Release)': {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': '_Drivers\Intel RST', 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': 'SetupRST_17.2.exe', 'L_ITEM': 'SetupRST_17.2.exe',
'L_7ZIP': 'SetupRST_17.2.exe', 'L_7ZIP': 'SetupRST_17.2.exe',
}, },
'Intel RST (Previous Releases)': { 'Intel RST (Previous Releases)': {
'L_TYPE': 'Folder', 'L_TYPE': 'Folder',
'L_PATH': '_Drivers\Intel RST', 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': '.', 'L_ITEM': '.',
'L_NCMD': 'True', 'L_NCMD': 'True',
}, },
@ -309,7 +295,7 @@ LAUNCHERS = {
}, },
'Snappy Driver Installer Origin': { 'Snappy Driver Installer Origin': {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': '_Drivers\SDIO', 'L_PATH': r'_Drivers\SDIO',
'L_ITEM': 'SDIO.exe', 'L_ITEM': 'SDIO.exe',
}, },
}, },
@ -435,6 +421,12 @@ LAUNCHERS = {
}, },
}, },
r'Misc': { r'Misc': {
'Activate Windows': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'activate.py',
'L_ELEV': 'True',
},
'Cleanup CBS Temp Files': { 'Cleanup CBS Temp Files': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
@ -452,6 +444,20 @@ LAUNCHERS = {
'L_PATH': 'ConEmu', 'L_PATH': 'ConEmu',
'L_ITEM': 'ConEmu.exe', 'L_ITEM': 'ConEmu.exe',
}, },
'Disable Windows Updates': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'windows_updates.py',
'L_ARGS': '--disable',
'L_ELEV': 'True',
},
'Enable Windows Updates': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'windows_updates.py',
'L_ARGS': '--enable',
'L_ELEV': 'True',
},
'Enter SafeMode': { 'Enter SafeMode': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
@ -491,7 +497,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'XMPlay', 'L_PATH': 'XMPlay',
'L_ITEM': 'xmplay.exe', 'L_ITEM': 'xmplay.exe',
'L_ARGS': '"%bin%\XMPlay\music.7z"', 'L_ARGS': r'"%bin%\XMPlay\music.7z"',
}, },
}, },
r'Repairs': { r'Repairs': {
@ -551,7 +557,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'RKill', 'L_PATH': 'RKill',
'L_ITEM': 'RKill.exe', 'L_ITEM': 'RKill.exe',
'L_ARGS': '-s -l %log_dir%\Tools\RKill.log', 'L_ARGS': r'-s -l %log_dir%\Tools\RKill.log',
'L_ELEV': 'True', 'L_ELEV': 'True',
'Extra Code': [ 'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs', r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@ -594,5 +600,3 @@ LAUNCHERS = {
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -21,9 +21,6 @@ QUICKBOOKS_SERVER_IP='10.0.0.10'
# Time Zones # Time Zones
LINUX_TIME_ZONE='America/Denver' # See 'timedatectl list-timezones' for valid values LINUX_TIME_ZONE='America/Denver' # See 'timedatectl list-timezones' for valid values
WINDOWS_TIME_ZONE='Mountain Standard Time' # See 'tzutil /l' for valid values WINDOWS_TIME_ZONE='Mountain Standard Time' # See 'tzutil /l' for valid values
# WiFi
WIFI_SSID='SomeWiFi'
WIFI_PASSWORD='Abracadabra'
# SERVER VARIABLES # SERVER VARIABLES
## NOTE: Windows can only use one user per server. This means that if ## NOTE: Windows can only use one user per server. This means that if

View file

@ -1,13 +1,19 @@
# Wizard Kit: Settings - Setup '''Wizard Kit: Settings - Setup'''
# pylint: disable=bad-continuation,line-too-long
# vim: sts=2 sw=2 ts=2
import os import os
import winreg try:
import winreg
HKU = winreg.HKEY_USERS
HKCR = winreg.HKEY_CLASSES_ROOT
HKCU = winreg.HKEY_CURRENT_USER
HKLM = winreg.HKEY_LOCAL_MACHINE
except ImportError:
if os.name != 'posix':
raise
# General # General
HKU = winreg.HKEY_USERS
HKCR = winreg.HKEY_CLASSES_ROOT
HKCU = winreg.HKEY_CURRENT_USER
HKLM = winreg.HKEY_LOCAL_MACHINE
OTHER_RESULTS = { OTHER_RESULTS = {
'Error': { 'Error': {
'CalledProcessError': 'Unknown Error', 'CalledProcessError': 'Unknown Error',
@ -92,6 +98,15 @@ SETTINGS_EXPLORER_SYSTEM = {
}, },
} }
SETTINGS_EXPLORER_USER = { SETTINGS_EXPLORER_USER = {
# Desktop theme
r'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize': {
'Invalid modes': ['Cur'],
'DWORD Items': {
# <= v1809 default
'AppsUseLightTheme': 1,
'SystemUsesLightTheme': 0,
},
},
# Disable features # Disable features
r'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager': { r'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager': {
'DWORD Items': { 'DWORD Items': {
@ -104,21 +119,41 @@ SETTINGS_EXPLORER_USER = {
}, },
# File Explorer # File Explorer
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced': { r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
'Invalid modes': ['Cur'],
'DWORD Items': { 'DWORD Items': {
# Change default Explorer view to "Computer" # Change default Explorer view to "Computer"
'LaunchTo': 1, 'LaunchTo': 1,
}, },
}, },
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
# Dup path so it Will be applied to all modes
'DWORD Items': {
# Launch Folder Windows in a Separate Process
'SeparateProcess': 1,
},
},
# Hide People bar # Hide People bar
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': { r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': {
'Invalid modes': ['Cur'],
'DWORD Items': {'PeopleBand': 0}, 'DWORD Items': {'PeopleBand': 0},
}, },
# Hide Search button / box # Hide Search button / box
r'Software\Microsoft\Windows\CurrentVersion\Search': { r'Software\Microsoft\Windows\CurrentVersion\Search': {
'Invalid modes': ['Cur'],
'DWORD Items': {'SearchboxTaskbarMode': 0}, 'DWORD Items': {'SearchboxTaskbarMode': 0},
}, },
} }
# LibreOffice
LIBREOFFICE_XCU_DATA = '''<?xml version="1.0" encoding="UTF-8"?>
<oor:items xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.presentation.PresentationDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>Impress MS PowerPoint 2007 XML</value></prop></item>
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.sheet.SpreadsheetDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>Calc MS Excel 2007 XML</value></prop></item>
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.text.TextDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>MS Word 2007 XML</value></prop></item>
<item oor:path="/org.openoffice.Office.Common/Save/Document"><prop oor:name="WarnAlienFormat" oor:op="fuse"><value>false</value></prop></item>
</oor:items>
'''
# Visual C++ Runtimes # Visual C++ Runtimes
VCR_REDISTS = [ VCR_REDISTS = [
{'Name': 'Visual C++ 2010 x32...', {'Name': 'Visual C++ 2010 x32...',
@ -157,5 +192,3 @@ SETTINGS_WINDOWS_UPDATES = {
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -1,4 +1,6 @@
# Wizard Kit: Settings - Sources '''Wizard Kit: Settings - Sources'''
# pylint: disable=line-too-long
# vim: sts=2 sw=2 ts=2 tw=0
SOURCE_URLS = { SOURCE_URLS = {
'Adobe Reader DC': 'http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/1901020098/AcroRdrDC1901020098_en_US.exe', 'Adobe Reader DC': 'http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/1901020098/AcroRdrDC1901020098_en_US.exe',
@ -15,7 +17,7 @@ SOURCE_URLS = {
'ERUNT': 'http://www.aumha.org/downloads/erunt.zip', 'ERUNT': 'http://www.aumha.org/downloads/erunt.zip',
'Everything32': 'https://www.voidtools.com/Everything-1.4.1.935.x86.en-US.zip', 'Everything32': 'https://www.voidtools.com/Everything-1.4.1.935.x86.en-US.zip',
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.935.x64.en-US.zip', 'Everything64': 'https://www.voidtools.com/Everything-1.4.1.935.x64.en-US.zip',
'FastCopy': 'http://ftp.vector.co.jp/71/31/2323/FastCopy363_installer.exe', 'FastCopy': 'https://fastcopy.jp/archive/FastCopy380_installer.exe',
'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/1709472/ublock_origin-1.18.6-an+fx.xpi', 'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/1709472/ublock_origin-1.18.6-an+fx.xpi',
'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe', 'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe', 'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe',
@ -23,6 +25,7 @@ SOURCE_URLS = {
'Intel SSD Toolbox': r'https://downloadmirror.intel.com/28593/eng/Intel%20SSD%20Toolbox%20-%20v3.5.9.exe', 'Intel SSD Toolbox': r'https://downloadmirror.intel.com/28593/eng/Intel%20SSD%20Toolbox%20-%20v3.5.9.exe',
'IOBit_Uninstaller': r'https://portableapps.com/redirect/?a=IObitUninstallerPortable&s=s&d=pa&f=IObitUninstallerPortable_7.5.0.7.paf.exe', 'IOBit_Uninstaller': r'https://portableapps.com/redirect/?a=IObitUninstallerPortable&s=s&d=pa&f=IObitUninstallerPortable_7.5.0.7.paf.exe',
'KVRT': 'http://devbuilds.kaspersky-labs.com/devbuilds/KVRT/latest/full/KVRT.exe', 'KVRT': 'http://devbuilds.kaspersky-labs.com/devbuilds/KVRT/latest/full/KVRT.exe',
'LibreOffice': 'https://download.documentfoundation.org/libreoffice/stable/6.2.4/win/x86_64/LibreOffice_6.2.4_Win_x64.msi',
'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe', 'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
'NirCmd32': 'https://www.nirsoft.net/utils/nircmd.zip', 'NirCmd32': 'https://www.nirsoft.net/utils/nircmd.zip',
'NirCmd64': 'https://www.nirsoft.net/utils/nircmd-x64.zip', 'NirCmd64': 'https://www.nirsoft.net/utils/nircmd-x64.zip',
@ -37,8 +40,8 @@ SOURCE_URLS = {
'SDIO Torrent': 'http://snappy-driver-installer.org/downloads/SDIO_Update.torrent', 'SDIO Torrent': 'http://snappy-driver-installer.org/downloads/SDIO_Update.torrent',
'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe', 'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe',
'TestDisk': 'https://www.cgsecurity.org/testdisk-7.1-WIP.win.zip', 'TestDisk': 'https://www.cgsecurity.org/testdisk-7.1-WIP.win.zip',
'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-i686-bin.zip', 'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-i686-bin.zip',
'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-x86_64-bin.zip', 'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-x86_64-bin.zip',
'Winapp2': 'https://github.com/MoscaDotTo/Winapp2/archive/master.zip', 'Winapp2': 'https://github.com/MoscaDotTo/Winapp2/archive/master.zip',
'WizTree': 'https://antibody-software.com/files/wiztree_3_28_portable.zip', 'WizTree': 'https://antibody-software.com/files/wiztree_3_28_portable.zip',
'XMPlay 7z': 'https://support.xmplay.com/files/16/xmp-7z.zip?v=800962', 'XMPlay 7z': 'https://support.xmplay.com/files/16/xmp-7z.zip?v=800962',
@ -66,10 +69,18 @@ VCREDIST_SOURCES = {
'64': 'https://aka.ms/vs/15/release/vc_redist.x64.exe', '64': 'https://aka.ms/vs/15/release/vc_redist.x64.exe',
}, },
} }
NINITE_REGEX = {
'base': ['7-Zip', 'VLC'],
'standard': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
'standard7': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
}
NINITE_SOURCES = { NINITE_SOURCES = {
'Bundles': { 'Bundles': {
'Legacy.exe': '.net4.7.2-7zip-chrome-firefox-vlc', 'base.exe': '.net4.7.2-7zip-vlc',
'Modern.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-vlc', 'base-standard.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-sumatrapdf-vlc',
'base-standard7.exe': '.net4.7.2-7zip-chrome-firefox-sumatrapdf-vlc',
'standard.exe': 'chrome-classicstart-firefox-sumatrapdf',
'standard7.exe': 'chrome-firefox-sumatrapdf',
}, },
'Audio-Video': { 'Audio-Video': {
'AIMP.exe': 'aimp', 'AIMP.exe': 'aimp',
@ -216,5 +227,3 @@ WINDOWS_UPDATE_SOURCES = {
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2 tw=0

View file

@ -0,0 +1,118 @@
'''Wizard Kit: Settings - UFD'''
# pylint: disable=C0326,E0611
# vim: sts=2 sw=2 ts=2
from collections import OrderedDict
from settings.main import KIT_NAME_FULL,KIT_NAME_SHORT
# General
DOCSTRING = '''WizardKit: Build UFD
Usage:
build-ufd [options] --ufd-device PATH --linux PATH
[--linux-minimal PATH]
[--main-kit PATH]
[--winpe PATH]
[--extra-dir PATH]
build-ufd (-h | --help)
Options:
-d PATH, --linux-dgpu PATH
-e PATH, --extra-dir PATH
-k PATH, --main-kit PATH
-l PATH, --linux PATH
-m PATH, --linux-minimal PATH
-u PATH, --ufd-device PATH
-w PATH, --winpe PATH
-h --help Show this page
-M --use-mbr Use real MBR instead of GPT w/ Protective MBR
-F --force Bypass all confirmation messages. USE WITH EXTREME CAUTION!
-U --update Don't format device, just update
'''
ISO_LABEL = '{}_LINUX'.format(KIT_NAME_SHORT)
UFD_LABEL = '{}_UFD'.format(KIT_NAME_SHORT)
UFD_SOURCES = OrderedDict({
'Linux': {'Arg': '--linux', 'Type': 'ISO'},
'Linux (dGPU)': {'Arg': '--linux-dgpu', 'Type': 'ISO'},
'Linux (Minimal)': {'Arg': '--linux-minimal', 'Type': 'ISO'},
'WinPE': {'Arg': '--winpe', 'Type': 'ISO'},
'Main Kit': {'Arg': '--main-kit', 'Type': 'KIT'},
'Extra Dir': {'Arg': '--extra-dir', 'Type': 'DIR'},
})
# Definitions: Boot entries
BOOT_ENTRIES = {
# Path to check: Comment to remove
'/arch_minimal': 'UFD-MINIMAL',
'/dgpu': 'UFD-DGPU',
'/sources/boot.wim': 'UFD-WINPE',
}
BOOT_FILES = {
# Directory: extension
'/arch/boot/syslinux': 'cfg',
'/EFI/boot': 'conf',
}
# Definitions: Sources and Destinations
## NOTES: Paths are relative to the root of the ISO/UFD
## Sources use rsync's trailing slash syntax
ITEMS = {
'Extra Dir': (
('/', '/'),
),
'Linux': (
('/arch', '/'),
('/isolinux', '/'),
('/EFI/boot', '/EFI/'),
('/EFI/memtest86', '/EFI/'),
),
'Linux (dGPU)': (
('/arch/boot/x86_64/archiso.img', '/dgpu/'),
('/arch/boot/x86_64/vmlinuz', '/dgpu/'),
('/arch/pkglist.x86_64.txt', '/dgpu/'),
('/arch/x86_64', '/dgpu/'),
),
'Linux (Minimal)': (
('/arch/boot/x86_64/archiso.img', '/arch_minimal/'),
('/arch/boot/x86_64/vmlinuz', '/arch_minimal/'),
('/arch/pkglist.x86_64.txt', '/arch_minimal/'),
('/arch/x86_64', '/arch_minimal/'),
),
'Main Kit': (
('/', '/{}/'.format(KIT_NAME_FULL)),
),
'WinPE': (
('/bootmgr', '/'),
('/bootmgr.efi', '/'),
('/en_us', '/'),
('/Boot/', '/boot/'),
('/EFI/Boot/', '/EFI/Microsoft/'),
('/EFI/Microsoft/', '/EFI/Microsoft/'),
('/Boot/BCD', '/sources/'),
('/Boot/boot.sdi', '/sources/'),
('/bootmgr', '/sources/'),
('/sources/boot.wim', '/sources/'),
),
}
ITEMS_HIDDEN = (
# Linux (all versions)
'arch',
'arch_minimal',
'dgpu',
'EFI',
'isolinux',
# Main Kit
'{}/.bin'.format(KIT_NAME_FULL),
'{}/.cbin'.format(KIT_NAME_FULL),
# WinPE
'boot',
'bootmgr',
'bootmgr.efi',
'en-us',
'images',
'sources',
)
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -1,8 +1,10 @@
# Wizard Kit: Settings - Windows Builds '''Wizard Kit: Settings - Windows Builds'''
# pylint: disable=bad-continuation,bad-whitespace
# vim: sts=2 sw=2 ts=2
## NOTE: Data from here: https://en.wikipedia.org/wiki/Windows_10_version_history
WINDOWS_BUILDS = { WINDOWS_BUILDS = {
# Build, Version, Release, Codename, Marketing Name, Notes # Build, Version, Release, Codename, Marketing Name, Notes
'6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'), '6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6001': ('Vista', 'SP1', 'Longhorn', None, 'unsupported'), '6001': ('Vista', 'SP1', 'Longhorn', None, 'unsupported'),
'6002': ('Vista', 'SP2', 'Longhorn', None, 'unsupported'), '6002': ('Vista', 'SP2', 'Longhorn', None, 'unsupported'),
@ -202,15 +204,22 @@ WINDOWS_BUILDS = {
'18356': ('10', None, '19H1', None, 'preview build'), '18356': ('10', None, '19H1', None, 'preview build'),
'18358': ('10', None, '19H1', None, 'preview build'), '18358': ('10', None, '19H1', None, 'preview build'),
'18361': ('10', None, '19H1', None, 'preview build'), '18361': ('10', None, '19H1', None, 'preview build'),
'18362': ('10', 'v1903', '19H1', 'May 2019 Update', None),
'18836': ('10', None, '20H1', None, 'preview build'), '18836': ('10', None, '20H1', None, 'preview build'),
'18841': ('10', None, '20H1', None, 'preview build'), '18841': ('10', None, '20H1', None, 'preview build'),
'18845': ('10', None, '20H1', None, 'preview build'), '18845': ('10', None, '20H1', None, 'preview build'),
'18850': ('10', None, '20H1', None, 'preview build'), '18850': ('10', None, '20H1', None, 'preview build'),
'18855': ('10', None, '20H1', None, 'preview build'), '18855': ('10', None, '20H1', None, 'preview build'),
'18860': ('10', None, '20H1', None, 'preview build'),
'18865': ('10', None, '20H1', None, 'preview build'),
'18875': ('10', None, '20H1', None, 'preview build'),
'18885': ('10', None, '20H1', None, 'preview build'),
'18890': ('10', None, '20H1', None, 'preview build'),
'18894': ('10', None, '20H1', None, 'preview build'),
'18895': ('10', None, '20H1', None, 'preview build'),
'18898': ('10', None, '20H1', None, 'preview build'),
} }
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -32,8 +32,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
pause('Press Enter to exit...') pause('Press Enter to exit...')
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -1,133 +0,0 @@
# Wizard Kit: System Checklist
import os
import sys
# Init
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from functions.activation import *
from functions.cleanup import *
from functions.info import *
from functions.product_keys import *
from functions.setup import *
from functions.sw_diags import *
init_global_vars()
os.system('title {}: System Checklist Tool'.format(KIT_NAME_FULL))
set_log_file('System Checklist.log')
if __name__ == '__main__':
try:
stay_awake()
clear_screen()
print_info('{}: System Checklist Tool\n'.format(KIT_NAME_FULL))
ticket_number = get_ticket_number()
other_results = {
'Error': {
'BIOSKeyNotFoundError': 'BIOS key not found',
'CalledProcessError': 'Unknown Error',
'FileNotFoundError': 'File not found',
'GenericError': 'Unknown Error',
'SecureBootDisabledError': 'Disabled',
},
'Warning': {
'OSInstalledLegacyError': 'OS installed Legacy',
'SecureBootNotAvailError': 'Not available',
'SecureBootUnknownError': 'Unknown',
}}
if ENABLED_TICKET_NUMBERS:
print_info('Starting System Checklist for Ticket #{}\n'.format(
ticket_number))
# Configure
print_info('Configure')
if global_vars['OS']['Version'] == '10':
try_and_print(message='Explorer...',
function=config_explorer_system, cs='Done')
try_and_print(message='Windows Updates...',
function=config_windows_updates, cs='Done')
try_and_print(message='Updating Clock...',
function=update_clock, cs='Done')
# Restart Explorer
try_and_print(message='Restarting Explorer...',
function=restart_explorer, cs='Done')
# Cleanup
print_info('Cleanup')
try_and_print(message='AdwCleaner...',
function=cleanup_adwcleaner, cs='Done', other_results=other_results)
try_and_print(message='Desktop...',
function=cleanup_desktop, cs='Done')
try_and_print(message='{}...'.format(KIT_NAME_FULL),
function=delete_empty_folders, cs='Done',
folder_path=global_vars['ClientDir'])
# Export system info
print_info('Backup System Information')
try_and_print(message='AIDA64 reports...',
function=run_aida64, cs='Done', other_results=other_results)
try_and_print(message='File listing...',
function=backup_file_list, cs='Done', other_results=other_results)
try_and_print(message='Power plans...',
function=backup_power_plans, cs='Done')
try_and_print(message='Product Keys...', other_results=other_results,
function=run_produkey, cs='Done')
try_and_print(message='Registry...',
function=backup_registry, cs='Done', other_results=other_results)
# User data
print_info('User Data')
show_user_data_summary()
# Summary
print_info('Summary')
try_and_print(message='Operating System:',
function=show_os_name, ns='Unknown', silent_function=False)
try_and_print(message='Activation:',
function=show_os_activation, ns='Unknown', silent_function=False)
if (not windows_is_activated()
and global_vars['OS']['Version'] in ('8', '8.1', '10')):
try_and_print(message='BIOS Activation:',
function=activate_with_bios,
other_results=other_results)
try_and_print(message='Secure Boot Status:',
function=check_secure_boot_status, other_results=other_results)
try_and_print(message='Installed RAM:',
function=show_installed_ram, ns='Unknown', silent_function=False)
show_free_space()
try_and_print(message='Installed Antivirus:',
function=get_installed_antivirus, ns='Unknown',
other_results=other_results, print_return=True)
try_and_print(message='Installed Office:',
function=get_installed_office, ns='Unknown',
other_results=other_results, print_return=True)
# Play audio, show devices, open Windows updates, and open Activation
try_and_print(message='Opening Device Manager...',
function=open_device_manager, cs='Started')
try_and_print(message='Opening HWiNFO (Sensors)...',
function=run_hwinfo_sensors, cs='Started', other_results=other_results)
try_and_print(message='Opening Windows Updates...',
function=open_windows_updates, cs='Started')
if not windows_is_activated():
try_and_print(message='Opening Windows Activation...',
function=open_windows_activation, cs='Started')
sleep(3)
try_and_print(message='Running XMPlay...',
function=run_xmplay, cs='Started', other_results=other_results)
try:
check_secure_boot_status(show_alert=True)
except:
# Only trying to open alert message boxes
pass
# Done
print_standard('\nDone.')
pause('Press Enter exit...')
exit_script()
except SystemExit:
pass
except:
major_exception()
# vim: sts=2 sw=2 ts=2

View file

@ -165,8 +165,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
pause('Press Enter to exit...') pause('Press Enter to exit...')
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -0,0 +1,354 @@
'''Wizard Kit: System Setup'''
# pylint: disable=wildcard-import,wrong-import-position
# vim: sts=2 sw=2 ts=2
import os
import sys
# Init
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from collections import OrderedDict
from functions.activation import *
from functions.browsers import *
from functions.cleanup import *
from functions.info import *
from functions.product_keys import *
from functions.setup import *
from functions.sw_diags import *
from functions.windows_updates import *
init_global_vars()
os.system('title {}: System Setup'.format(KIT_NAME_FULL))
set_log_file('System Setup.log')
# STATIC VARIABLES
# pylint: disable=bad-whitespace,line-too-long
OTHER_RESULTS = {
'Error': {
'BIOSKeyNotFoundError': 'BIOS KEY NOT FOUND',
'CalledProcessError': 'UNKNOWN ERROR',
'FileNotFoundError': 'FILE NOT FOUND',
'GenericError': 'UNKNOWN ERROR',
'Not4KAlignedError': 'FALSE',
'SecureBootDisabledError': 'DISABLED',
'WindowsUnsupportedError': 'UNSUPPORTED',
},
'Warning': {
'GenericRepair': 'REPAIRED',
'NoProfilesError': 'NO PROFILES FOUND',
'NotInstalledError': 'NOT INSTALLED',
'OSInstalledLegacyError': 'OS INSTALLED LEGACY',
'SecureBootNotAvailError': 'NOT AVAILABLE',
'SecureBootUnknownError': 'UNKNOWN',
'UnsupportedOSError': 'UNSUPPORTED OS',
'WindowsOutdatedError': 'OUTDATED',
},
}
SETUP_ACTIONS = OrderedDict({
# Install software
'Installing Programs': {'Info': True},
'VCR': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_vcredists, 'Just run': True,},
'LibreOffice': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_libreoffice,
'If answer': 'LibreOffice', 'KWArgs': {'quickstart': False, 'register_mso_types': True, 'use_mso_formats': True, 'vcredist': False},
},
'Ninite bundle': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_ninite_bundle, 'KWArgs': {'cs': 'STARTED'},},
# Browsers
'Scanning for browsers': {'Info': True},
'Scan': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': scan_for_browsers, 'Just run': True, 'KWArgs': {'skip_ie': True},},
'Backing up browsers': {'Info': True},
'Backup browsers': {'New': False, 'Fab': True, 'Cur': True, 'HW': False, 'Function': backup_browsers, 'Just run': True,},
# Install extensions
'Installing Extensions': {'Info': True},
'Classic Shell skin': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Function': install_classicstart_skin, 'Win10 only': True,},
'Chrome extensions': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_chrome_extensions,},
'Firefox extensions': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_firefox_extensions,},
# Configure software'
'Configuring Programs': {'Info': True},
'Browser add-ons': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_adblock, 'Just run': True,
'Pause': 'Please enable uBlock Origin for all browsers',
},
'Classic Start': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Function': config_classicstart, 'Win10 only': True,},
'Config Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': config_windows_updates, 'Win10 only': True,},
'Enable Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': enable_windows_updates, 'KWArgs': {'silent': True},},
'Explorer (system)': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': config_explorer_system, 'Win10 only': True,},
'Explorer (user)': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': config_explorer_user, 'Win10 only': True,},
'Restart Explorer': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': restart_explorer,},
'Update Clock': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': update_clock,},
# Cleanup
'Cleaning up': {'Info': True},
'AdwCleaner': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': cleanup_adwcleaner,},
'Desktop': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': cleanup_desktop,},
'KIT_NAME_FULL': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': delete_empty_folders,},
# System Info
'Exporting system info': {'Info': True},
'AIDA64 Report': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': run_aida64,},
'File listing': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': backup_file_list,},
'Power plans': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': backup_power_plans,},
'Product Keys': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_produkey,},
'Registry': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': backup_registry,},
# Show Summary
'Summary': {'Info': True},
'Operating System': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_os_name, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
'Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_os_activation, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
'BIOS Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': activate_with_bios, 'If not activated': True,},
'Secure Boot': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': check_secure_boot_status, 'KWArgs': {'show_alert': False},},
'Installed RAM': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_installed_ram, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
'Temp size': {'New': False, 'Fab': False, 'Cur': True, 'HW': False, 'Function': show_temp_files_size, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
'Show free space': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_free_space, 'Just run': True,},
'Installed AV': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': get_installed_antivirus, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
'Installed Office': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': get_installed_office, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
'Partitions 4K aligned': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': check_4k_alignment, 'KWArgs': {'cs': 'TRUE', 'ns': 'FALSE'},},
# Open things
'Opening Programs': {'Info': True},
'Device Manager': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': open_device_manager, 'KWArgs': {'cs': 'STARTED'},},
'HWiNFO sensors': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_hwinfo_sensors, 'KWArgs': {'cs': 'STARTED'},},
'Speed test': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': open_speedtest, 'KWArgs': {'cs': 'STARTED'},},
'Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': open_windows_updates, 'KWArgs': {'cs': 'STARTED'},},
'Windows Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': open_windows_activation, 'If not activated': True, 'KWArgs': {'cs': 'STARTED'},},
'Sleep': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': sleep, 'Just run': True, 'KWArgs': {'seconds': 3},},
'XMPlay': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_xmplay, 'KWArgs': {'cs': 'STARTED'},},
})
SETUP_ACTION_KEYS = (
'Function',
'If not activated',
'Info',
'Just run',
'KWArgs',
'Pause',
)
SETUP_QUESTIONS = {
# AV
'MSE': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
# LibreOffice
'LibreOffice': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
# Ninite
'Base': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Ninite': True},
'Missing': {'New': False, 'Fab': True, 'Cur': False, 'HW': False, 'Ninite': True},
'Standard': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Ninite': True},
}
# pylint: enable=bad-whitespace,line-too-long
# Functions
def check_os_and_abort():
"""Check OS and prompt to abort if not supported."""
result = try_and_print(
message='OS support status...',
function=check_os_support_status,
cs='GOOD',
)
if not result['CS'] and 'Unsupported' in result['Error']:
print_warning('OS version not supported by this script')
if not ask('Continue anyway? (NOT RECOMMENDED)'):
abort()
def get_actions(setup_mode, answers):
"""Get actions to perform based on setup_mode, returns OrderedDict."""
actions = OrderedDict({})
for _key, _val in SETUP_ACTIONS.items():
_action = {}
_if_answer = _val.get('If answer', False)
_win10_only = _val.get('Win10 only', False)
# Set enabled status
_enabled = _val.get(setup_mode, False)
if _if_answer:
_enabled = _enabled and answers[_if_answer]
if _win10_only:
_enabled = _enabled and global_vars['OS']['Version'] == '10'
_action['Enabled'] = _enabled
# Set other keys
for _sub_key in SETUP_ACTION_KEYS:
_action[_sub_key] = _val.get(_sub_key, None)
# Fix KWArgs
if _action.get('KWArgs', {}) is None:
_action['KWArgs'] = {}
# Handle "special" actions
if _key == 'KIT_NAME_FULL':
# Cleanup WK folders
_key = KIT_NAME_FULL
_action['KWArgs'] = {'folder_path': global_vars['ClientDir']}
elif _key == 'Ninite bundle':
# Add install_ninite_bundle() kwargs
_action['KWArgs'].update({
kw.lower(): kv for kw, kv in answers.items()
if SETUP_QUESTIONS.get(kw, {}).get('Ninite', False)
})
elif _key == 'Explorer (user)':
# Explorer settings (user)
_action['KWArgs'] = {'setup_mode': setup_mode}
# Add to dict
actions[_key] = _action
return actions
def get_answers(setup_mode):
"""Get setup answers based on setup_mode and user input, returns dict."""
answers = {k: v.get(setup_mode, False) for k, v in SETUP_QUESTIONS.items()}
# Answer setup questions as needed
if answers['MSE'] is None and global_vars['OS']['Version'] == '7':
answers.update(get_av_selection())
if answers['LibreOffice'] is None:
answers['LibreOffice'] = ask('Install LibreOffice?')
return answers
def get_av_selection():
"""Get AV selection."""
av_answers = {
'MSE': False,
}
av_options = [
{
'Name': 'Microsoft Security Essentials',
'Disabled': global_vars['OS']['Version'] not in ['7'],
},
]
actions = [
{'Name': 'None', 'Letter': 'N'},
{'Name': 'Quit', 'Letter': 'Q'},
]
# Show menu
selection = menu_select(
'Please select an option to install',
main_entries=av_options,
action_entries=actions)
if selection.isnumeric():
index = int(selection) - 1
if 'Microsoft' in av_options[index]['Name']:
av_answers['MSE'] = True
elif selection == 'Q':
abort()
return av_answers
def get_mode():
"""Get mode via menu_select, returns str."""
setup_mode = None
mode_options = [
{'Name': 'New', 'Display Name': 'New / Clean install (no data)'},
{'Name': 'Data', 'Display Name': 'Clean install with data migration'},
{'Name': 'Cur', 'Display Name': 'Original OS (post-repair or overinstall)'},
{'Name': 'HW', 'Display Name': 'Hardware service (i.e. no software work)'},
]
actions = [
{'Name': 'Quit', 'Letter': 'Q'},
]
# Get selection
selection = menu_select(
'Please select a setup mode',
main_entries=mode_options,
action_entries=actions)
if selection.isnumeric():
index = int(selection) - 1
setup_mode = mode_options[index]['Name']
elif selection == 'Q':
abort()
return setup_mode
def main():
"""Main function."""
stay_awake()
clear_screen()
# Check installed OS
check_os_and_abort()
# Get setup mode
setup_mode = get_mode()
# Get answers to setup questions
answers = get_answers(setup_mode)
# Get actions to perform
actions = get_actions(setup_mode, answers)
# Perform actions
for action, values in actions.items():
kwargs = values.get('KWArgs', {})
# Print info lines
if values.get('Info', False):
print_info(action)
continue
# Print disabled actions
if not values.get('Enabled', False):
show_data(
message='{}...'.format(action),
data='DISABLED',
warning=True,
)
continue
# Check Windows activation if requested
if values.get('If not activated', False) and windows_is_activated():
# Skip
continue
# Run function
if values.get('Just run', False):
values['Function'](**kwargs)
else:
result = try_and_print(
message='{}...'.format(action),
function=values['Function'],
other_results=OTHER_RESULTS,
**kwargs)
# Wait for Ninite proc(s)
if action == 'Ninite bundle':
print_standard('Waiting for installations to finish...')
try:
for proc in result['Out']:
proc.wait()
except KeyboardInterrupt:
pass
# Pause
if values.get('Pause', False):
print_standard(values['Pause'])
pause()
# Show alert box for SecureBoot issues
try:
check_secure_boot_status(show_alert=True)
except Exception: # pylint: disable=broad-except
# Ignoring exceptions since we just want to show the popup
pass
# Done
pause('Press Enter to exit... ')
if __name__ == '__main__':
try:
main()
exit_script()
except SystemExit as sys_exit:
exit_script(sys_exit.code)
except: # pylint: disable=bare-except
major_exception()

View file

@ -21,8 +21,8 @@ if __name__ == '__main__':
# Done # Done
print_standard('\nDone.') print_standard('\nDone.')
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -57,6 +57,7 @@ if __name__ == '__main__':
# Installers # Installers
print_info(' Installers') print_info(' Installers')
try_and_print(message='Adobe Reader DC...', function=update_adobe_reader_dc, other_results=other_results, width=40) try_and_print(message='Adobe Reader DC...', function=update_adobe_reader_dc, other_results=other_results, width=40)
try_and_print(message='LibreOffice...', function=update_libreoffice, other_results=other_results, width=40)
try_and_print(message='Macs Fan Control...', function=update_macs_fan_control, other_results=other_results, width=40) try_and_print(message='Macs Fan Control...', function=update_macs_fan_control, other_results=other_results, width=40)
try_and_print(message='MS Office...', function=update_office, other_results=other_results, width=40) try_and_print(message='MS Office...', function=update_office, other_results=other_results, width=40)
try_and_print(message='Visual C++ Runtimes...', function=update_vcredists, other_results=other_results, width=40) try_and_print(message='Visual C++ Runtimes...', function=update_vcredists, other_results=other_results, width=40)
@ -134,8 +135,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
pause("Press Enter to exit...") pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -1,90 +0,0 @@
# Wizard Kit: User Checklist
import os
import sys
# Init
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from functions.browsers import *
from functions.cleanup import *
from functions.setup import *
init_global_vars()
os.system('title {}: User Checklist Tool'.format(KIT_NAME_FULL))
set_log_file('User Checklist ({USERNAME}).log'.format(**global_vars['Env']))
if __name__ == '__main__':
try:
stay_awake()
clear_screen()
print_info('{}: User Checklist\n'.format(KIT_NAME_FULL))
other_results = {
'Warning': {
'NotInstalledError': 'Not installed',
'NoProfilesError': 'No profiles found',
}}
answer_config_browsers = ask('Install adblock?')
if answer_config_browsers:
answer_reset_browsers = ask(
'Reset browsers to safe defaults first?')
if global_vars['OS']['Version'] == '10':
answer_config_classicshell = ask('Configure ClassicShell?')
answer_config_explorer_user = ask('Configure Explorer?')
# Cleanup
print_info('Cleanup')
try_and_print(message='Desktop...',
function=cleanup_desktop, cs='Done')
# Scan for supported browsers
print_info('Scanning for browsers')
scan_for_browsers()
# Homepages
print_info('Current homepages')
list_homepages()
# Backup
print_info('Backing up browsers')
backup_browsers()
# Reset
if answer_config_browsers and answer_reset_browsers:
print_info('Resetting browsers')
reset_browsers()
# Configure
print_info('Configuring programs')
if answer_config_browsers:
install_adblock()
if global_vars['OS']['Version'] == '10':
if answer_config_classicshell:
try_and_print(message='ClassicStart...',
function=config_classicstart, cs='Done')
if answer_config_explorer_user:
try_and_print(message='Explorer...',
function=config_explorer_user, cs='Done')
if (not answer_config_browsers
and not answer_config_classicshell
and not answer_config_explorer_user):
print_warning(' Skipped')
else:
if not answer_config_browsers:
print_warning(' Skipped')
# Restart Explorer
try_and_print(message='Restarting Explorer...',
function=restart_explorer, cs='Done')
# Run speedtest
popen_program(['start', '', 'https://fast.com'], shell=True)
# Done
print_standard('\nDone.')
pause('Press Enter to exit...')
exit_script()
except SystemExit:
pass
except:
major_exception()
# vim: sts=2 sw=2 ts=2

View file

@ -59,8 +59,8 @@ if __name__ == '__main__':
print_standard('\nDone.') print_standard('\nDone.')
pause("Press Enter to exit...") pause("Press Enter to exit...")
exit_script() exit_script()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

View file

@ -0,0 +1,46 @@
# Wizard Kit: Windows updates
import os
import sys
# Init
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from functions.windows_updates import *
init_global_vars()
os.system('title {}: Windows Updates Tool'.format(KIT_NAME_FULL))
set_log_file('Windows Updates Tool.log')
if __name__ == '__main__':
try:
clear_screen()
print_info('{}: Windows Updates Tool\n'.format(KIT_NAME_FULL))
# Check args
if '--disable' in sys.argv:
disable_windows_updates()
elif '--enable' in sys.argv:
enable_windows_updates()
else:
print_error('Bad mode.')
abort()
# Done
exit_script()
except GenericError as err:
# Failed to complete request, show error(s) and prompt tech
print_standard(' ')
for line in str(err).splitlines():
print_warning(line)
print_standard(' ')
print_error('Error(s) encountered, see above.')
print_standard(' ')
if '--disable' in sys.argv:
print_standard('Please reboot and try again.')
pause('Press Enter to exit... ')
exit_script(1)
except SystemExit as sys_exit:
exit_script(sys_exit.code)
except:
major_exception()
# vim: sts=2 sw=2 ts=2

View file

@ -15,8 +15,8 @@ set_log_file('WinPE.log')
if __name__ == '__main__': if __name__ == '__main__':
try: try:
menu_root() menu_root()
except SystemExit: except SystemExit as sys_exit:
pass exit_script(sys_exit.code)
except: except:
major_exception() major_exception()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -19,6 +19,7 @@ menuentry "MemTest86" {
icon /EFI/boot/icons/wk_memtest.png icon /EFI/boot/icons/wk_memtest.png
loader /EFI/memtest86/memtestx64.efi loader /EFI/memtest86/memtestx64.efi
} }
menuentry "Linux" { menuentry "Linux" {
icon /EFI/boot/icons/wk_arch.png icon /EFI/boot/icons/wk_arch.png
loader /arch/boot/x86_64/vmlinuz loader /arch/boot/x86_64/vmlinuz
@ -26,15 +27,32 @@ menuentry "Linux" {
initrd /arch/boot/amd_ucode.img initrd /arch/boot/amd_ucode.img
initrd /arch/boot/x86_64/archiso.img initrd /arch/boot/x86_64/archiso.img
options "archisobasedir=arch archisolabel=%ARCHISO_LABEL% copytoram loglevel=3" options "archisobasedir=arch archisolabel=%ARCHISO_LABEL% copytoram loglevel=3"
submenuentry "Linux (i3)" {
add_options "i3"
}
submenuentry "Linux (CLI)" { submenuentry "Linux (CLI)" {
add_options "loglevel=4 nomodeset nox" add_options "nox"
} }
#UFD-MINIMAL#submenuentry "Linux (Minimal)" {
#UFD-MINIMAL# loader /arch_minimal/vmlinuz
#UFD-MINIMAL# initrd
#UFD-MINIMAL# initrd /arch/boot/intel_ucode.img
#UFD-MINIMAL# initrd /arch/boot/amd_ucode.img
#UFD-MINIMAL# initrd /arch_minimal/archiso.img
#UFD-MINIMAL# options
#UFD-MINIMAL# options "archisobasedir=arch_minimal archisolabel=%ARCHISO_LABEL% copytoram loglevel=3"
#UFD-MINIMAL#}
} }
#UFD-WINPE#menuentry "WindowsPE" { #UFD-WINPE#menuentry "WindowsPE" {
#UFD-WINPE# ostype windows #UFD-WINPE# ostype windows
#UFD-WINPE# icon /EFI/boot/icons/wk_win.png #UFD-WINPE# icon /EFI/boot/icons/wk_win.png
#UFD-WINPE# loader /EFI/microsoft/bootx64.efi #UFD-WINPE# loader /EFI/microsoft/bootx64.efi
#UFD-WINPE#} #UFD-WINPE#}
#UFD-DGPU#menuentry "Mac dGPU Disable Tool" {
#UFD-DGPU# icon /EFI/boot/icons/dgpu.png
#UFD-DGPU# loader /dgpu/vmlinuz
#UFD-DGPU# initrd /arch/boot/intel_ucode.img
#UFD-DGPU# initrd /arch/boot/amd_ucode.img
#UFD-DGPU# initrd /dgpu/archiso.img
#UFD-DGPU# options "archisobasedir=dgpu archisolabel=%ARCHISO_LABEL% nomodeset"
#UFD-DGPU#}

View file

@ -34,5 +34,5 @@ alias srsz='sudo rsync -avhzPS --stats --exclude-from="$HOME/.rsync_exclusions"'
alias testdisk='sudo testdisk' alias testdisk='sudo testdisk'
alias umount='sudo umount' alias umount='sudo umount'
alias unmount='sudo umount' alias unmount='sudo umount'
alias wkclone='sudo ddrescue-tui clone' alias wkclone='ddrescue-tui clone'
alias wkimage='sudo ddrescue-tui image' alias wkimage='ddrescue-tui image'

View file

@ -1,5 +1,5 @@
set -g status off set -g status off
set -g pane-active-border-fg white set -g pane-active-border-style fg=white
# Window names # Window names
set -g set-titles on set -g set-titles on

View file

@ -1,23 +1,22 @@
## .update_network ##
#!/bin/env bash #!/bin/env bash
# #
## Connect to network and update hostname ## Setup network and update hostname
# Connect # Wait for WiFi
connect-to-network sleep 1s
sleep 2s
# Set hostname
IP="$(ip a show scope global \ IP="$(ip a show scope global \
| grep inet \ | grep inet \
| head -1 \ | head -1 \
| sed -r 's#.*inet ([0-9]+.[0-9]+.[0-9]+.[0-9]+.)/.*#\1#')" | sed -r 's#.*inet ([0-9]+.[0-9]+.[0-9]+.[0-9]+.)/.*#\1#')"
HOSTNAME="$(dig +noall +answer +short -x "$IP" \ if [[ "${IP:+x}" ]]; then
| grep -v ';' \ NEW_HOSTNAME="$(dig +noall +answer +short -x "$IP" \
| head -1 \ | grep -v ';' \
| sed 's/\.$//')" | head -1 \
| sed 's/\.$//')"
# Set hostname fi
if [[ "${HOSTNAME:+x}" ]]; then if [[ "${NEW_HOSTNAME:+x}" ]]; then
sudo hostnamectl set-hostname "${HOSTNAME}" sudo hostnamectl set-hostname "${NEW_HOSTNAME}"
fi fi

View file

@ -8,17 +8,6 @@ LINUX boot/x86_64/vmlinuz
INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img
APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram loglevel=3 APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram loglevel=3
LABEL wk_linux_i3
TEXT HELP
A live Linux environment (i3)
* HW diagnostics, file-based backups, data recovery, etc
ENDTEXT
MENU LABEL Linux (i3)
LINUX boot/x86_64/vmlinuz
INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img
APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram loglevel=3 i3
SYSAPPEND 3
LABEL wk_linux_cli LABEL wk_linux_cli
TEXT HELP TEXT HELP
A live Linux environment (CLI) A live Linux environment (CLI)
@ -27,5 +16,17 @@ ENDTEXT
MENU LABEL Linux (CLI) MENU LABEL Linux (CLI)
LINUX boot/x86_64/vmlinuz LINUX boot/x86_64/vmlinuz
INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img
APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram nox nomodeset APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram nox
SYSAPPEND 3 SYSAPPEND 3
#UFD-MINIMAL#LABEL wk_linux_minimal
#UFD-MINIMAL#TEXT HELP
#UFD-MINIMAL#A live Linux environment (Minimal)
#UFD-MINIMAL# * HW diagnostics, file-based backups, data recovery, etc
#UFD-MINIMAL#ENDTEXT
#UFD-MINIMAL#MENU LABEL Linux (Minimal)
#UFD-MINIMAL#LINUX ../arch_minimal/vmlinuz
#UFD-MINIMAL#INITRD boot/intel_ucode.img,boot/amd_ucode.img,../arch_minimal/archiso.img
#UFD-MINIMAL#APPEND archisobasedir=arch_minimal archisolabel=%ARCHISO_LABEL% copytoram loglevel=3
#UFD-MINIMAL#SYSAPPEND 3

View file

@ -7,5 +7,7 @@ rofi.color-normal: argb:d02d3036, #d8d8d8, argb:d02d3036, #2d3036, #d64937
rofi.color-active: argb:d0222222, #d64937, argb:d0222222, #d64937, #d8d8d8 rofi.color-active: argb:d0222222, #d64937, argb:d0222222, #d64937, #d8d8d8
rofi.color-urgent: argb:d0888888, #d8d8d8, argb:d0888888, #888888, #d64937 rofi.color-urgent: argb:d0888888, #d8d8d8, argb:d0888888, #888888, #d64937
rofi.font: Noto Sans 12
rofi.separator-style: solid rofi.separator-style: solid
rofi.hide-scrollbar: true rofi.hide-scrollbar: true

View file

@ -0,0 +1,6 @@
[Unit]
Description=Conky config update service
[Service]
Type=oneshot
ExecStart=%h/.update_conky

View file

@ -0,0 +1,10 @@
[Unit]
Description=Conky config update timer
[Timer]
OnBootSec=5s
OnUnitActiveSec=30s
Unit=update-conky.service
[Install]
WantedBy=timers.target

View file

@ -1,18 +1,24 @@
#!/bin/bash #!/bin/bash
IF_LIST=($(ip l | egrep '^[0-9]+:\s+(eth|en|wl)' | sed -r 's/^[0-9]+:\s+(\w+):.*/\1/' | sort)) IF_LIST=($(ip l \
| egrep '^[0-9]+:\s+(eth|en|wl)' \
| sed -r 's/^[0-9]+:\s+(\w+):.*/\1/' \
| sort))
# Reset conkyrc to default
rm "${HOME}/.conkyrc"
cp "${HOME}/.conkyrc_base" "${HOME}/.conkyrc"
# Add interfaces to conkyrc # Add interfaces to conkyrc
if fgrep '#Network' $HOME/.conkyrc; then for i in "${IF_LIST[@]}"; do
for i in "${IF_LIST[@]}"; do if [[ "${i:0:1}" == "e" ]]; then
if [[ "${i:0:1}" == "e" ]]; then sed -i -r "s/#Network/Wired:\${alignr}\${addr $i}\n#Network/" $HOME/.conkyrc
sed -i -r "s/#Network/Wired:\${alignr}\${addr $i}\n#Network/" $HOME/.conkyrc else
else sed -i -r "s/#Network/Wireless:\${alignr}\${addr $i}\n#Network/" $HOME/.conkyrc
sed -i -r "s/#Network/Wireless:\${alignr}\${addr $i}\n#Network/" $HOME/.conkyrc fi
fi done
done
# Remove '#Network' line to prevent duplicating lines if this script is re-run # Remove '#Network' line to prevent duplicating lines if this script is re-run
sed -i -r "s/#Network//" $HOME/.conkyrc sed -i -r "s/#Network//" $HOME/.conkyrc
fi
# vim: sts=2 sw=2 ts=2

View file

@ -32,10 +32,10 @@ offset_urxvt="24"
# Update settings if necessary # Update settings if necessary
if [[ "${dpi}" -ge 192 ]]; then if [[ "${dpi}" -ge 192 ]]; then
# Conky # Conky
sed -i 's/minimum_size 180 0/minimum_size 360 0/' "${HOME}/.conkyrc" sed -i 's/minimum_size 180 0/minimum_size 360 0/' "${HOME}/.conkyrc_base"
sed -i 's/maximum_width 180/maximum_width 360/' "${HOME}/.conkyrc" sed -i 's/maximum_width 180/maximum_width 360/' "${HOME}/.conkyrc_base"
sed -i 's/gap_x 20/gap_x 40/' "${HOME}/.conkyrc" sed -i 's/gap_x 20/gap_x 40/' "${HOME}/.conkyrc_base"
sed -i 's/gap_y 24/gap_y 48/' "${HOME}/.conkyrc" sed -i 's/gap_y 24/gap_y 48/' "${HOME}/.conkyrc_base"
# Fonts # Fonts
sed -i 's/!Xft.dpi: 192/Xft.dpi: 192/' "${HOME}/.Xresources" sed -i 's/!Xft.dpi: 192/Xft.dpi: 192/' "${HOME}/.Xresources"
@ -47,6 +47,9 @@ if [[ "${dpi}" -ge 192 ]]; then
# i3 # i3
sed -i -r 's/(height\s+) 26/\1 52/' "${HOME}/.config/i3/config" sed -i -r 's/(height\s+) 26/\1 52/' "${HOME}/.config/i3/config"
# Rofi
sed -i -r 's/Noto Sans 12/Noto Sans 24/' "${HOME}/.config/rofi/config"
# Tint2 # Tint2
sed -i 's/panel_size = 100% 30/panel_size = 100% 60/' \ sed -i 's/panel_size = 100% 30/panel_size = 100% 60/' \
"${HOME}/.config/tint2/tint2rc" "${HOME}/.config/tint2/tint2rc"
@ -67,6 +70,9 @@ fi
urxvt_geometry="${width_urxvt}x${height_urxvt}+${offset_urxvt}+${offset_urxvt}" urxvt_geometry="${width_urxvt}x${height_urxvt}+${offset_urxvt}+${offset_urxvt}"
sed -i -r "s/${REGEX_URXVT}/\1${urxvt_geometry}/" "${HOME}/.Xresources" sed -i -r "s/${REGEX_URXVT}/\1${urxvt_geometry}/" "${HOME}/.Xresources"
# Update conky
$HOME/.update_conky
# Update X # Update X
xset s off xset s off
xset -dpms xset -dpms

View file

@ -9,9 +9,6 @@ if [ "$(fgconsole 2>/dev/null)" -eq "1" ]; then
sed -i -r 's/openbox-session/i3/' ~/.xinitrc sed -i -r 's/openbox-session/i3/' ~/.xinitrc
fi fi
# Update Conky
$HOME/.update_conky
# Start X or HW-diags # Start X or HW-diags
if ! fgrep -q "nox" /proc/cmdline; then if ! fgrep -q "nox" /proc/cmdline; then
# Kill Xorg after 30 seconds if it doesn't fully initialize # Kill Xorg after 30 seconds if it doesn't fully initialize

View file

@ -0,0 +1,2 @@
#Put WiFi network info here
#'WiFi SSID': 'WiFi Password'

View file

@ -16,6 +16,7 @@ e2fsprogs
hexedit hexedit
hfsprogs hfsprogs
htop htop
iwd
ldmtool ldmtool
ldns ldns
lha lha
@ -31,7 +32,9 @@ networkmanager
p7zip p7zip
progsreiserfs progsreiserfs
python python
python-docopt
python-psutil python-psutil
python-pytz
python-requests python-requests
reiserfsprogs reiserfsprogs
rfkill rfkill

View file

@ -14,6 +14,7 @@ rp-pppoe
smartmontools smartmontools
speedtouch speedtouch
testdisk testdisk
wpa_actiond
vim-minimal vim-minimal
vpnc vpnc
wvdial wvdial

View file

@ -281,6 +281,10 @@ function update_live_env() {
mkdir -p "$LIVE_DIR/airootfs/usr/share/wallpaper" mkdir -p "$LIVE_DIR/airootfs/usr/share/wallpaper"
cp "$ROOT_DIR/Images/Linux.png" "$LIVE_DIR/airootfs/usr/share/wallpaper/burned.in" cp "$ROOT_DIR/Images/Linux.png" "$LIVE_DIR/airootfs/usr/share/wallpaper/burned.in"
fi fi
# WiFi
cp "$ROOT_DIR/.linux_items/known_networks" "$LIVE_DIR/airootfs/root/known_networks"
echo "add-known-networks --user=$username" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"
} }
function update_repo() { function update_repo() {