Compare commits
193 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 875166b683 | |||
| 179748c469 | |||
| ffcee1156a | |||
| edd944a325 | |||
| 75119c15ad | |||
| e6db63c8b0 | |||
| e388b77639 | |||
| bbdef56df2 | |||
| 6a5a944ea0 | |||
| 3621914312 | |||
| 0ef9412995 | |||
| 0335797a5d | |||
| 2a07aebff3 | |||
| 244d917c73 | |||
| 13b8dc8696 | |||
| 58576cbdb4 | |||
| a3a7b25512 | |||
| 50033f42f6 | |||
| d00d625142 | |||
| 97842e82f2 | |||
| 4a54b6e00c | |||
| ee7c7c2448 | |||
| 3aff533c4d | |||
| a256e6e764 | |||
| cc3e36c60d | |||
| f91eac3ec3 | |||
| 667de2d672 | |||
| 272fd3e43f | |||
| 58069f9db2 | |||
| 33d266f83e | |||
| e84feb3160 | |||
| 243fb78837 | |||
| 565ec32294 | |||
| 694eec911b | |||
| 44b7f786e7 | |||
| f7d212d115 | |||
| 3230984a4f | |||
| 9f4b0ffa82 | |||
| 75bad41e93 | |||
| 8602723adb | |||
| b01cb6ed26 | |||
| aaae24b790 | |||
| 94c8c2ba01 | |||
| 80a0d9874a | |||
| e85d9c220e | |||
| 864a546fe3 | |||
| 3ce134901a | |||
| 6d62bfeaea | |||
| c18e82af75 | |||
| 9f5c097f81 | |||
| 42c72f20f4 | |||
| 4e887c318b | |||
| e39a2acf26 | |||
| 8ab56ae9d8 | |||
| 1c6f0f5f28 | |||
| 4aedea65c7 | |||
| d7067af522 | |||
| 484f13dc29 | |||
| 868932c5e4 | |||
| 17a62d6f36 | |||
| b20b612315 | |||
| 73bd58a973 | |||
| 33e9cde0f4 | |||
| ee9d316217 | |||
| f7345a8a54 | |||
| 075a0d8541 | |||
| 5147a4105f | |||
| d933ec6415 | |||
| f5681a93d8 | |||
| dbe4a342cc | |||
| 460fd9c952 | |||
| 7603b93338 | |||
| 42720d322b | |||
| 6bef46bb4d | |||
| 670619b65e | |||
| b9c4c9c32f | |||
| e7642bdc63 | |||
| a12995b37d | |||
| ee34e692dd | |||
| 2134536960 | |||
| 47ccd7dd91 | |||
| 24e4f7ddcc | |||
| bddf47816f | |||
| 0c1c65182c | |||
| cda5aee714 | |||
| 7c16d13f65 | |||
| 840008d8cd | |||
| 090a9f2b96 | |||
| 4467369811 | |||
| a20fdf7bd3 | |||
| df1d2b713f | |||
| 4a34f5477d | |||
| 0ace951380 | |||
| 6a1cf98d0b | |||
| 8f14fd2442 | |||
| a78a077bdf | |||
| d101ec627f | |||
| 408a0c6114 | |||
| ebd1bbda18 | |||
| 7499639c5c | |||
| d6f3455236 | |||
| 55d752dd8b | |||
| f8fc38a78b | |||
| 895d8d2f0a | |||
| 815cfde84a | |||
| 9a7fdba3f9 | |||
| f9a6850c1a | |||
| 172f00e4e9 | |||
| 86203a4b86 | |||
| 8e234ce0cd | |||
| 9689dcfeab | |||
| cafa2c24fb | |||
| 3ff61e9948 | |||
| 9980dab27b | |||
| 55ce4d8ded | |||
| 662f8c1254 | |||
| d34df7ae07 | |||
| dfcc717048 | |||
| 21cbe5d445 | |||
| d94e9097b7 | |||
| 228a5f640e | |||
| acd484f891 | |||
| d958945fe8 | |||
| 3e10f2cb8c | |||
| 9810c630f6 | |||
| c3bf5f6730 | |||
| c63b388f81 | |||
| 20a0881421 | |||
| 203ad715e0 | |||
| 986c870090 | |||
| 4feb15182e | |||
| 88d3ade64d | |||
| 4202d3c1dc | |||
| bcb9228234 | |||
| f2ab06374b | |||
| a2c41fbaf2 | |||
| 7e6cfa1896 | |||
| 13e14e6734 | |||
| 45a7f84e19 | |||
| 86f748c599 | |||
| becc564269 | |||
| 7ab6ccbd36 | |||
| 8e7d202c32 | |||
| 05de5c7294 | |||
| fc2b90a2c0 | |||
| de7993c39c | |||
| dbb606601d | |||
| 1dc22d5991 | |||
| f50ea711e6 | |||
| 2cce572acf | |||
| a253fdc80f | |||
| 386a8b7000 | |||
| a5eb64a055 | |||
| c009ab2d41 | |||
| 1bfdb14be4 | |||
| bf9d994675 | |||
| f654052f1d | |||
| 12326a5e2c | |||
| 171cd0019e | |||
| 62edaac25a | |||
| 60d08a189d | |||
| 69832eda5d | |||
| 534f258846 | |||
| 0126452bf1 | |||
| 3334638a2c | |||
| cb012423bb | |||
| 4c76e59238 | |||
| ba69773fba | |||
| f19c4b2422 | |||
| 59d89575ed | |||
| 9678f143c7 | |||
| 7aafcd7c01 | |||
| b834be9f00 | |||
| ba3bf480f7 | |||
| f9bcd534d4 | |||
| d302be2d7c | |||
| 13fc64e6ab | |||
| 44ddb3c258 | |||
| 95d7159414 | |||
| 03a143488c | |||
| 6efc970374 | |||
| e3ebc2d1b8 | |||
| 96136e8e46 | |||
| ddb9c4041b | |||
| 9228137187 | |||
| 89fd647792 | |||
| 9f66b151af | |||
| 08294caffc | |||
| 7c66eb5e99 | |||
| d5bc74d21b | |||
| 4f6a07c449 | |||
| 606e657591 | |||
| 2717ad1a88 |
160 changed files with 9409 additions and 8535 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2021 Alan Mason
|
Copyright (c) 2023 Alan Mason
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ if defined L_NCMD (
|
||||||
rem use Powershell's window instead of %CON%
|
rem use Powershell's window instead of %CON%
|
||||||
echo UAC.ShellExecute "%POWERSHELL%", "%ps_args% -File "%script%"", "", "runas", 3 >> "%bin%\tmp\Elevate.vbs"
|
echo UAC.ShellExecute "%POWERSHELL%", "%ps_args% -File "%script%"", "", "runas", 3 >> "%bin%\tmp\Elevate.vbs"
|
||||||
) else (
|
) else (
|
||||||
echo UAC.ShellExecute "%CON%", "-run %POWERSHELL% %ps_args% -File "%script%" -new_console:n", "", "runas", 1 >> "%bin%\tmp\Elevate.vbs"
|
echo UAC.ShellExecute "%CON%", "-run %POWERSHELL% %ps_args% -File "^"%script%^"" -new_console:n", "", "runas", 1 >> "%bin%\tmp\Elevate.vbs"
|
||||||
)
|
)
|
||||||
|
|
||||||
rem Run
|
rem Run
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,2 @@
|
||||||
# WizardKit: Scripts #
|
# WizardKit: Scripts #
|
||||||
|
|
||||||
## pylint ##
|
|
||||||
|
|
||||||
These scripts use two spaces per indent instead of the default four. As such you will need to update your pylintrc file or run like this:
|
|
||||||
|
|
||||||
`pylint --indent-after-paren=2 --indent-string=' ' wk`
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,23 @@
|
||||||
"""WizardKit: Auto Repair Tool"""
|
"""WizardKit: Auto Repair Tool"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import wk
|
import wk
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
REBOOT_STR = wk.std.color_string('Reboot', 'YELLOW')
|
REBOOT_STR = wk.ui.ansi.color_string('Reboot', 'YELLOW')
|
||||||
class MenuEntry():
|
class MenuEntry():
|
||||||
"""Simple class to allow cleaner code below."""
|
"""Simple class to allow cleaner code below."""
|
||||||
def __init__(self, name, function=None, selected=True, **kwargs):
|
def __init__(
|
||||||
self.name = name
|
self,
|
||||||
self.details = {
|
name: str,
|
||||||
|
function: str | None = None,
|
||||||
|
selected: bool = True,
|
||||||
|
**kwargs):
|
||||||
|
self.name: str = name
|
||||||
|
self.details: dict[str, Any] = {
|
||||||
'Function': function,
|
'Function': function,
|
||||||
'Selected': selected,
|
'Selected': selected,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
@ -54,14 +61,14 @@ BASE_MENUS = {
|
||||||
),
|
),
|
||||||
'Manual Steps': (
|
'Manual Steps': (
|
||||||
MenuEntry('AdwCleaner', 'auto_adwcleaner'),
|
MenuEntry('AdwCleaner', 'auto_adwcleaner'),
|
||||||
MenuEntry('UninstallView', 'auto_uninstallview'),
|
MenuEntry('Bulk Crap Uninstaller', 'auto_bcuninstaller'),
|
||||||
MenuEntry('Enable Windows Updates', 'auto_windows_updates_enable'),
|
MenuEntry('Enable Windows Updates', 'auto_windows_updates_enable'),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'Options': (
|
'Options': (
|
||||||
MenuEntry('Kill Explorer', selected=False),
|
MenuEntry('Kill Explorer', selected=False),
|
||||||
|
MenuEntry('Run AVRemover (once)'),
|
||||||
MenuEntry('Run RKill'),
|
MenuEntry('Run RKill'),
|
||||||
MenuEntry('Run TDSSKiller (once)'),
|
|
||||||
MenuEntry('Sync Clock'),
|
MenuEntry('Sync Clock'),
|
||||||
MenuEntry('Use Autologon', selected=False),
|
MenuEntry('Use Autologon', selected=False),
|
||||||
),
|
),
|
||||||
|
|
@ -76,7 +83,6 @@ PRESETS = {
|
||||||
'Default': { # Will be expanded at runtime using BASE_MENUS
|
'Default': { # Will be expanded at runtime using BASE_MENUS
|
||||||
'Options': (
|
'Options': (
|
||||||
'Run RKill',
|
'Run RKill',
|
||||||
'Run TDSSKiller (once)',
|
|
||||||
'Sync Clock',
|
'Sync Clock',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -87,8 +93,8 @@ if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
wk.repairs.win.run_auto_repairs(BASE_MENUS, PRESETS)
|
wk.repairs.win.run_auto_repairs(BASE_MENUS, PRESETS)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
wk.std.abort()
|
wk.ui.cli.abort()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,22 @@
|
||||||
"""WizardKit: Auto System Setup Tool"""
|
"""WizardKit: Auto System Setup Tool"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import wk
|
import wk
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
class MenuEntry():
|
class MenuEntry():
|
||||||
"""Simple class to allow cleaner code below."""
|
"""Simple class to allow cleaner code below."""
|
||||||
def __init__(self, name, function=None, selected=True, **kwargs):
|
def __init__(
|
||||||
self.name = name
|
self,
|
||||||
self.details = {
|
name: str,
|
||||||
|
function: str | None = None,
|
||||||
|
selected: bool = True,
|
||||||
|
**kwargs):
|
||||||
|
self.name: str = name
|
||||||
|
self.details: dict[str, Any] = {
|
||||||
'Function': function,
|
'Function': function,
|
||||||
'Selected': selected,
|
'Selected': selected,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
@ -26,14 +33,17 @@ BASE_MENUS = {
|
||||||
MenuEntry('Set Custom Power Plan', 'auto_set_custom_power_plan'),
|
MenuEntry('Set Custom Power Plan', 'auto_set_custom_power_plan'),
|
||||||
),
|
),
|
||||||
'Install Software': (
|
'Install Software': (
|
||||||
MenuEntry('Visual C++ Runtimes', 'auto_install_vcredists'),
|
MenuEntry('Winget', 'auto_install_winget'),
|
||||||
MenuEntry('Firefox', 'auto_install_firefox'),
|
MenuEntry('Firefox', 'auto_install_firefox'),
|
||||||
MenuEntry('LibreOffice', 'auto_install_libreoffice', selected=False),
|
MenuEntry('LibreOffice', 'auto_install_libreoffice', selected=False),
|
||||||
MenuEntry('Open Shell', 'auto_install_open_shell'),
|
MenuEntry('Open Shell', 'auto_install_open_shell'),
|
||||||
MenuEntry('Software Bundle', 'auto_install_software_bundle'),
|
MenuEntry('Software Bundle', 'auto_install_software_bundle'),
|
||||||
|
MenuEntry('Software Upgrades', 'auto_install_software_upgrades'),
|
||||||
|
MenuEntry('Visual C++ Runtimes', 'auto_install_vcredists'),
|
||||||
),
|
),
|
||||||
'Configure System': (
|
'Configure System': (
|
||||||
MenuEntry('Open Shell', 'auto_config_open_shell'),
|
MenuEntry('Open Shell', 'auto_config_open_shell'),
|
||||||
|
MenuEntry('Disable Password Expiration', 'auto_disable_password_expiration'),
|
||||||
MenuEntry('Enable BSoD MiniDumps', 'auto_enable_bsod_minidumps'),
|
MenuEntry('Enable BSoD MiniDumps', 'auto_enable_bsod_minidumps'),
|
||||||
MenuEntry('Enable RegBack', 'auto_enable_regback'),
|
MenuEntry('Enable RegBack', 'auto_enable_regback'),
|
||||||
MenuEntry('Enable System Restore', 'auto_system_restore_enable'),
|
MenuEntry('Enable System Restore', 'auto_system_restore_enable'),
|
||||||
|
|
@ -61,6 +71,7 @@ BASE_MENUS = {
|
||||||
'Run Programs': (
|
'Run Programs': (
|
||||||
MenuEntry('Device Manager', 'auto_open_device_manager'),
|
MenuEntry('Device Manager', 'auto_open_device_manager'),
|
||||||
MenuEntry('HWiNFO Sensors', 'auto_open_hwinfo_sensors'),
|
MenuEntry('HWiNFO Sensors', 'auto_open_hwinfo_sensors'),
|
||||||
|
MenuEntry('Microsoft Store Updates', 'auto_open_microsoft_store_updates'),
|
||||||
MenuEntry('Snappy Driver Installer', 'auto_open_snappy_driver_installer_origin'),
|
MenuEntry('Snappy Driver Installer', 'auto_open_snappy_driver_installer_origin'),
|
||||||
MenuEntry('Windows Activation', 'auto_open_windows_activation'),
|
MenuEntry('Windows Activation', 'auto_open_windows_activation'),
|
||||||
MenuEntry('Windows Updates', 'auto_open_windows_updates'),
|
MenuEntry('Windows Updates', 'auto_open_windows_updates'),
|
||||||
|
|
@ -90,6 +101,9 @@ PRESETS = {
|
||||||
'Install Software': (
|
'Install Software': (
|
||||||
'Firefox', # Needed to handle profile upgrade nonsense
|
'Firefox', # Needed to handle profile upgrade nonsense
|
||||||
),
|
),
|
||||||
|
'Run Programs': (
|
||||||
|
'Microsoft Store Updates',
|
||||||
|
),
|
||||||
'System Summary': (
|
'System Summary': (
|
||||||
'Operating System',
|
'Operating System',
|
||||||
'Windows Activation',
|
'Windows Activation',
|
||||||
|
|
@ -154,8 +168,8 @@ if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
wk.setup.win.run_auto_setup(BASE_MENUS, PRESETS)
|
wk.setup.win.run_auto_setup(BASE_MENUS, PRESETS)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
wk.std.abort()
|
wk.ui.cli.abort()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,5 @@ if __name__ == '__main__':
|
||||||
wk.kit.ufd.build_ufd()
|
wk.kit.ufd.build_ufd()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: #pylint: disable=bare-except
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
wk.kit.build.build_kit()
|
wk.kit.build.build_kit()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
wk.std.abort()
|
wk.ui.cli.abort()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
13
scripts/check_av.ps1
Normal file
13
scripts/check_av.ps1
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# WizardKit: Check Antivirus
|
||||||
|
|
||||||
|
#Requires -Version 3.0
|
||||||
|
if (Test-Path Env:\DEBUG) {
|
||||||
|
Set-PSDebug -Trace 1
|
||||||
|
}
|
||||||
|
$Host.UI.RawUI.WindowTitle = "WizardKit: Check Antivirus"
|
||||||
|
$Host.UI.RawUI.BackgroundColor = "black"
|
||||||
|
$Host.UI.RawUI.ForegroundColor = "white"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
# Main
|
||||||
|
Get-CimInstance -Namespace "root\SecurityCenter2" -ClassName AntivirusProduct | select displayName,productState | ConvertTo-Json
|
||||||
13
scripts/check_partition_alignment.ps1
Normal file
13
scripts/check_partition_alignment.ps1
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# WizardKit: Check Partition Alignment
|
||||||
|
|
||||||
|
#Requires -Version 3.0
|
||||||
|
if (Test-Path Env:\DEBUG) {
|
||||||
|
Set-PSDebug -Trace 1
|
||||||
|
}
|
||||||
|
$Host.UI.RawUI.WindowTitle = "WizardKit: Check Partition Alignment"
|
||||||
|
$Host.UI.RawUI.BackgroundColor = "black"
|
||||||
|
$Host.UI.RawUI.ForegroundColor = "white"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
# Main
|
||||||
|
Get-CimInstance -Query "Select * from Win32_DiskPartition" | select Name,Size,StartingOffset | ConvertTo-Json
|
||||||
|
|
@ -2,22 +2,13 @@
|
||||||
"""WizardKit: ddrescue TUI"""
|
"""WizardKit: ddrescue TUI"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
from docopt import docopt
|
|
||||||
|
|
||||||
import wk
|
import wk
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
|
||||||
docopt(wk.clone.ddrescue.DOCSTRING)
|
|
||||||
except SystemExit:
|
|
||||||
print('')
|
|
||||||
wk.std.pause('Press Enter to exit...')
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wk.clone.ddrescue.main()
|
wk.clone.ddrescue.main()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
13
scripts/disable_password_expiration.ps1
Normal file
13
scripts/disable_password_expiration.ps1
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# WizardKit: Disable Password Expiration (Local Accounts)
|
||||||
|
|
||||||
|
#Requires -Version 3.0
|
||||||
|
if (Test-Path Env:\DEBUG) {
|
||||||
|
Set-PSDebug -Trace 1
|
||||||
|
}
|
||||||
|
$Host.UI.RawUI.WindowTitle = "Disable Password Expiration"
|
||||||
|
$Host.UI.RawUI.BackgroundColor = "black"
|
||||||
|
$Host.UI.RawUI.ForegroundColor = "white"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
# Main
|
||||||
|
Get-LocalUser | Set-LocalUser -PasswordNeverExpires $true
|
||||||
|
|
@ -5,10 +5,18 @@ python.exe -i embedded_python_env.py
|
||||||
"""
|
"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import pickle
|
||||||
import wk
|
import wk
|
||||||
|
|
||||||
|
|
||||||
wk.std.print_colored(
|
# Functions
|
||||||
|
def load_state():
|
||||||
|
with open('debug/state.pickle', 'rb') as f:
|
||||||
|
return pickle.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
# Main
|
||||||
|
wk.ui.cli.print_colored(
|
||||||
(wk.cfg.main.KIT_NAME_FULL, ': ', 'Debug Console'),
|
(wk.cfg.main.KIT_NAME_FULL, ': ', 'Debug Console'),
|
||||||
('GREEN', None, 'YELLOW'),
|
('GREEN', None, 'YELLOW'),
|
||||||
sep='',
|
sep='',
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,13 @@
|
||||||
"""WizardKit: Hardware Diagnostics"""
|
"""WizardKit: Hardware Diagnostics"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
from docopt import docopt
|
|
||||||
|
|
||||||
import wk
|
import wk
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
|
||||||
docopt(wk.hw.diags.DOCSTRING)
|
|
||||||
except SystemExit:
|
|
||||||
print('')
|
|
||||||
wk.std.pause('Press Enter to exit...')
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wk.hw.diags.main()
|
wk.hw.diags.main()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,15 @@ import platform
|
||||||
import wk
|
import wk
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
"""Show sensor data on screen."""
|
"""Show sensor data on screen."""
|
||||||
sensors = wk.hw.sensors.Sensors()
|
sensors = wk.hw.sensors.Sensors()
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
wk.std.clear_screen()
|
wk.ui.cli.clear_screen()
|
||||||
while True:
|
while True:
|
||||||
print('\033[100A', end='')
|
print('\033[100A', end='')
|
||||||
sensors.update_sensor_data()
|
sensors.update_sensor_data()
|
||||||
wk.std.print_report(sensors.generate_report('Current', 'Max'))
|
wk.ui.cli.print_report(sensors.generate_report('Current', 'Max'))
|
||||||
wk.std.sleep(1)
|
wk.std.sleep(1)
|
||||||
elif platform.system() == 'Linux':
|
elif platform.system() == 'Linux':
|
||||||
proc = wk.exe.run_program(cmd=['mktemp'])
|
proc = wk.exe.run_program(cmd=['mktemp'])
|
||||||
|
|
@ -42,5 +42,5 @@ if __name__ == '__main__':
|
||||||
pass
|
pass
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: #pylint: disable=bare-except
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
37
scripts/install_winget.ps1
Normal file
37
scripts/install_winget.ps1
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# WizardKit: Install winget (if needed)
|
||||||
|
|
||||||
|
#Requires -Version 3.0
|
||||||
|
if (Test-Path Env:\DEBUG) {
|
||||||
|
Set-PSDebug -Trace 1
|
||||||
|
}
|
||||||
|
$Host.UI.RawUI.WindowTitle = "WizardKit: Winget installer"
|
||||||
|
$Host.UI.RawUI.BackgroundColor = "black"
|
||||||
|
$Host.UI.RawUI.ForegroundColor = "white"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
$EXIT_OK = 0
|
||||||
|
$EXIT_INSTALLED = 1
|
||||||
|
$EXIT_FAILED_TO_INSTALL = 2
|
||||||
|
|
||||||
|
# Main
|
||||||
|
$NeedsInstalled = $false
|
||||||
|
try {
|
||||||
|
$_ = $(winget --version)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$NeedsInstalled = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install
|
||||||
|
if (! $NeedsInstalled) {
|
||||||
|
exit $EXIT_INSTALLED
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Add-AppxPackage -ErrorAction Stop -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
exit $EXIT_FAILED_TO_INSTALL
|
||||||
|
}
|
||||||
|
|
||||||
|
exit $EXIT_OK
|
||||||
7
scripts/journal-datarec-monitor
Executable file
7
scripts/journal-datarec-monitor
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
## Monitor journal log for data recovery related events
|
||||||
|
|
||||||
|
echo -e 'Monitoring journal output...\n'
|
||||||
|
journalctl -kf \
|
||||||
|
| grep -Ei --color=always 'ata|nvme|scsi|sd[a..z]+|usb|comreset|critical|error'
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
"""WizardKit: Launch Snappy Driver Installer Origin"""
|
"""WizardKit: Launch Snappy Driver Installer Origin"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
from subprocess import CompletedProcess
|
||||||
|
|
||||||
import wk
|
import wk
|
||||||
from wk.cfg.net import SDIO_SERVER
|
from wk.cfg.net import SDIO_SERVER
|
||||||
|
|
||||||
|
|
@ -20,19 +22,19 @@ SDIO_REMOTE_PATH = wk.io.get_path_obj(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def try_again():
|
def try_again() -> bool:
|
||||||
"""Ask to try again or quit."""
|
"""Ask to try again or quit."""
|
||||||
if wk.std.ask(' Try again?'):
|
if wk.ui.cli.ask(' Try again?'):
|
||||||
return True
|
return True
|
||||||
if not wk.std.ask(' Use local version?'):
|
if not wk.ui.cli.ask(' Use local version?'):
|
||||||
wk.std.abort()
|
wk.ui.cli.abort()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def use_network_sdio():
|
def use_network_sdio() -> bool:
|
||||||
"""Try to mount SDIO server."""
|
"""Try to mount SDIO server."""
|
||||||
use_network = False
|
use_network = False
|
||||||
def _mount_server():
|
def _mount_server() -> CompletedProcess:
|
||||||
print('Connecting to server... (Press CTRL+c to use local copy)')
|
print('Connecting to server... (Press CTRL+c to use local copy)')
|
||||||
return wk.net.mount_network_share(SDIO_SERVER, read_write=False)
|
return wk.net.mount_network_share(SDIO_SERVER, read_write=False)
|
||||||
|
|
||||||
|
|
@ -47,7 +49,7 @@ def use_network_sdio():
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
break
|
break
|
||||||
except MOUNT_EXCEPTIONS as err:
|
except MOUNT_EXCEPTIONS as err:
|
||||||
wk.std.print_error(f' {err}')
|
wk.ui.cli.print_error(f' {err}')
|
||||||
if not try_again():
|
if not try_again():
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
|
@ -57,7 +59,7 @@ def use_network_sdio():
|
||||||
break
|
break
|
||||||
|
|
||||||
# Failed to mount
|
# Failed to mount
|
||||||
wk.std.print_error(' Failed to mount server')
|
wk.ui.cli.print_error(' Failed to mount server')
|
||||||
if not try_again():
|
if not try_again():
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -66,17 +68,25 @@ def use_network_sdio():
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
wk.std.set_title(
|
wk.ui.cli.set_title(
|
||||||
f'{wk.cfg.main.KIT_NAME_FULL}: Snappy Driver Installer Origin Launcher',
|
f'{wk.cfg.main.KIT_NAME_FULL}: Snappy Driver Installer Origin Launcher',
|
||||||
)
|
)
|
||||||
log_dir = wk.log.format_log_path(tool=True).parent
|
log_dir = wk.log.format_log_path(tool=True).parent
|
||||||
USE_NETWORK = False
|
USE_NETWORK = False
|
||||||
|
|
||||||
|
# Windows 11 workaround
|
||||||
|
if wk.os.win.OS_VERSION == 11:
|
||||||
|
appid_services = ['appid', 'appidsvc', 'applockerfltr']
|
||||||
|
for svc in appid_services:
|
||||||
|
wk.os.win.stop_service(svc)
|
||||||
|
if any([wk.os.win.get_service_status(s) != 'stopped' for s in appid_services]):
|
||||||
|
raise wk.std.GenericWarning('Failed to stop AppID services')
|
||||||
|
|
||||||
# Try to mount server
|
# Try to mount server
|
||||||
try:
|
try:
|
||||||
USE_NETWORK = use_network_sdio()
|
USE_NETWORK = use_network_sdio()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
wk.std.abort()
|
wk.ui.cli.abort()
|
||||||
|
|
||||||
# Run SDIO
|
# Run SDIO
|
||||||
EXE_PATH = SDIO_LOCAL_PATH
|
EXE_PATH = SDIO_LOCAL_PATH
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import json
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
CPU_REGEX = re.compile(r'(core|k\d+)temp', re.IGNORECASE)
|
CPU_REGEX = re.compile(r'(core|k\d+)temp', re.IGNORECASE)
|
||||||
NON_TEMP_REGEX = re.compile(r'^(fan|in|curr)', re.IGNORECASE)
|
NON_TEMP_REGEX = re.compile(r'^(fan|in|curr)', re.IGNORECASE)
|
||||||
|
|
||||||
def get_data():
|
def get_data() -> dict[Any, Any]:
|
||||||
cmd = ('sensors', '-j')
|
cmd = ('sensors', '-j')
|
||||||
data = {}
|
data = {}
|
||||||
raw_data = []
|
raw_data = []
|
||||||
|
|
@ -38,7 +40,7 @@ def get_data():
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_max_temp(data):
|
def get_max_temp(data) -> str:
|
||||||
cpu_temps = []
|
cpu_temps = []
|
||||||
max_cpu_temp = '??° C'
|
max_cpu_temp = '??° C'
|
||||||
for adapter, sources in data.items():
|
for adapter, sources in data.items():
|
||||||
|
|
|
||||||
|
|
@ -8,34 +8,34 @@ import wk
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def main():
|
def main() -> None:
|
||||||
"""Mount all volumes and show results."""
|
"""Mount all volumes and show results."""
|
||||||
wk.std.print_standard(f'{wk.cfg.main.KIT_NAME_FULL}: Volume mount tool')
|
wk.ui.cli.print_standard(f'{wk.cfg.main.KIT_NAME_FULL}: Volume mount tool')
|
||||||
wk.std.print_standard(' ')
|
wk.ui.cli.print_standard(' ')
|
||||||
|
|
||||||
# Mount volumes and get report
|
# Mount volumes and get report
|
||||||
wk.std.print_standard('Mounting volumes...')
|
wk.ui.cli.print_standard('Mounting volumes...')
|
||||||
wk.os.linux.mount_volumes()
|
wk.os.linux.mount_volumes()
|
||||||
report = wk.os.linux.build_volume_report()
|
report = wk.os.linux.build_volume_report()
|
||||||
|
|
||||||
# Show results
|
# Show results
|
||||||
wk.std.print_info('Results')
|
wk.ui.cli.print_info('Results')
|
||||||
wk.std.print_report(report)
|
wk.ui.cli.print_report(report)
|
||||||
|
|
||||||
# GUI mode
|
# GUI mode
|
||||||
if 'gui' in sys.argv:
|
if 'gui' in sys.argv:
|
||||||
wk.std.pause('Press Enter to exit...')
|
wk.ui.cli.pause('Press Enter to exit...')
|
||||||
wk.exe.popen_program(['nohup', 'thunar', '/media'])
|
wk.exe.popen_program(['nohup', 'thunar', '/media'])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if wk.std.PLATFORM != 'Linux':
|
if wk.std.PLATFORM != 'Linux':
|
||||||
os_name = wk.std.PLATFORM.replace('Darwin', 'macOS')
|
os_name = wk.std.PLATFORM.replace('Darwin', 'macOS')
|
||||||
wk.std.print_error(f'This script is not supported under {os_name}.')
|
wk.ui.cli.print_error(f'This script is not supported under {os_name}.')
|
||||||
wk.std.abort()
|
wk.ui.cli.abort()
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: #pylint: disable=bare-except
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import wk
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def main():
|
def main() -> None:
|
||||||
"""Attempt to mount backup shares and print report."""
|
"""Attempt to mount backup shares and print report."""
|
||||||
wk.std.print_info('Mounting Backup Shares')
|
wk.ui.cli.print_info('Mounting Backup Shares')
|
||||||
report = wk.net.mount_backup_shares()
|
report = wk.net.mount_backup_shares()
|
||||||
for line in report:
|
for line in report:
|
||||||
color = 'GREEN'
|
color = 'GREEN'
|
||||||
|
|
@ -17,7 +17,7 @@ def main():
|
||||||
color = 'RED'
|
color = 'RED'
|
||||||
elif 'Already' in line:
|
elif 'Already' in line:
|
||||||
color = 'YELLOW'
|
color = 'YELLOW'
|
||||||
print(wk.std.color_string(line, color))
|
print(wk.ansi.color_string(line, color))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
@ -25,5 +25,5 @@ if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: #pylint: disable=bare-except
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import wk
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
SCANDIR = os.getcwd()
|
SCANDIR = os.getcwd()
|
||||||
USAGE = '''Usage: {script} <search-terms>...
|
USAGE = '''Usage: {script} <search-terms>...
|
||||||
|
|
@ -13,12 +15,6 @@ USAGE = '''Usage: {script} <search-terms>...
|
||||||
|
|
||||||
This script will search all doc/docx files below the current directory for
|
This script will search all doc/docx files below the current directory for
|
||||||
the search-terms provided (case-insensitive).'''.format(script=__file__)
|
the search-terms provided (case-insensitive).'''.format(script=__file__)
|
||||||
|
|
||||||
# Init
|
|
||||||
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
from functions.network import *
|
|
||||||
init_global_vars()
|
|
||||||
|
|
||||||
REGEX_DOC_FILES = re.compile(r'\.docx?$', re.IGNORECASE)
|
REGEX_DOC_FILES = re.compile(r'\.docx?$', re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -34,10 +30,10 @@ def scan_file(file_path, search):
|
||||||
match = False
|
match = False
|
||||||
try:
|
try:
|
||||||
if entry.name.lower().endswith('.docx'):
|
if entry.name.lower().endswith('.docx'):
|
||||||
result = run_program(['unzip', '-p', entry.path])
|
result = wk.exe.run_program(['unzip', '-p', entry.path])
|
||||||
else:
|
else:
|
||||||
# Assuming .doc
|
# Assuming .doc
|
||||||
result = run_program(['antiword', entry.path])
|
result = wk.exe.run_program(['antiword', entry.path])
|
||||||
out = result.stdout.decode()
|
out = result.stdout.decode()
|
||||||
match = re.search(search, out, re.IGNORECASE)
|
match = re.search(search, out, re.IGNORECASE)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -50,13 +46,13 @@ def scan_file(file_path, search):
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
# Prep
|
# Prep
|
||||||
clear_screen()
|
wk.ui.cli.clear_screen()
|
||||||
terms = [re.sub(r'\s+', r'\s*', t) for t in sys.argv[1:]]
|
terms = [re.sub(r'\s+', r'\s*', t) for t in sys.argv[1:]]
|
||||||
search = '({})'.format('|'.join(terms))
|
search = '({})'.format('|'.join(terms))
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
# Print usage
|
# Print usage
|
||||||
print_standard(USAGE)
|
wk.ui.cli.print_standard(USAGE)
|
||||||
else:
|
else:
|
||||||
matches = []
|
matches = []
|
||||||
for entry in scan_for_docs(SCANDIR):
|
for entry in scan_for_docs(SCANDIR):
|
||||||
|
|
@ -64,21 +60,20 @@ if __name__ == '__main__':
|
||||||
# Strip None values (i.e. non-matching entries)
|
# Strip None values (i.e. non-matching entries)
|
||||||
matches = [m for m in matches if m]
|
matches = [m for m in matches if m]
|
||||||
if matches:
|
if matches:
|
||||||
print_success('Found {} {}:'.format(
|
wk.ui.cli.print_success('Found {} {}:'.format(
|
||||||
len(matches),
|
len(matches),
|
||||||
'Matches' if len(matches) > 1 else 'Match'))
|
'Matches' if len(matches) > 1 else 'Match'))
|
||||||
for match in matches:
|
for match in matches:
|
||||||
print_standard(match)
|
wk.ui.cli.print_standard(match)
|
||||||
else:
|
else:
|
||||||
print_error('No matches found.')
|
wk.ui.cli.print_error('No matches found.')
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
print_standard('\nDone.')
|
wk.ui.cli.print_standard('\nDone.')
|
||||||
#pause("Press Enter to exit...")
|
#pause("Press Enter to exit...")
|
||||||
exit_script()
|
except SystemExit:
|
||||||
except SystemExit as sys_exit:
|
raise
|
||||||
exit_script(sys_exit.code)
|
except: # noqa: E722
|
||||||
except:
|
wk.ui.cli.major_exception()
|
||||||
major_exception()
|
|
||||||
|
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
## sort photorec results into something usefull
|
|
||||||
|
|
||||||
## Set paths
|
|
||||||
recup_dir="${1%/}"
|
|
||||||
[ -n "$recup_dir" ] || recup_dir="."
|
|
||||||
recup_dir="$(realpath "$recup_dir")"
|
|
||||||
out_dir="$recup_dir/Recovered"
|
|
||||||
bad_dir="$recup_dir/Corrupt"
|
|
||||||
|
|
||||||
## Test path before starting (using current dir if not specified)
|
|
||||||
for d in $recup_dir/recup*; do
|
|
||||||
### Source: http://stackoverflow.com/a/6364244
|
|
||||||
## Check if the glob gets expanded to existing files.
|
|
||||||
## If not, f here will be exactly the pattern above
|
|
||||||
## and the exists test will evaluate to false.
|
|
||||||
[ -e "$d" ] && echo "Found recup folder(s)" || {
|
|
||||||
echo "ERROR: No recup folders found"
|
|
||||||
echo "Usage: $0 recup_dir"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
## This is all we needed to know, so we can break after the first iteration
|
|
||||||
break
|
|
||||||
done
|
|
||||||
|
|
||||||
# Hard link files into folders by type
|
|
||||||
for d in $recup_dir/recup*; do
|
|
||||||
if [ -d "$d" ]; then
|
|
||||||
echo "Linking $d"
|
|
||||||
pushd $d >/dev/null
|
|
||||||
find -type f | while read k; do
|
|
||||||
file="$(basename "$k")"
|
|
||||||
src="$(realpath "$k")"
|
|
||||||
ext="$(echo "${file##*.}" | tr '[:upper:]' '[:lower:]')"
|
|
||||||
ext_dir="$out_dir/$ext"
|
|
||||||
if [ "${file##*.}" = "$file" ]; then
|
|
||||||
ext_dir="$out_dir/_MISC_"
|
|
||||||
elif [ "$ext" = "jpg" ] && [ "${file:0:1}" = "t" ]; then
|
|
||||||
ext_dir="$out_dir/jpg-thumbnail"
|
|
||||||
fi
|
|
||||||
#echo " $file -> $ext_dir"
|
|
||||||
[ -d "$ext_dir" ] || mkdir -p "$ext_dir"
|
|
||||||
ln "$src" "$ext_dir"
|
|
||||||
done
|
|
||||||
popd >/dev/null
|
|
||||||
else
|
|
||||||
echo "ERROR: '$d' not a directory"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
## Check the files output by photorec for corruption
|
|
||||||
pushd "$out_dir" >/dev/null
|
|
||||||
|
|
||||||
# Check archives with 7-Zip
|
|
||||||
#for d in 7z bz2 gz lzh lzo rar tar xz zip; do
|
|
||||||
# if [ -d "$d" ]; then
|
|
||||||
# echo "Checking $d files"
|
|
||||||
# pushd "$d" >/dev/null
|
|
||||||
# for f in *; do
|
|
||||||
# if ! 7z t "$f" >/dev/null 2>&1; then
|
|
||||||
# #echo " BAD: $f"
|
|
||||||
# [ -d "$bad_dir/$d" ] || mkdir -p "$bad_dir/$d"
|
|
||||||
# mv -n "$f" "$bad_dir/$d/$f"
|
|
||||||
# fi
|
|
||||||
# done
|
|
||||||
# popd >/dev/null
|
|
||||||
# fi
|
|
||||||
#done
|
|
||||||
|
|
||||||
# Check Audio/Video files with ffprobe
|
|
||||||
for d in avi flac flv m4a m4p m4v mkv mid mov mp2 mp3 mp4 mpg mpg2 ogg ts vob wav; do
|
|
||||||
if [ -d "$d" ]; then
|
|
||||||
echo "Checking $d files"
|
|
||||||
pushd "$d" >/dev/null
|
|
||||||
for f in *; do
|
|
||||||
if ! ffprobe "$f" >/dev/null 2>&1; then
|
|
||||||
#echo " BAD: $f"
|
|
||||||
[ -d "$bad_dir/$d" ] || mkdir -p "$bad_dir/$d"
|
|
||||||
mv -n "$f" "$bad_dir/$d/$f"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
popd >/dev/null
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check .doc files with antiword
|
|
||||||
if [ -d "doc" ]; then
|
|
||||||
echo "Checking doc files"
|
|
||||||
pushd "doc" >/dev/null
|
|
||||||
for f in *doc; do
|
|
||||||
if ! antiword "$f" >/dev/null 2>&1; then
|
|
||||||
#echo " BAD: $f"
|
|
||||||
[ -d "$bad_dir/doc" ] || mkdir -p "$bad_dir/doc"
|
|
||||||
mv -n "$f" "$bad_dir/doc/$f"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
popd >/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check .docx files with 7z and grep
|
|
||||||
if [ -d "docx" ]; then
|
|
||||||
echo "Checking docx files"
|
|
||||||
pushd "docx" >/dev/null
|
|
||||||
for f in *docx; do
|
|
||||||
if ! 7z l "$f" | grep -q -s "word/document.xml"; then
|
|
||||||
#echo " BAD: $f"
|
|
||||||
[ -d "$bad_dir/docx" ] || mkdir -p "$bad_dir/docx"
|
|
||||||
mv -n "$f" "$bad_dir/docx/$f"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
popd >/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Sort pictures by date (only for common camera formats)
|
|
||||||
for d in jpg mrw orf raf raw rw2 tif x3f; do
|
|
||||||
if [ -d "$d" ]; then
|
|
||||||
echo "Sorting $d files by date"
|
|
||||||
pushd "$d" >/dev/null
|
|
||||||
for f in *; do
|
|
||||||
date_dir="$(date -d "$(stat -c %y "$f")" +"%F")"
|
|
||||||
[ -d "$date_dir" ] || mkdir "$date_dir"
|
|
||||||
mv -n "$f" "$date_dir/"
|
|
||||||
done
|
|
||||||
popd >/dev/null
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Sort mov files by encoded date
|
|
||||||
if [ -d "mov" ]; then
|
|
||||||
echo "Sorting mov files by date"
|
|
||||||
pushd "mov" >/dev/null
|
|
||||||
for f in *mov; do
|
|
||||||
enc_date="$(mediainfo "$f" | grep -i "Encoded date" | head -1 | sed -r 's/.*: //')"
|
|
||||||
date_dir="$(date -d "$enc_date" +"%F")"
|
|
||||||
echo "$date_dir" | grep -E -q -s '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' || date_dir="Unknown Date"
|
|
||||||
[ -d "$date_dir" ] || mkdir "$date_dir"
|
|
||||||
mv -n "$f" "$date_dir/"
|
|
||||||
done
|
|
||||||
popd >/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
## sort audio files by tags
|
|
||||||
|
|
||||||
## sort matroska files by metadata
|
|
||||||
|
|
||||||
## return to original dir
|
|
||||||
popd >/dev/null
|
|
||||||
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"wk/os/__init__.py" = ["F401"]
|
"wk/os/__init__.py" = ["F401"]
|
||||||
"wk/repairs/__init__.py" = ["F401"]
|
"wk/repairs/__init__.py" = ["F401"]
|
||||||
"wk/setup/__init__.py" = ["F401"]
|
"wk/setup/__init__.py" = ["F401"]
|
||||||
|
"wk/ui/__init__.py" = ["F401"]
|
||||||
|
|
||||||
# Long lines
|
# Long lines
|
||||||
"wk/borrowed/acpi.py" = ["E501", "F841"]
|
"wk/borrowed/acpi.py" = ["E501", "F841"]
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,21 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""WizardKit: Unmount Backup Shares"""
|
"""WizardKit: Unmount Backup Shares"""
|
||||||
# pylint: disable=invalid-name
|
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
import wk
|
import wk
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def main():
|
def main() -> None:
|
||||||
"""Attempt to mount backup shares and print report."""
|
"""Attempt to mount backup shares and print report."""
|
||||||
wk.std.print_info('Unmounting Backup Shares')
|
wk.ui.cli.print_info('Unmounting Backup Shares')
|
||||||
report = wk.net.unmount_backup_shares()
|
report = wk.net.unmount_backup_shares()
|
||||||
for line in report:
|
for line in report:
|
||||||
color = 'GREEN'
|
color = 'GREEN'
|
||||||
line = f' {line}'
|
line = f' {line}'
|
||||||
if 'Not mounted' in line:
|
if 'Not mounted' in line:
|
||||||
color = 'YELLOW'
|
color = 'YELLOW'
|
||||||
print(wk.std.color_string(line, color))
|
print(wk.ui.ansi.color_string(line, color))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
@ -24,5 +23,5 @@ if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except: #pylint: disable=bare-except
|
except: # noqa: E722
|
||||||
wk.std.major_exception()
|
wk.ui.cli.major_exception()
|
||||||
|
|
|
||||||
|
|
@ -25,24 +25,24 @@ if PLATFORM not in ('macOS', 'Linux'):
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def main():
|
def main() -> None:
|
||||||
"""Upload logs for review."""
|
"""Upload logs for review."""
|
||||||
lines = []
|
lines = []
|
||||||
try_and_print = wk.std.TryAndPrint()
|
try_and_print = wk.ui.cli.TryAndPrint()
|
||||||
|
|
||||||
# Set log
|
# Set log
|
||||||
wk.log.update_log_path(dest_name='Upload-Logs', timestamp=True)
|
wk.log.update_log_path(dest_name='Upload-Logs', timestamp=True)
|
||||||
|
|
||||||
# Instructions
|
# Instructions
|
||||||
wk.std.print_success(f'{wk.cfg.main.KIT_NAME_FULL}: Upload Logs')
|
wk.ui.cli.print_success(f'{wk.cfg.main.KIT_NAME_FULL}: Upload Logs')
|
||||||
wk.std.print_standard('')
|
wk.ui.cli.print_standard('')
|
||||||
wk.std.print_standard('Please state the reason for the review.')
|
wk.ui.cli.print_standard('Please state the reason for the review.')
|
||||||
wk.std.print_info(' End note with an empty line.')
|
wk.ui.cli.print_info(' End note with an empty line.')
|
||||||
wk.std.print_standard('')
|
wk.ui.cli.print_standard('')
|
||||||
|
|
||||||
# Get reason note
|
# Get reason note
|
||||||
while True:
|
while True:
|
||||||
text = wk.std.input_text('> ')
|
text = wk.ui.cli.input_text('> ')
|
||||||
if not text:
|
if not text:
|
||||||
lines.append('')
|
lines.append('')
|
||||||
break
|
break
|
||||||
|
|
@ -60,17 +60,16 @@ def main():
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
def upload_log_dir(reason='Testing'):
|
def upload_log_dir(reason='Testing') -> None:
|
||||||
"""Upload compressed log_dir to the crash server."""
|
"""Upload compressed log_dir to the crash server."""
|
||||||
server = wk.cfg.net.CRASH_SERVER
|
server = wk.cfg.net.CRASH_SERVER
|
||||||
dest = pathlib.Path(f'~/{reason}_{NOW.strftime("%Y-%m-%dT%H%M%S%z")}.txz')
|
dest = pathlib.Path(f'~/{reason}_{NOW.strftime("%Y-%m-%dT%H%M%S%z")}.txz')
|
||||||
dest = dest.expanduser().resolve()
|
dest = dest.expanduser().resolve()
|
||||||
data = None
|
|
||||||
|
|
||||||
# Compress LOG_DIR (relative to parent dir)
|
# Compress LOG_DIR (relative to parent dir)
|
||||||
os.chdir(LOG_DIR.parent)
|
os.chdir(LOG_DIR.parent)
|
||||||
cmd = ['tar', 'caf', dest.name, LOG_DIR.name]
|
cmd = ['tar', 'caf', dest.name, LOG_DIR.name]
|
||||||
proc = wk.exe.run_program(cmd, check=False)
|
wk.exe.run_program(cmd, check=False)
|
||||||
|
|
||||||
# Upload compressed data
|
# Upload compressed data
|
||||||
url = f'{server["Url"]}/{dest.name}'
|
url = f'{server["Url"]}/{dest.name}'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""WizardKit: wk module init"""
|
"""WizardKit: wk module init"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
from sys import version_info as version
|
from sys import stderr, version_info
|
||||||
|
|
||||||
from . import cfg
|
from . import cfg
|
||||||
from . import clone
|
from . import clone
|
||||||
|
|
@ -17,21 +17,22 @@ from . import os
|
||||||
from . import repairs
|
from . import repairs
|
||||||
from . import setup
|
from . import setup
|
||||||
from . import std
|
from . import std
|
||||||
from . import tmux
|
from . import ui
|
||||||
|
|
||||||
|
|
||||||
# Check env
|
# Check env
|
||||||
if version < (3, 7):
|
if version_info < (3, 10):
|
||||||
# Unsupported
|
# Unsupported
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'This package is unsupported on Python {version.major}.{version.minor}'
|
'This package is unsupported on Python '
|
||||||
|
f'{version_info.major}.{version_info.minor}'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Init
|
# Init
|
||||||
try:
|
try:
|
||||||
log.start()
|
log.start()
|
||||||
except UserWarning as err:
|
except UserWarning as err:
|
||||||
std.print_warning(err)
|
print(err, file=stderr)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from . import log
|
||||||
from . import main
|
from . import main
|
||||||
from . import music
|
from . import music
|
||||||
from . import net
|
from . import net
|
||||||
from . import python
|
|
||||||
from . import repairs
|
from . import repairs
|
||||||
from . import setup
|
from . import setup
|
||||||
from . import sources
|
from . import sources
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
"""WizardKit: Config - ddrescue"""
|
"""WizardKit: Config - ddrescue"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
# Layout
|
# Layout
|
||||||
TMUX_SIDE_WIDTH = 21
|
TMUX_SIDE_WIDTH = 21
|
||||||
TMUX_LAYOUT = OrderedDict({
|
TMUX_LAYOUT = {
|
||||||
'Source': {'height': 2, 'Check': True},
|
'Source': {'height': 2, 'Check': True},
|
||||||
'Started': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
'Started': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
||||||
'Progress': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
'Progress': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
||||||
})
|
}
|
||||||
|
|
||||||
# ddrescue
|
# ddrescue
|
||||||
AUTO_PASS_THRESHOLDS = {
|
AUTO_PASS_THRESHOLDS = {
|
||||||
|
|
@ -39,7 +37,7 @@ DDRESCUE_SETTINGS = {
|
||||||
'--retry-passes': {'Selected': True, 'Value': '0', },
|
'--retry-passes': {'Selected': True, 'Value': '0', },
|
||||||
'--reverse': {'Selected': False, },
|
'--reverse': {'Selected': False, },
|
||||||
'--skip-size': {'Selected': True, 'Value': '0.001,0.02', }, # Percentages of source size
|
'--skip-size': {'Selected': True, 'Value': '0.001,0.02', }, # Percentages of source size
|
||||||
'--test-mode': {'Selected': False, 'Value': 'test.map', },
|
'--test-mode': {'Selected': False, },
|
||||||
'--timeout': {'Selected': True, 'Value': '30m', },
|
'--timeout': {'Selected': True, 'Value': '30m', },
|
||||||
'-vvvv': {'Selected': True, 'Hidden': True, },
|
'-vvvv': {'Selected': True, 'Hidden': True, },
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
ATTRIBUTE_COLORS = (
|
ATTRIBUTE_COLORS = (
|
||||||
|
|
@ -22,8 +20,13 @@ BADBLOCKS_REGEX = re.compile(
|
||||||
)
|
)
|
||||||
BADBLOCKS_RESULTS_REGEX = re.compile(r'^(.*?)\x08.*\x08(.*)')
|
BADBLOCKS_RESULTS_REGEX = re.compile(r'^(.*?)\x08.*\x08(.*)')
|
||||||
BADBLOCKS_SKIP_REGEX = re.compile(r'^(Checking|\[)', re.IGNORECASE)
|
BADBLOCKS_SKIP_REGEX = re.compile(r'^(Checking|\[)', re.IGNORECASE)
|
||||||
CPU_CRITICAL_TEMP = 99
|
CPU_TEMPS = {
|
||||||
CPU_FAILURE_TEMP = 90
|
'Cooling Delta': 25,
|
||||||
|
'Cooling Low Cutoff': 50,
|
||||||
|
'Critical': 100,
|
||||||
|
'Idle Delta': 25,
|
||||||
|
'Idle High': 70,
|
||||||
|
}
|
||||||
CPU_TEST_MINUTES = 7
|
CPU_TEST_MINUTES = 7
|
||||||
IO_GRAPH_WIDTH = 40
|
IO_GRAPH_WIDTH = 40
|
||||||
IO_ALT_TEST_SIZE_FACTOR = 0.01
|
IO_ALT_TEST_SIZE_FACTOR = 0.01
|
||||||
|
|
@ -161,18 +164,6 @@ THRESH_HDD_AVG_LOW = 65 * 1024**2
|
||||||
THRESH_SSD_MIN = 90 * 1024**2
|
THRESH_SSD_MIN = 90 * 1024**2
|
||||||
THRESH_SSD_AVG_HIGH = 135 * 1024**2
|
THRESH_SSD_AVG_HIGH = 135 * 1024**2
|
||||||
THRESH_SSD_AVG_LOW = 100 * 1024**2
|
THRESH_SSD_AVG_LOW = 100 * 1024**2
|
||||||
TMUX_SIDE_WIDTH = 20
|
|
||||||
TMUX_LAYOUT = OrderedDict({
|
|
||||||
'Top': {'height': 2, 'Check': True},
|
|
||||||
'Started': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
|
||||||
'Progress': {'width': TMUX_SIDE_WIDTH, 'Check': True},
|
|
||||||
# Testing panes
|
|
||||||
'Temps': {'height': 1000, 'Check': False},
|
|
||||||
'Prime95': {'height': 11, 'Check': False},
|
|
||||||
'SMART': {'height': 4, 'Check': True},
|
|
||||||
'badblocks': {'height': 5, 'Check': True},
|
|
||||||
'I/O Benchmark': {'height': 1000, 'Check': False},
|
|
||||||
})
|
|
||||||
# VOLUME THRESHOLDS in percent
|
# VOLUME THRESHOLDS in percent
|
||||||
VOLUME_WARNING_THRESHOLD = 70
|
VOLUME_WARNING_THRESHOLD = 70
|
||||||
VOLUME_FAILURE_THRESHOLD = 85
|
VOLUME_FAILURE_THRESHOLD = 85
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,12 @@ LAUNCHERS = {
|
||||||
'L_ITEM': 'auto_repairs.py',
|
'L_ITEM': 'auto_repairs.py',
|
||||||
'L_ELEV': 'True',
|
'L_ELEV': 'True',
|
||||||
},
|
},
|
||||||
'2) Windows Updates': {
|
'2) Store & Windows Updates': {
|
||||||
'L_TYPE': 'Executable',
|
'L_TYPE': 'Executable',
|
||||||
'L_PATH': r'%SystemRoot%\System32',
|
'L_PATH': r'%SystemRoot%\System32',
|
||||||
'L_ITEM': 'control.exe',
|
'L_ITEM': 'control.exe',
|
||||||
'L_ARGS': 'update',
|
'L_ARGS': 'update',
|
||||||
|
'Extra Code': ['explorer ms-windows-store:updates'],
|
||||||
},
|
},
|
||||||
'3) Snappy Driver Installer Origin': {
|
'3) Snappy Driver Installer Origin': {
|
||||||
'L_TYPE': 'PyScript',
|
'L_TYPE': 'PyScript',
|
||||||
|
|
@ -68,6 +69,12 @@ LAUNCHERS = {
|
||||||
'L_PATH': 'BlueScreenView',
|
'L_PATH': 'BlueScreenView',
|
||||||
'L_ITEM': 'BlueScreenView.exe',
|
'L_ITEM': 'BlueScreenView.exe',
|
||||||
},
|
},
|
||||||
|
'BCUninstaller': {
|
||||||
|
'L_TYPE': 'Executable',
|
||||||
|
'L_PATH': 'BCUninstaller',
|
||||||
|
'L_ITEM': 'BCUninstaller.exe',
|
||||||
|
'L_ELEV': 'True',
|
||||||
|
},
|
||||||
'ConEmu (as ADMIN)': {
|
'ConEmu (as ADMIN)': {
|
||||||
'L_TYPE': 'Executable',
|
'L_TYPE': 'Executable',
|
||||||
'L_PATH': 'ConEmu',
|
'L_PATH': 'ConEmu',
|
||||||
|
|
@ -97,6 +104,18 @@ LAUNCHERS = {
|
||||||
'if /i "%PROCESSOR_ARCHITECTURE%" == "AMD64" set "ARCH=64"',
|
'if /i "%PROCESSOR_ARCHITECTURE%" == "AMD64" set "ARCH=64"',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
'Device Cleanup': {
|
||||||
|
'L_TYPE': 'Executable',
|
||||||
|
'L_PATH': 'DeviceCleanup',
|
||||||
|
'L_ITEM': 'DeviceCleanup.exe',
|
||||||
|
'L_ELEV': 'True',
|
||||||
|
},
|
||||||
|
'Display Driver Uninstaller': {
|
||||||
|
'L_TYPE': 'Executable',
|
||||||
|
'L_PATH': 'DDU',
|
||||||
|
'L_ITEM': 'Display Driver Uninstaller.exe',
|
||||||
|
'L_ELEV': 'True',
|
||||||
|
},
|
||||||
'ERUNT': {
|
'ERUNT': {
|
||||||
'L_TYPE': 'Executable',
|
'L_TYPE': 'Executable',
|
||||||
'L_PATH': 'erunt',
|
'L_PATH': 'erunt',
|
||||||
|
|
@ -248,12 +267,6 @@ LAUNCHERS = {
|
||||||
'L_PATH': 'PuTTY',
|
'L_PATH': 'PuTTY',
|
||||||
'L_ITEM': 'PUTTY.EXE',
|
'L_ITEM': 'PUTTY.EXE',
|
||||||
},
|
},
|
||||||
'UninstallView': {
|
|
||||||
'L_TYPE': 'Executable',
|
|
||||||
'L_PATH': 'UninstallView',
|
|
||||||
'L_ITEM': 'UninstallView.exe',
|
|
||||||
'L_ELEV': 'True',
|
|
||||||
},
|
|
||||||
'WizTree': {
|
'WizTree': {
|
||||||
'L_TYPE': 'Executable',
|
'L_TYPE': 'Executable',
|
||||||
'L_PATH': 'WizTree',
|
'L_PATH': 'WizTree',
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
"""WizardKit: Config - Python"""
|
|
||||||
# vim: sts=2 sw=2 ts=2
|
|
||||||
|
|
||||||
from sys import version_info
|
|
||||||
|
|
||||||
DATACLASS_DECORATOR_KWARGS = {}
|
|
||||||
if version_info.major >= 3 and version_info.minor >= 10:
|
|
||||||
DATACLASS_DECORATOR_KWARGS['slots'] = True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print("This file is not meant to be called directly.")
|
|
||||||
|
|
||||||
# vim: sts=2 sw=2 ts=2
|
|
||||||
|
|
@ -29,8 +29,20 @@ REG_CHROME_UBLOCK_ORIGIN = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
REG_WINDOWS_BSOD_MINIDUMPS = {
|
||||||
|
'HKLM': {
|
||||||
|
# Enable small memory dumps
|
||||||
|
r'SYSTEM\CurrentControlSet\Control\CrashControl': (
|
||||||
|
('CrashDumpEnabled', 3, 'DWORD'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
REG_WINDOWS_EXPLORER = {
|
REG_WINDOWS_EXPLORER = {
|
||||||
'HKLM': {
|
'HKLM': {
|
||||||
|
# Allow password sign-in for MS accounts
|
||||||
|
r'Software\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device': (
|
||||||
|
('DevicePasswordLessBuildVersion', 0, 'DWORD'),
|
||||||
|
),
|
||||||
# Disable Location Tracking
|
# Disable Location Tracking
|
||||||
r'Software\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}': (
|
r'Software\Microsoft\Windows NT\CurrentVersion\Sensor\Overrides\{BFA794E4-F964-4FDB-90F6-51056BFE4B44}': (
|
||||||
('SensorPermissionState', 0, 'DWORD'),
|
('SensorPermissionState', 0, 'DWORD'),
|
||||||
|
|
@ -46,6 +58,10 @@ REG_WINDOWS_EXPLORER = {
|
||||||
r'Software\Policies\Microsoft\Windows\DataCollection': (
|
r'Software\Policies\Microsoft\Windows\DataCollection': (
|
||||||
('AllowTelemetry', 0, 'DWORD'),
|
('AllowTelemetry', 0, 'DWORD'),
|
||||||
),
|
),
|
||||||
|
# Disable floating Bing search widget
|
||||||
|
r'Software\Policies\Microsoft\Edge': (
|
||||||
|
('WebWidgetAllowed', 0, 'DWORD'),
|
||||||
|
),
|
||||||
# Disable Edge first run screen
|
# Disable Edge first run screen
|
||||||
r'Software\Policies\Microsoft\MicrosoftEdge\Main': (
|
r'Software\Policies\Microsoft\MicrosoftEdge\Main': (
|
||||||
('PreventFirstRunPage', 1, 'DWORD'),
|
('PreventFirstRunPage', 1, 'DWORD'),
|
||||||
|
|
@ -109,6 +125,7 @@ REG_OPEN_SHELL_SETTINGS = {
|
||||||
('ShowedStyle2', 1, 'DWORD'),
|
('ShowedStyle2', 1, 'DWORD'),
|
||||||
),
|
),
|
||||||
r'Software\OpenShell\StartMenu\Settings': (
|
r'Software\OpenShell\StartMenu\Settings': (
|
||||||
|
('HighlightNew', 0, 'DWORD'),
|
||||||
('MenuStyle', 'Win7', 'SZ'),
|
('MenuStyle', 'Win7', 'SZ'),
|
||||||
('RecentPrograms', 'Recent', 'SZ'),
|
('RecentPrograms', 'Recent', 'SZ'),
|
||||||
('SkinW7', 'Fluent-Metro', 'SZ'),
|
('SkinW7', 'Fluent-Metro', 'SZ'),
|
||||||
|
|
|
||||||
|
|
@ -22,49 +22,39 @@ SOURCES = {
|
||||||
'RKill': 'https://download.bleepingcomputer.com/grinler/rkill.exe',
|
'RKill': 'https://download.bleepingcomputer.com/grinler/rkill.exe',
|
||||||
'RegDelNull': 'https://live.sysinternals.com/RegDelNull.exe',
|
'RegDelNull': 'https://live.sysinternals.com/RegDelNull.exe',
|
||||||
'RegDelNull64': 'https://live.sysinternals.com/RegDelNull64.exe',
|
'RegDelNull64': 'https://live.sysinternals.com/RegDelNull64.exe',
|
||||||
'Software Bundle': 'https://ninite.com/.net4.8-7zip-chrome-edge-vlc/ninite.exe',
|
|
||||||
'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe',
|
|
||||||
|
|
||||||
# Visual C++ Runtimes: https://docs.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist
|
|
||||||
'VCRedist_2012_x32': 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x86.exe',
|
|
||||||
'VCRedist_2012_x64': 'https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe',
|
|
||||||
'VCRedist_2013_x32': 'https://aka.ms/highdpimfc2013x86enu',
|
|
||||||
'VCRedist_2013_x64': 'https://aka.ms/highdpimfc2013x64enu',
|
|
||||||
'VCRedist_2022_x32': 'https://aka.ms/vs/17/release/vc_redist.x86.exe',
|
|
||||||
'VCRedist_2022_x64': 'https://aka.ms/vs/17/release/vc_redist.x64.exe',
|
|
||||||
|
|
||||||
# Build Kit
|
# Build Kit
|
||||||
'AIDA64': 'https://download.aida64.com/aida64engineer675.zip',
|
'AIDA64': 'https://download.aida64.com/aida64engineer692.zip',
|
||||||
'Adobe Reader DC': 'https://ardownload2.adobe.com/pub/adobe/reader/win/AcrobatDC/2200220191/AcroRdrDC2200220191_en_US.exe',
|
'Adobe Reader DC': 'https://ardownload2.adobe.com/pub/adobe/reader/win/AcrobatDC/2300620360/AcroRdrDC2300620360_en_US.exe',
|
||||||
'Aria2': 'https://github.com/aria2/aria2/releases/download/release-1.36.0/aria2-1.36.0-win-32bit-build1.zip',
|
'Aria2': 'https://github.com/aria2/aria2/releases/download/release-1.36.0/aria2-1.36.0-win-32bit-build1.zip',
|
||||||
'Autoruns32': 'http://live.sysinternals.com/Autoruns.exe',
|
'Autoruns32': 'http://live.sysinternals.com/Autoruns.exe',
|
||||||
'Autoruns64': 'http://live.sysinternals.com/Autoruns64.exe',
|
'Autoruns64': 'http://live.sysinternals.com/Autoruns64.exe',
|
||||||
'BleachBit': 'https://download.bleachbit.org/BleachBit-4.4.2-portable.zip',
|
'BleachBit': 'https://download.bleachbit.org/BleachBit-4.4.2-portable.zip',
|
||||||
'BlueScreenView32': 'http://www.nirsoft.net/utils/bluescreenview.zip',
|
'BlueScreenView32': 'http://www.nirsoft.net/utils/bluescreenview.zip',
|
||||||
'BlueScreenView64': 'http://www.nirsoft.net/utils/bluescreenview-x64.zip',
|
'BlueScreenView64': 'http://www.nirsoft.net/utils/bluescreenview-x64.zip',
|
||||||
|
'BCUninstaller': 'https://github.com/Klocman/Bulk-Crap-Uninstaller/releases/download/v5.7/BCUninstaller_5.7_portable.zip',
|
||||||
|
'DDU': 'https://www.wagnardsoft.com/DDU/download/DDU%20v18.0.6.8.exe',
|
||||||
'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.1020.x86.zip',
|
'Everything32': 'https://www.voidtools.com/Everything-1.4.1.1024.x86.zip',
|
||||||
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.1020.x64.zip',
|
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.1024.x64.zip',
|
||||||
'FastCopy': 'https://ftp.vector.co.jp/75/32/2323/FastCopy4.2.0_installer.exe',
|
'FastCopy': 'https://github.com/FastCopyLab/FastCopyDist2/raw/main/FastCopy5.4.2_installer.exe',
|
||||||
'Fluent-Metro': 'https://github.com/bonzibudd/Fluent-Metro/releases/download/v1.5.3/Fluent-Metro_1.5.3.zip',
|
'Fluent-Metro': 'https://github.com/bonzibudd/Fluent-Metro/releases/download/v1.5.3/Fluent-Metro_1.5.3.zip',
|
||||||
'FurMark': 'https://geeks3d.com/dl/get/696',
|
'FurMark': 'https://geeks3d.com/dl/get/728',
|
||||||
'HWiNFO': 'https://www.sac.sk/download/utildiag/hwi_730.zip',
|
'HWiNFO': 'https://www.sac.sk/download/utildiag/hwi_764.zip',
|
||||||
'LibreOffice32': 'https://download.documentfoundation.org/libreoffice/stable/7.3.6/win/x86/LibreOffice_7.3.6_Win_x86.msi',
|
'LibreOffice32': 'https://download.documentfoundation.org/libreoffice/stable/7.6.2/win/x86/LibreOffice_7.6.2_Win_x86.msi',
|
||||||
'LibreOffice64': 'https://download.documentfoundation.org/libreoffice/stable/7.3.6/win/x86_64/LibreOffice_7.3.6_Win_x64.msi',
|
'LibreOffice64': 'https://download.documentfoundation.org/libreoffice/stable/7.6.2/win/x86_64/LibreOffice_7.6.2_Win_x86-64.msi',
|
||||||
'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
|
'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
|
||||||
'Neutron': 'http://keir.net/download/neutron.zip',
|
'Neutron': 'http://keir.net/download/neutron.zip',
|
||||||
'Notepad++': 'https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.1.9.3/npp.8.1.9.3.portable.minimalist.7z',
|
'Notepad++': 'https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.5.8/npp.8.5.8.portable.minimalist.7z',
|
||||||
'OpenShell': 'https://github.com/Open-Shell/Open-Shell-Menu/releases/download/v4.4.170/OpenShellSetup_4_4_170.exe',
|
'OpenShell': 'https://github.com/Open-Shell/Open-Shell-Menu/releases/download/v4.4.191/OpenShellSetup_4_4_191.exe',
|
||||||
'PuTTY': 'https://the.earth.li/~sgtatham/putty/latest/w32/putty.zip',
|
'PuTTY': 'https://the.earth.li/~sgtatham/putty/latest/w32/putty.zip',
|
||||||
'SDIO Torrent': 'https://www.glenn.delahoy.com/downloads/sdio/SDIO_Update.torrent',
|
'SDIO Torrent': 'https://www.glenn.delahoy.com/downloads/sdio/SDIO_Update.torrent',
|
||||||
'UninstallView32': 'https://www.nirsoft.net/utils/uninstallview.zip',
|
'WizTree': 'https://diskanalyzer.com/files/wiztree_4_15_portable.zip',
|
||||||
'UninstallView64': 'https://www.nirsoft.net/utils/uninstallview-x64.zip',
|
|
||||||
'WizTree': 'https://diskanalyzer.com/files/wiztree_4_10_portable.zip',
|
|
||||||
'XMPlay': 'https://support.xmplay.com/files/20/xmplay385.zip?v=47090',
|
'XMPlay': 'https://support.xmplay.com/files/20/xmplay385.zip?v=47090',
|
||||||
'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',
|
||||||
'XMPlay Game': 'https://support.xmplay.com/files/12/xmp-gme.zip?v=515637',
|
'XMPlay Game': 'https://support.xmplay.com/files/12/xmp-gme.zip?v=515637',
|
||||||
'XMPlay RAR': 'https://support.xmplay.com/files/16/xmp-rar.zip?v=409646',
|
'XMPlay RAR': 'https://support.xmplay.com/files/16/xmp-rar.zip?v=409646',
|
||||||
'XMPlay Innocuous': 'https://support.xmplay.com/files/10/Innocuous%20(v1.5).zip?v=155959',
|
'XMPlay Innocuous': 'https://support.xmplay.com/files/10/Innocuous%20(v1.7).zip?v=645281',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
"""WizardKit: Config - UFD"""
|
"""WizardKit: Config - UFD"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from wk.cfg.main import KIT_NAME_FULL
|
from wk.cfg.main import KIT_NAME_FULL
|
||||||
|
|
||||||
|
|
||||||
# General
|
# General
|
||||||
SOURCES = OrderedDict({
|
SOURCES = {
|
||||||
'Linux': {'Arg': '--linux', 'Type': 'ISO'},
|
'Linux': {'Arg': '--linux', 'Type': 'ISO'},
|
||||||
'WinPE': {'Arg': '--winpe', 'Type': 'ISO'},
|
'WinPE': {'Arg': '--winpe', 'Type': 'ISO'},
|
||||||
'Main Kit': {'Arg': '--main-kit', 'Type': 'KIT'},
|
'Main Kit': {'Arg': '--main-kit', 'Type': 'KIT'},
|
||||||
'Extra Dir': {'Arg': '--extra-dir', 'Type': 'DIR'},
|
'Extra Dir': {'Arg': '--extra-dir', 'Type': 'DIR'},
|
||||||
})
|
}
|
||||||
|
|
||||||
# Definitions: Boot entries
|
# Definitions: Boot entries
|
||||||
BOOT_ENTRIES = {
|
BOOT_ENTRIES = {
|
||||||
|
|
@ -39,8 +37,6 @@ ITEMS = {
|
||||||
),
|
),
|
||||||
'Linux': (
|
'Linux': (
|
||||||
('/arch', '/'),
|
('/arch', '/'),
|
||||||
('/EFI/boot', '/EFI/'),
|
|
||||||
('/syslinux', '/'),
|
|
||||||
),
|
),
|
||||||
'Main Kit': (
|
'Main Kit': (
|
||||||
('/', f'/{KIT_NAME_FULL}/'),
|
('/', f'/{KIT_NAME_FULL}/'),
|
||||||
|
|
@ -58,6 +54,25 @@ ITEMS = {
|
||||||
('/sources/boot.wim', '/sources/'),
|
('/sources/boot.wim', '/sources/'),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
ITEMS_FROM_LIVE = {
|
||||||
|
'WizardKit UFD base': (
|
||||||
|
('/usr/share/WizardKit/', '/'),
|
||||||
|
),
|
||||||
|
'rEFInd': (
|
||||||
|
('/usr/share/refind/drivers_x64/', '/EFI/Boot/drivers_x64/'),
|
||||||
|
('/usr/share/refind/icons/', '/EFI/Boot/icons/'),
|
||||||
|
('/usr/share/refind/refind_x64.efi', '/EFI/Boot/'),
|
||||||
|
),
|
||||||
|
'Syslinux': (
|
||||||
|
('/usr/lib/syslinux/bios/', '/syslinux/'),
|
||||||
|
),
|
||||||
|
'Memtest86': (
|
||||||
|
('/usr/share/memtest86-efi/', '/EFI/Memtest86/'),
|
||||||
|
),
|
||||||
|
'Wimboot': (
|
||||||
|
('/usr/share/wimboot/', '/syslinux/'),
|
||||||
|
),
|
||||||
|
}
|
||||||
ITEMS_HIDDEN = (
|
ITEMS_HIDDEN = (
|
||||||
# Linux (all versions)
|
# Linux (all versions)
|
||||||
'arch',
|
'arch',
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,6 @@ WINDOWS_BUILDS = {
|
||||||
# Windows 11
|
# Windows 11
|
||||||
'10.0.22000': '21H2',
|
'10.0.22000': '21H2',
|
||||||
'10.0.22621': '22H2',
|
'10.0.22621': '22H2',
|
||||||
|
'10.0.22631': '23H2',
|
||||||
|
'10.0.26100': '24H2',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
scripts/wk/cfg/winget/default.json
Normal file
32
scripts/wk/cfg/winget/default.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/winget-packages.schema.2.0.json",
|
||||||
|
"CreationDate": "2023-06-25T01:40:45.003-00:00",
|
||||||
|
"Sources": [
|
||||||
|
{
|
||||||
|
"Packages": [
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "7zip.7zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "Google.Chrome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "Microsoft.Edge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "Mozilla.Firefox"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "VideoLAN.VLC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SourceDetails": {
|
||||||
|
"Argument": "https://cdn.winget.microsoft.com/cache",
|
||||||
|
"Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
|
||||||
|
"Name": "winget",
|
||||||
|
"Type": "Microsoft.PreIndexed.Package"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"WinGetVersion": "1.4.11071"
|
||||||
|
}
|
||||||
29
scripts/wk/cfg/winget/vcredists.json
Normal file
29
scripts/wk/cfg/winget/vcredists.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/winget-packages.schema.2.0.json",
|
||||||
|
"CreationDate": "2023-06-25T01:40:45.003-00:00",
|
||||||
|
"Sources": [
|
||||||
|
{
|
||||||
|
"Packages": [
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "Microsoft.VCRedist.2013.x64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "Microsoft.VCRedist.2013.x86"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "Microsoft.VCRedist.2015+.x64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PackageIdentifier": "Microsoft.VCRedist.2015+.x86"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SourceDetails": {
|
||||||
|
"Argument": "https://cdn.winget.microsoft.com/cache",
|
||||||
|
"Identifier": "Microsoft.Winget.Source_8wekyb3d8bbwe",
|
||||||
|
"Name": "winget",
|
||||||
|
"Type": "Microsoft.PreIndexed.Package"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"WinGetVersion": "1.4.11071"
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
"""WizardKit: ddrescue-tui module init"""
|
"""WizardKit: ddrescue-tui module init"""
|
||||||
|
|
||||||
|
from . import block_pair
|
||||||
from . import ddrescue
|
from . import ddrescue
|
||||||
|
from . import image
|
||||||
|
from . import menus
|
||||||
|
from . import state
|
||||||
|
|
|
||||||
575
scripts/wk/clone/block_pair.py
Normal file
575
scripts/wk/clone/block_pair.py
Normal file
|
|
@ -0,0 +1,575 @@
|
||||||
|
"""WizardKit: ddrescue TUI - Block Pairs"""
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import plistlib
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from wk import cfg, exe, std
|
||||||
|
from wk.clone import menus
|
||||||
|
from wk.hw import disk as hw_disk
|
||||||
|
from wk.ui import ansi, cli
|
||||||
|
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
DDRESCUE_LOG_REGEX = re.compile(
|
||||||
|
r'^\s*(?P<key>\S+):\s+'
|
||||||
|
r'(?P<size>\d+)\s+'
|
||||||
|
r'(?P<unit>[PTGMKB]i?B?)'
|
||||||
|
r'.*\(\s*(?P<percent>\d+\.?\d*)%\)$',
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
class BlockPair():
|
||||||
|
"""Object for tracking source to dest recovery data."""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
source_dev: hw_disk.Disk,
|
||||||
|
destination: pathlib.Path,
|
||||||
|
working_dir: pathlib.Path,
|
||||||
|
):
|
||||||
|
self.sector_size: int = source_dev.phy_sec
|
||||||
|
self.source: pathlib.Path = pathlib.Path(source_dev.path)
|
||||||
|
self.destination: pathlib.Path = destination
|
||||||
|
self.map_data: dict[str, bool | int] = {}
|
||||||
|
self.map_path: pathlib.Path = pathlib.Path()
|
||||||
|
self.size: int = source_dev.size
|
||||||
|
self.status: dict[str, float | int | str] = {
|
||||||
|
'read-skip': 'Pending',
|
||||||
|
'read-full': 'Pending',
|
||||||
|
'trim': 'Pending',
|
||||||
|
'scrape': 'Pending',
|
||||||
|
}
|
||||||
|
self.test_map: pathlib.Path | None = None
|
||||||
|
self.view_map: bool = 'DISPLAY' in os.environ or 'WAYLAND_DISPLAY' in os.environ
|
||||||
|
self.view_proc: subprocess.Popen | None = None
|
||||||
|
|
||||||
|
# Set map path
|
||||||
|
# e.g. '(Clone|Image)_Model_Serial[_p#]_Size[_Label].map'
|
||||||
|
map_name = f'{source_dev.model}_{source_dev.serial}'
|
||||||
|
if source_dev.bus == 'Image':
|
||||||
|
map_name = 'Image'
|
||||||
|
if source_dev.parent:
|
||||||
|
part_num = re.sub(r"^.*?(\d+)$", r"\1", self.source.name)
|
||||||
|
map_name += f'_p{part_num}'
|
||||||
|
size_str = std.bytes_to_string(
|
||||||
|
size=self.size,
|
||||||
|
use_binary=False,
|
||||||
|
)
|
||||||
|
map_name += f'_{size_str.replace(" ", "")}'
|
||||||
|
if source_dev.raw_details.get('label', ''):
|
||||||
|
map_name += f'_{source_dev.raw_details["label"]}'
|
||||||
|
map_name = map_name.replace(' ', '_')
|
||||||
|
map_name = map_name.replace('/', '_')
|
||||||
|
map_name = map_name.replace('\\', '_')
|
||||||
|
if destination.is_dir():
|
||||||
|
# Imaging
|
||||||
|
self.map_path = pathlib.Path(f'{destination}/Image_{map_name}.map')
|
||||||
|
self.destination = self.map_path.with_suffix('.dd')
|
||||||
|
self.destination.touch()
|
||||||
|
else:
|
||||||
|
# Cloning
|
||||||
|
self.map_path = pathlib.Path(f'{working_dir}/Clone_{map_name}.map')
|
||||||
|
|
||||||
|
# Create map file if needed
|
||||||
|
# NOTE: We need to set the domain size for --complete-only to work
|
||||||
|
if not self.map_path.exists():
|
||||||
|
self.map_path.write_text(
|
||||||
|
data=cfg.ddrescue.DDRESCUE_MAP_TEMPLATE.format(
|
||||||
|
name=cfg.main.KIT_NAME_FULL,
|
||||||
|
size=self.size,
|
||||||
|
),
|
||||||
|
encoding='utf-8',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set initial status
|
||||||
|
self.set_initial_status()
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
"""Override to allow pickling ddrescue.State() objects."""
|
||||||
|
bp_state = self.__dict__.copy()
|
||||||
|
del bp_state['view_proc']
|
||||||
|
return bp_state
|
||||||
|
|
||||||
|
def get_error_size(self) -> int:
|
||||||
|
"""Get error size in bytes, returns int."""
|
||||||
|
return self.size - self.get_rescued_size()
|
||||||
|
|
||||||
|
def get_percent_recovered(self) -> float:
|
||||||
|
"""Get percent rescued from map_data, returns float."""
|
||||||
|
return 100 * self.map_data.get('rescued', 0) / self.size
|
||||||
|
|
||||||
|
def get_rescued_size(self) -> int:
|
||||||
|
"""Get rescued size using map data.
|
||||||
|
|
||||||
|
NOTE: Returns 0 if no map data is available.
|
||||||
|
"""
|
||||||
|
self.load_map_data()
|
||||||
|
return self.map_data.get('rescued', 0)
|
||||||
|
|
||||||
|
def load_map_data(self) -> None:
|
||||||
|
"""Load map data from file.
|
||||||
|
|
||||||
|
NOTE: If the file is missing it is assumed that recovery hasn't
|
||||||
|
started yet so default values will be returned instead.
|
||||||
|
"""
|
||||||
|
data: dict[str, bool | int] = {'full recovery': False, 'pass completed': False}
|
||||||
|
|
||||||
|
# Get output from ddrescuelog
|
||||||
|
cmd = [
|
||||||
|
'ddrescuelog',
|
||||||
|
'--binary-prefixes',
|
||||||
|
'--show-status',
|
||||||
|
f'--size={self.size}',
|
||||||
|
self.map_path,
|
||||||
|
]
|
||||||
|
proc = exe.run_program(cmd, check=False)
|
||||||
|
|
||||||
|
# Parse output
|
||||||
|
for line in proc.stdout.splitlines():
|
||||||
|
_r = DDRESCUE_LOG_REGEX.search(line)
|
||||||
|
if _r:
|
||||||
|
if _r.group('key') == 'rescued' and _r.group('percent') == '100':
|
||||||
|
# Fix rounding errors from ddrescuelog output
|
||||||
|
data['rescued'] = self.size
|
||||||
|
else:
|
||||||
|
data[_r.group('key')] = std.string_to_bytes(
|
||||||
|
f'{_r.group("size")} {_r.group("unit")}',
|
||||||
|
)
|
||||||
|
data['pass completed'] = 'current status: finished' in line.lower()
|
||||||
|
|
||||||
|
# Check if 100% done (only if map is present and non-zero size
|
||||||
|
# NOTE: ddrescuelog returns 0 (i.e. 100% done) for empty files
|
||||||
|
if self.map_path.exists() and self.map_path.stat().st_size != 0:
|
||||||
|
cmd = [
|
||||||
|
'ddrescuelog',
|
||||||
|
'--done-status',
|
||||||
|
f'--size={self.size}',
|
||||||
|
self.map_path,
|
||||||
|
]
|
||||||
|
proc = exe.run_program(cmd, check=False)
|
||||||
|
data['full recovery'] = proc.returncode == 0
|
||||||
|
|
||||||
|
# Done
|
||||||
|
self.map_data.update(data)
|
||||||
|
|
||||||
|
def pass_complete(self, pass_name) -> bool:
|
||||||
|
"""Check if pass_name is complete based on map data, returns bool."""
|
||||||
|
pending_size = self.map_data['non-tried']
|
||||||
|
|
||||||
|
# Full recovery
|
||||||
|
if self.map_data.get('full recovery', False):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# New recovery
|
||||||
|
if 'non-tried' not in self.map_data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Initial read skip pass
|
||||||
|
if pass_name == 'read-skip':
|
||||||
|
pass_threshold = cfg.ddrescue.AUTO_PASS_THRESHOLDS[pass_name]
|
||||||
|
if self.get_percent_recovered() >= pass_threshold:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Recovery in progress
|
||||||
|
if pass_name in ('trim', 'scrape'):
|
||||||
|
pending_size += self.map_data['non-trimmed']
|
||||||
|
if pass_name == 'scrape':
|
||||||
|
pending_size += self.map_data['non-scraped']
|
||||||
|
if pending_size == 0:
|
||||||
|
# This is true when the previous and current passes are complete
|
||||||
|
return True
|
||||||
|
|
||||||
|
# This should never be reached
|
||||||
|
return False
|
||||||
|
|
||||||
|
def safety_check(self) -> None:
|
||||||
|
"""Run safety check and abort if necessary."""
|
||||||
|
# TODO: Expand section to support non-Linux systems
|
||||||
|
dest_size = -1
|
||||||
|
if self.destination.is_block_device():
|
||||||
|
cmd = [
|
||||||
|
'lsblk', '--bytes', '--json',
|
||||||
|
'--nodeps', '--noheadings', '--output=size',
|
||||||
|
self.destination,
|
||||||
|
]
|
||||||
|
json_data = exe.get_json_from_command(cmd)
|
||||||
|
dest_size = json_data['blockdevices'][0]['size']
|
||||||
|
del json_data
|
||||||
|
|
||||||
|
# Check destination size if cloning
|
||||||
|
if not self.destination.is_file() and dest_size < self.size:
|
||||||
|
cli.print_error(f'Invalid destination: {self.destination}')
|
||||||
|
raise std.GenericAbort()
|
||||||
|
|
||||||
|
def set_initial_status(self) -> None:
|
||||||
|
"""Read map data and set initial statuses."""
|
||||||
|
self.load_map_data()
|
||||||
|
percent = self.get_percent_recovered()
|
||||||
|
for name in self.status:
|
||||||
|
if self.pass_complete(name):
|
||||||
|
self.status[name] = percent
|
||||||
|
else:
|
||||||
|
# Stop checking
|
||||||
|
if percent > 0:
|
||||||
|
self.status[name] = percent
|
||||||
|
break
|
||||||
|
|
||||||
|
def skip_pass(self, pass_name) -> None:
|
||||||
|
"""Mark pass as skipped if applicable."""
|
||||||
|
if self.status[pass_name] == 'Pending':
|
||||||
|
self.status[pass_name] = 'Skipped'
|
||||||
|
|
||||||
|
def update_progress(self, pass_name) -> None:
|
||||||
|
"""Update progress via map data."""
|
||||||
|
self.load_map_data()
|
||||||
|
|
||||||
|
# Update status
|
||||||
|
percent = self.get_percent_recovered()
|
||||||
|
if percent > 0:
|
||||||
|
self.status[pass_name] = percent
|
||||||
|
|
||||||
|
# Mark future passes as skipped if applicable
|
||||||
|
if percent == 100:
|
||||||
|
status_keys = list(self.status.keys())
|
||||||
|
for pass_n in status_keys[status_keys.index(pass_name)+1:]:
|
||||||
|
self.status[pass_n] = 'Skipped'
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def add_clone_block_pairs(state) -> list[hw_disk.Disk]:
|
||||||
|
"""Add device to device block pairs and set settings if necessary."""
|
||||||
|
source_sep = get_partition_separator(state.source.path.name)
|
||||||
|
dest_sep = get_partition_separator(state.destination.path.name)
|
||||||
|
settings = {}
|
||||||
|
|
||||||
|
# Clone settings
|
||||||
|
settings = state.load_settings(discard_unused_settings=True)
|
||||||
|
|
||||||
|
# Add pairs from previous run
|
||||||
|
if settings['Partition Mapping']:
|
||||||
|
source_parts = []
|
||||||
|
for part_map in settings['Partition Mapping']:
|
||||||
|
bp_source = hw_disk.Disk(
|
||||||
|
f'{state.source.path}{source_sep}{part_map[0]}',
|
||||||
|
)
|
||||||
|
bp_dest = pathlib.Path(
|
||||||
|
f'{state.destination.path}{dest_sep}{part_map[1]}',
|
||||||
|
)
|
||||||
|
source_parts.append(bp_source)
|
||||||
|
state.add_block_pair(bp_source, bp_dest)
|
||||||
|
return source_parts
|
||||||
|
|
||||||
|
# Add pairs from selection
|
||||||
|
source_parts = menus.select_disk_parts('Clone', state.source)
|
||||||
|
if state.source.path.samefile(source_parts[0].path):
|
||||||
|
# Whole disk (or single partition via args), skip settings
|
||||||
|
bp_dest = state.destination.path
|
||||||
|
state.add_block_pair(state.source, bp_dest)
|
||||||
|
return source_parts
|
||||||
|
|
||||||
|
# New run, use new settings file
|
||||||
|
settings['Needs Format'] = True
|
||||||
|
offset = 0
|
||||||
|
user_choice = cli.choice(
|
||||||
|
'Format clone using GPT, MBR, or match Source type?',
|
||||||
|
['G', 'M', 'S'],
|
||||||
|
)
|
||||||
|
if user_choice == 'G':
|
||||||
|
settings['Table Type'] = 'GPT'
|
||||||
|
elif user_choice == 'M':
|
||||||
|
settings['Table Type'] = 'MBR'
|
||||||
|
else:
|
||||||
|
# Match source type
|
||||||
|
settings['Table Type'] = get_table_type(state.source.path)
|
||||||
|
if cli.ask('Create an empty Windows boot partition on the clone?'):
|
||||||
|
settings['Create Boot Partition'] = True
|
||||||
|
offset = 2 if settings['Table Type'] == 'GPT' else 1
|
||||||
|
|
||||||
|
# Add pairs
|
||||||
|
for dest_num, part in enumerate(source_parts):
|
||||||
|
dest_num += offset + 1
|
||||||
|
bp_dest = pathlib.Path(
|
||||||
|
f'{state.destination.path}{dest_sep}{dest_num}',
|
||||||
|
)
|
||||||
|
state.add_block_pair(part, bp_dest)
|
||||||
|
|
||||||
|
# Add to settings file
|
||||||
|
source_num = re.sub(r'^.*?(\d+)$', r'\1', part.path.name)
|
||||||
|
settings['Partition Mapping'].append([source_num, dest_num])
|
||||||
|
|
||||||
|
# Save settings
|
||||||
|
state.save_settings(settings)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return source_parts
|
||||||
|
|
||||||
|
|
||||||
|
def add_image_block_pairs(state) -> list[hw_disk.Disk]:
|
||||||
|
"""Add device to image file block pairs."""
|
||||||
|
source_parts = menus.select_disk_parts(state.mode, state.source)
|
||||||
|
for part in source_parts:
|
||||||
|
state.add_block_pair(part, state.destination)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return source_parts
|
||||||
|
|
||||||
|
|
||||||
|
def build_block_pair_report(block_pairs, settings) -> list:
|
||||||
|
"""Build block pair report, returns list."""
|
||||||
|
report = []
|
||||||
|
notes = []
|
||||||
|
if block_pairs:
|
||||||
|
report.append(ansi.color_string('Block Pairs', 'GREEN'))
|
||||||
|
else:
|
||||||
|
# Bail early
|
||||||
|
return report
|
||||||
|
|
||||||
|
# Show block pair mapping
|
||||||
|
if settings and settings['Create Boot Partition']:
|
||||||
|
if settings['Table Type'] == 'GPT':
|
||||||
|
report.append(f'{" —— ":<9} --> EFI System Partition')
|
||||||
|
report.append(f'{" —— ":<9} --> Microsoft Reserved Partition')
|
||||||
|
elif settings['Table Type'] == 'MBR':
|
||||||
|
report.append(f'{" —— ":<9} --> System Reserved')
|
||||||
|
for pair in block_pairs:
|
||||||
|
report.append(f'{pair.source.name:<9} --> {pair.destination.name}')
|
||||||
|
|
||||||
|
# Show resume messages as necessary
|
||||||
|
if settings:
|
||||||
|
if not settings['First Run']:
|
||||||
|
notes.append(
|
||||||
|
ansi.color_string(
|
||||||
|
['NOTE:', 'Clone settings loaded from previous run.'],
|
||||||
|
['BLUE', None],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if settings['Needs Format'] and settings['Table Type']:
|
||||||
|
msg = f'Destination will be formatted using {settings["Table Type"]}'
|
||||||
|
notes.append(
|
||||||
|
ansi.color_string(
|
||||||
|
['NOTE:', msg],
|
||||||
|
['BLUE', None],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if any(pair.get_rescued_size() > 0 for pair in block_pairs):
|
||||||
|
notes.append(
|
||||||
|
ansi.color_string(
|
||||||
|
['NOTE:', 'Resume data loaded from map file(s).'],
|
||||||
|
['BLUE', None],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add notes to report
|
||||||
|
if notes:
|
||||||
|
report.append(' ')
|
||||||
|
report.extend(notes)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def build_sfdisk_partition_line(table_type, dev_path, size, details) -> str:
|
||||||
|
"""Build sfdisk partition line using passed details, returns str."""
|
||||||
|
line = f'{dev_path} : size={size}'
|
||||||
|
dest_type = ''
|
||||||
|
source_filesystem = str(details.get('fstype', '')).upper()
|
||||||
|
source_table_type = ''
|
||||||
|
source_type = details.get('parttype', '')
|
||||||
|
|
||||||
|
# Set dest type
|
||||||
|
if re.match(r'^0x\w+$', source_type):
|
||||||
|
# Source is a MBR type
|
||||||
|
source_table_type = 'MBR'
|
||||||
|
if table_type == 'MBR':
|
||||||
|
dest_type = source_type.replace('0x', '').lower()
|
||||||
|
elif re.match(r'^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', source_type):
|
||||||
|
# Source is a GPT type
|
||||||
|
source_table_type = 'GPT'
|
||||||
|
if table_type == 'GPT':
|
||||||
|
dest_type = source_type.upper()
|
||||||
|
if not dest_type:
|
||||||
|
# Assuming changing table types, set based on FS
|
||||||
|
if source_filesystem in cfg.ddrescue.PARTITION_TYPES.get(table_type, {}):
|
||||||
|
dest_type = cfg.ddrescue.PARTITION_TYPES[table_type][source_filesystem]
|
||||||
|
line += f', type={dest_type}'
|
||||||
|
|
||||||
|
# Safety Check
|
||||||
|
if not dest_type:
|
||||||
|
cli.print_error(f'Failed to determine partition type for: {dev_path}')
|
||||||
|
raise std.GenericAbort()
|
||||||
|
|
||||||
|
# Add extra details
|
||||||
|
if details.get('partlabel', ''):
|
||||||
|
line += f', name="{details["partlabel"]}"'
|
||||||
|
if details.get('partuuid', '') and source_table_type == table_type:
|
||||||
|
# Only add UUID if source/dest table types match
|
||||||
|
line += f', uuid={details["partuuid"].upper()}'
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return line
|
||||||
|
|
||||||
|
|
||||||
|
def get_partition_separator(name) -> str:
|
||||||
|
"""Get partition separator based on device name, returns str."""
|
||||||
|
separator = ''
|
||||||
|
if re.search(r'(loop|mmc|nvme)', name, re.IGNORECASE):
|
||||||
|
separator = 'p'
|
||||||
|
|
||||||
|
return separator
|
||||||
|
|
||||||
|
|
||||||
|
def get_table_type(disk_path) -> str:
|
||||||
|
"""Get disk partition table type, returns str.
|
||||||
|
|
||||||
|
NOTE: If resulting table type is not GPT or MBR
|
||||||
|
then an exception is raised.
|
||||||
|
"""
|
||||||
|
disk_path = str(disk_path)
|
||||||
|
table_type = None
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
if std.PLATFORM == 'Linux':
|
||||||
|
cmd = f'lsblk --json --output=pttype --nodeps {disk_path}'.split()
|
||||||
|
json_data = exe.get_json_from_command(cmd)
|
||||||
|
table_type = json_data['blockdevices'][0].get('pttype', '').upper()
|
||||||
|
table_type = table_type.replace('DOS', 'MBR')
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
if std.PLATFORM == 'Darwin':
|
||||||
|
cmd = ['diskutil', 'list', '-plist', disk_path]
|
||||||
|
proc = exe.run_program(cmd, check=False, encoding=None, errors=None)
|
||||||
|
try:
|
||||||
|
plist_data = plistlib.loads(proc.stdout)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# Invalid / corrupt plist data? return empty dict to avoid crash
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
disk_details = plist_data.get('AllDisksAndPartitions', [{}])[0]
|
||||||
|
table_type = disk_details['Content']
|
||||||
|
table_type = table_type.replace('FDisk_partition_scheme', 'MBR')
|
||||||
|
table_type = table_type.replace('GUID_partition_scheme', 'GPT')
|
||||||
|
|
||||||
|
# Check type
|
||||||
|
if table_type not in ('GPT', 'MBR'):
|
||||||
|
cli.print_error(f'Unsupported partition table type: {table_type}')
|
||||||
|
raise std.GenericAbort()
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return table_type
|
||||||
|
|
||||||
|
|
||||||
|
def prep_destination(
|
||||||
|
state,
|
||||||
|
source_parts: list[hw_disk.Disk],
|
||||||
|
dry_run: bool = True,
|
||||||
|
) -> None:
|
||||||
|
"""Prep destination as necessary."""
|
||||||
|
# TODO: Split into Linux and macOS
|
||||||
|
# logical sector size is not easily found under macOS
|
||||||
|
# It might be easier to rewrite this section using macOS tools
|
||||||
|
dest_prefix = str(state.destination.path)
|
||||||
|
dest_prefix += get_partition_separator(state.destination.path.name)
|
||||||
|
esp_type = 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B'
|
||||||
|
msr_type = 'E3C9E316-0B5C-4DB8-817D-F92DF00215AE'
|
||||||
|
part_num = 0
|
||||||
|
sfdisk_script = []
|
||||||
|
settings = state.load_settings()
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not settings['Needs Format']:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add partition table settings
|
||||||
|
if settings['Table Type'] == 'GPT':
|
||||||
|
sfdisk_script.append('label: gpt')
|
||||||
|
else:
|
||||||
|
sfdisk_script.append('label: dos')
|
||||||
|
sfdisk_script.append('unit: sectors')
|
||||||
|
sfdisk_script.append('')
|
||||||
|
|
||||||
|
# Add boot partition if requested
|
||||||
|
if settings['Create Boot Partition']:
|
||||||
|
if settings['Table Type'] == 'GPT':
|
||||||
|
part_num += 1
|
||||||
|
sfdisk_script.append(
|
||||||
|
build_sfdisk_partition_line(
|
||||||
|
table_type='GPT',
|
||||||
|
dev_path=f'{dest_prefix}{part_num}',
|
||||||
|
size='260MiB',
|
||||||
|
details={'parttype': esp_type, 'partlabel': 'EFI System'},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
part_num += 1
|
||||||
|
sfdisk_script.append(
|
||||||
|
build_sfdisk_partition_line(
|
||||||
|
table_type=settings['Table Type'],
|
||||||
|
dev_path=f'{dest_prefix}{part_num}',
|
||||||
|
size='16MiB',
|
||||||
|
details={'parttype': msr_type, 'partlabel': 'Microsoft Reserved'},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
elif settings['Table Type'] == 'MBR':
|
||||||
|
part_num += 1
|
||||||
|
sfdisk_script.append(
|
||||||
|
build_sfdisk_partition_line(
|
||||||
|
table_type='MBR',
|
||||||
|
dev_path=f'{dest_prefix}{part_num}',
|
||||||
|
size='100MiB',
|
||||||
|
details={'parttype': '0x7', 'partlabel': 'System Reserved'},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add selected partition(s)
|
||||||
|
for part in source_parts:
|
||||||
|
num_sectors = part.size / state.destination.log_sec
|
||||||
|
num_sectors = math.ceil(num_sectors)
|
||||||
|
part_num += 1
|
||||||
|
sfdisk_script.append(
|
||||||
|
build_sfdisk_partition_line(
|
||||||
|
table_type=settings['Table Type'],
|
||||||
|
dev_path=f'{dest_prefix}{part_num}',
|
||||||
|
size=num_sectors,
|
||||||
|
details=part.raw_details,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save sfdisk script
|
||||||
|
script_path = (
|
||||||
|
f'{state.working_dir}/'
|
||||||
|
f'sfdisk_{state.destination.path.name}.script'
|
||||||
|
)
|
||||||
|
with open(script_path, 'w', encoding='utf-8') as _f:
|
||||||
|
_f.write('\n'.join(sfdisk_script))
|
||||||
|
|
||||||
|
# Skip real format for dry runs
|
||||||
|
if dry_run:
|
||||||
|
LOG.info('Dry run, refusing to format destination')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Format disk
|
||||||
|
LOG.warning('Formatting destination: %s', state.destination.path)
|
||||||
|
with open(script_path, 'r', encoding='utf-8') as _f:
|
||||||
|
proc = exe.run_program(
|
||||||
|
cmd=['sudo', 'sfdisk', state.destination.path],
|
||||||
|
stdin=_f,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
cli.print_error('Error(s) encoundtered while formatting destination')
|
||||||
|
raise std.GenericAbort()
|
||||||
|
|
||||||
|
# Update settings
|
||||||
|
settings['Needs Format'] = False
|
||||||
|
state.save_settings(settings)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
File diff suppressed because it is too large
Load diff
109
scripts/wk/clone/image.py
Normal file
109
scripts/wk/clone/image.py
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
"""WizardKit: ddrescue TUI - State"""
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import logging
|
||||||
|
import pathlib
|
||||||
|
import plistlib
|
||||||
|
import re
|
||||||
|
|
||||||
|
from wk import exe
|
||||||
|
from wk.std import PLATFORM
|
||||||
|
from wk.ui import cli
|
||||||
|
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def mount_raw_image(path) -> pathlib.Path:
|
||||||
|
"""Mount raw image using OS specific methods, returns pathlib.Path."""
|
||||||
|
loopback_path = None
|
||||||
|
|
||||||
|
if PLATFORM == 'Darwin':
|
||||||
|
loopback_path = mount_raw_image_macos(path)
|
||||||
|
elif PLATFORM == 'Linux':
|
||||||
|
loopback_path = mount_raw_image_linux(path)
|
||||||
|
|
||||||
|
# Check
|
||||||
|
if not loopback_path:
|
||||||
|
cli.print_error(f'Failed to mount image: {path}')
|
||||||
|
|
||||||
|
# Register unmount atexit
|
||||||
|
atexit.register(unmount_loopback_device, loopback_path)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return loopback_path
|
||||||
|
|
||||||
|
|
||||||
|
def mount_raw_image_linux(path) -> pathlib.Path:
|
||||||
|
"""Mount raw image using losetup, returns pathlib.Path."""
|
||||||
|
loopback_path = None
|
||||||
|
|
||||||
|
# Mount using losetup
|
||||||
|
cmd = [
|
||||||
|
'sudo',
|
||||||
|
'losetup',
|
||||||
|
'--find',
|
||||||
|
'--partscan',
|
||||||
|
'--show',
|
||||||
|
path,
|
||||||
|
]
|
||||||
|
proc = exe.run_program(cmd, check=False)
|
||||||
|
|
||||||
|
# Check result
|
||||||
|
if proc.returncode == 0:
|
||||||
|
loopback_path = proc.stdout.strip()
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return loopback_path
|
||||||
|
|
||||||
|
|
||||||
|
def mount_raw_image_macos(path) -> pathlib.Path:
|
||||||
|
"""Mount raw image using hdiutil, returns pathlib.Path."""
|
||||||
|
loopback_path = None
|
||||||
|
plist_data = {}
|
||||||
|
|
||||||
|
# Mount using hdiutil
|
||||||
|
# plistdata['system-entities'][{}...]
|
||||||
|
cmd = [
|
||||||
|
'hdiutil', 'attach',
|
||||||
|
'-imagekey', 'diskimage-class=CRawDiskImage',
|
||||||
|
'-nomount',
|
||||||
|
'-plist',
|
||||||
|
'-readonly',
|
||||||
|
path,
|
||||||
|
]
|
||||||
|
proc = exe.run_program(cmd, check=False, encoding=None, errors=None)
|
||||||
|
|
||||||
|
# Check result
|
||||||
|
try:
|
||||||
|
plist_data = plistlib.loads(proc.stdout)
|
||||||
|
except plistlib.InvalidFileException:
|
||||||
|
return None
|
||||||
|
for dev in plist_data.get('system-entities', []):
|
||||||
|
dev_path = dev.get('dev-entry', '')
|
||||||
|
if re.match(r'^/dev/disk\d+$', dev_path):
|
||||||
|
loopback_path = dev_path
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return loopback_path
|
||||||
|
|
||||||
|
|
||||||
|
def unmount_loopback_device(path) -> None:
|
||||||
|
"""Unmount loopback device using OS specific methods."""
|
||||||
|
cmd = []
|
||||||
|
|
||||||
|
# Build OS specific cmd
|
||||||
|
if PLATFORM == 'Darwin':
|
||||||
|
cmd = ['hdiutil', 'detach', path]
|
||||||
|
elif PLATFORM == 'Linux':
|
||||||
|
cmd = ['sudo', 'losetup', '--detach', path]
|
||||||
|
|
||||||
|
# Unmount loopback device
|
||||||
|
exe.run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
273
scripts/wk/clone/menus.py
Normal file
273
scripts/wk/clone/menus.py
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
"""WizardKit: ddrescue TUI - Menus"""
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from wk.cfg.ddrescue import DDRESCUE_SETTINGS
|
||||||
|
from wk.hw.disk import Disk, get_disks
|
||||||
|
from wk.std import GenericAbort, PLATFORM, bytes_to_string
|
||||||
|
from wk.ui import ansi, cli
|
||||||
|
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CLONE_SETTINGS = {
|
||||||
|
'Source': None,
|
||||||
|
'Destination': None,
|
||||||
|
'Create Boot Partition': False,
|
||||||
|
'First Run': True,
|
||||||
|
'Needs Format': False,
|
||||||
|
'Table Type': None,
|
||||||
|
'Partition Mapping': [
|
||||||
|
# (5, 1) ## Clone source partition #5 to destination partition #1
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if PLATFORM == 'Darwin':
|
||||||
|
# TODO: Direct I/O needs more testing under macOS
|
||||||
|
DDRESCUE_SETTINGS['Default']['--idirect'] = {'Selected': False, 'Hidden': True}
|
||||||
|
DDRESCUE_SETTINGS['Default']['--odirect'] = {'Selected': False, 'Hidden': True}
|
||||||
|
MENU_ACTIONS = (
|
||||||
|
'Start',
|
||||||
|
f'Change settings {ansi.color_string("(experts only)", "YELLOW")}',
|
||||||
|
f'Detect drives {ansi.color_string("(experts only)", "YELLOW")}',
|
||||||
|
'Quit')
|
||||||
|
MENU_TOGGLES = {
|
||||||
|
'Auto continue (if recovery % over threshold)': True,
|
||||||
|
'Retry (mark non-rescued sectors "non-tried")': False,
|
||||||
|
}
|
||||||
|
SETTING_PRESETS = (
|
||||||
|
'Default',
|
||||||
|
'Fast',
|
||||||
|
'Safe',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def main() -> cli.Menu:
|
||||||
|
"""Main menu, returns wk.ui.cli.Menu."""
|
||||||
|
menu = cli.Menu(title=ansi.color_string('ddrescue TUI: Main Menu', 'GREEN'))
|
||||||
|
menu.separator = ' '
|
||||||
|
|
||||||
|
# Add actions, options, etc
|
||||||
|
for action in MENU_ACTIONS:
|
||||||
|
if not (PLATFORM == 'Darwin' and 'Detect drives' in action):
|
||||||
|
menu.add_action(action)
|
||||||
|
for toggle, selected in MENU_TOGGLES.items():
|
||||||
|
menu.add_toggle(toggle, {'Selected': selected})
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return menu
|
||||||
|
|
||||||
|
|
||||||
|
def settings(mode: str, silent: bool = True) -> cli.Menu:
|
||||||
|
"""Settings menu, returns wk.ui.cli.Menu."""
|
||||||
|
title_text = [
|
||||||
|
ansi.color_string('ddrescue TUI: Expert Settings', 'GREEN'),
|
||||||
|
' ',
|
||||||
|
ansi.color_string(
|
||||||
|
['These settings can cause', 'MAJOR DAMAGE', 'to drives'],
|
||||||
|
['YELLOW', 'RED', 'YELLOW'],
|
||||||
|
),
|
||||||
|
'Please read the manual before making changes',
|
||||||
|
]
|
||||||
|
menu = cli.Menu(title='\n'.join(title_text))
|
||||||
|
menu.separator = ' '
|
||||||
|
preset = 'Default'
|
||||||
|
if not silent:
|
||||||
|
# Ask which preset to use
|
||||||
|
cli.print_standard(
|
||||||
|
f'Available ddrescue presets: {" / ".join(SETTING_PRESETS)}'
|
||||||
|
)
|
||||||
|
preset = cli.choice('Please select a preset:', SETTING_PRESETS)
|
||||||
|
|
||||||
|
# Fix selection
|
||||||
|
for _p in SETTING_PRESETS:
|
||||||
|
if _p.startswith(preset):
|
||||||
|
preset = _p
|
||||||
|
|
||||||
|
# Add default settings
|
||||||
|
menu.add_action('Load Preset')
|
||||||
|
menu.add_action('Main Menu')
|
||||||
|
for name, details in DDRESCUE_SETTINGS['Default'].items():
|
||||||
|
menu.add_option(name, details.copy())
|
||||||
|
|
||||||
|
# Update settings using preset
|
||||||
|
if preset != 'Default':
|
||||||
|
for name, details in DDRESCUE_SETTINGS[preset].items():
|
||||||
|
menu.options[name].update(details.copy())
|
||||||
|
|
||||||
|
# Disable direct output when saving to an image
|
||||||
|
if mode == 'Image':
|
||||||
|
menu.options['--odirect']['Disabled'] = True
|
||||||
|
menu.options['--odirect']['Selected'] = False
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return menu
|
||||||
|
|
||||||
|
|
||||||
|
def disks() -> cli.Menu:
|
||||||
|
"""Disk menu, returns wk.ui.cli.Menu()."""
|
||||||
|
cli.print_info('Scanning disks...')
|
||||||
|
available_disks = get_disks()
|
||||||
|
menu = cli.Menu('ddrescue TUI: Disk selection')
|
||||||
|
menu.disabled_str = 'Already selected'
|
||||||
|
menu.separator = ' '
|
||||||
|
menu.add_action('Quit')
|
||||||
|
for disk in available_disks:
|
||||||
|
menu.add_option(
|
||||||
|
name=(
|
||||||
|
f'{str(disk.path):<12} '
|
||||||
|
f'{disk.bus:<5} '
|
||||||
|
f'{bytes_to_string(disk.size, decimals=1, use_binary=False):<8} '
|
||||||
|
f'{disk.model} '
|
||||||
|
f'{disk.serial}'
|
||||||
|
),
|
||||||
|
details={'Object': disk},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return menu
|
||||||
|
|
||||||
|
|
||||||
|
def select_disk(prompt_msg: str, menu: cli.Menu) -> Disk:
|
||||||
|
"""Select disk from provided Menu, returns Disk()."""
|
||||||
|
menu.title = ansi.color_string(
|
||||||
|
f'ddrescue TUI: {prompt_msg} Selection', 'GREEN',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get selection
|
||||||
|
selection = menu.simple_select()
|
||||||
|
if 'Quit' in selection:
|
||||||
|
raise GenericAbort()
|
||||||
|
|
||||||
|
# Disable selected disk's menu entry
|
||||||
|
menu.options[selection[0]]['Disabled'] = True
|
||||||
|
|
||||||
|
# Update details to include child devices
|
||||||
|
selected_disk = selection[-1]['Object']
|
||||||
|
selected_disk.update_details(skip_children=False)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return selected_disk
|
||||||
|
|
||||||
|
|
||||||
|
def select_disk_parts(prompt_msg, disk) -> list[Disk]:
|
||||||
|
"""Select disk parts from list, returns list of Disk()."""
|
||||||
|
title = ansi.color_string('ddrescue TUI: Partition Selection', 'GREEN')
|
||||||
|
title += f'\n\nDisk: {disk.path} {disk.description}'
|
||||||
|
menu = cli.Menu(title)
|
||||||
|
menu.separator = ' '
|
||||||
|
menu.add_action('All')
|
||||||
|
menu.add_action('None')
|
||||||
|
menu.add_action('Proceed', {'Separator': True})
|
||||||
|
menu.add_action('Quit')
|
||||||
|
object_list = []
|
||||||
|
|
||||||
|
def _select_parts(menu) -> None:
|
||||||
|
"""Loop over selection menu until at least one partition selected."""
|
||||||
|
while True:
|
||||||
|
selection = menu.advanced_select(
|
||||||
|
f'Please select the parts to {prompt_msg.lower()}: ',
|
||||||
|
)
|
||||||
|
if 'All' in selection:
|
||||||
|
for option in menu.options.values():
|
||||||
|
option['Selected'] = True
|
||||||
|
elif 'None' in selection:
|
||||||
|
for option in menu.options.values():
|
||||||
|
option['Selected'] = False
|
||||||
|
elif 'Proceed' in selection:
|
||||||
|
if any(option['Selected'] for option in menu.options.values()):
|
||||||
|
# At least one partition/device selected/device selected
|
||||||
|
break
|
||||||
|
elif 'Quit' in selection:
|
||||||
|
raise GenericAbort()
|
||||||
|
|
||||||
|
# Bail early if running under macOS
|
||||||
|
if PLATFORM == 'Darwin':
|
||||||
|
return [disk]
|
||||||
|
|
||||||
|
# Bail early if child device selected
|
||||||
|
if disk.parent:
|
||||||
|
return [disk]
|
||||||
|
|
||||||
|
# Add parts
|
||||||
|
whole_disk_str = f'{str(disk.path):<14} (Whole device)'
|
||||||
|
for part in disk.children:
|
||||||
|
fstype = part.get('fstype', '')
|
||||||
|
fstype = str(fstype) if fstype else ''
|
||||||
|
size = part["size"]
|
||||||
|
name = (
|
||||||
|
f'{str(part["path"]):<14} '
|
||||||
|
f'{fstype.upper():<5} '
|
||||||
|
f'({bytes_to_string(size, decimals=1, use_binary=True):>6})'
|
||||||
|
)
|
||||||
|
menu.add_option(name, details={'Selected': True, 'pathlib.Path': part['path']})
|
||||||
|
|
||||||
|
# Add whole disk if necessary
|
||||||
|
if not menu.options:
|
||||||
|
menu.add_option(whole_disk_str, {'Selected': True, 'pathlib.Path': disk.path})
|
||||||
|
menu.title += '\n\n'
|
||||||
|
menu.title += ansi.color_string(' No partitions detected.', 'YELLOW')
|
||||||
|
|
||||||
|
# Get selection
|
||||||
|
_select_parts(menu)
|
||||||
|
|
||||||
|
# Build list of Disk() object_list
|
||||||
|
for option in menu.options.values():
|
||||||
|
if option['Selected']:
|
||||||
|
object_list.append(option['pathlib.Path'])
|
||||||
|
|
||||||
|
# Check if whole disk selected
|
||||||
|
if len(object_list) == len(disk.children):
|
||||||
|
# NOTE: This is not true if the disk has no partitions
|
||||||
|
msg = f'Preserve partition table and unused space in {prompt_msg.lower()}?'
|
||||||
|
if cli.ask(msg):
|
||||||
|
# Replace part list with whole disk obj
|
||||||
|
object_list = [disk.path]
|
||||||
|
|
||||||
|
# Convert object_list to Disk() objects
|
||||||
|
cli.print_standard(' ')
|
||||||
|
cli.print_info('Getting disk/partition details...')
|
||||||
|
object_list = [Disk(path) for path in object_list]
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return object_list
|
||||||
|
|
||||||
|
|
||||||
|
def select_path(prompt_msg) -> pathlib.Path:
|
||||||
|
"""Select path, returns pathlib.Path."""
|
||||||
|
invalid = False
|
||||||
|
menu = cli.Menu(
|
||||||
|
title=ansi.color_string(f'ddrescue TUI: {prompt_msg} Path Selection', 'GREEN'),
|
||||||
|
)
|
||||||
|
menu.separator = ' '
|
||||||
|
menu.add_action('Quit')
|
||||||
|
menu.add_option('Current directory')
|
||||||
|
menu.add_option('Enter manually')
|
||||||
|
path = pathlib.Path.cwd()
|
||||||
|
|
||||||
|
# Make selection
|
||||||
|
selection = menu.simple_select()
|
||||||
|
if 'Current directory' in selection:
|
||||||
|
pass
|
||||||
|
elif 'Enter manually' in selection:
|
||||||
|
path = pathlib.Path(cli.input_text('Please enter path: '))
|
||||||
|
elif 'Quit' in selection:
|
||||||
|
raise GenericAbort()
|
||||||
|
|
||||||
|
# Check
|
||||||
|
try:
|
||||||
|
path = path.resolve()
|
||||||
|
except TypeError:
|
||||||
|
invalid = True
|
||||||
|
if invalid or not path.is_dir():
|
||||||
|
cli.print_error(f'Invalid path: {path}')
|
||||||
|
raise GenericAbort()
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
1068
scripts/wk/clone/state.py
Normal file
1068
scripts/wk/clone/state.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,21 +1,87 @@
|
||||||
"""WizardKit: Debug Functions"""
|
"""WizardKit: Debug Functions"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import lzma
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import pickle
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from wk.cfg.net import CRASH_SERVER
|
||||||
|
from wk.log import get_root_logger_path
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
class Debug():
|
class Debug():
|
||||||
"""Object used when dumping debug data."""
|
"""Object used when dumping debug data."""
|
||||||
def method(self):
|
def method(self) -> None:
|
||||||
"""Dummy method used to identify functions vs data."""
|
"""Dummy method used to identify functions vs data."""
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
DEBUG_CLASS = Debug()
|
DEBUG_CLASS = Debug()
|
||||||
METHOD_TYPE = type(DEBUG_CLASS.method)
|
METHOD_TYPE = type(DEBUG_CLASS.method)
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def generate_object_report(obj, indent=0):
|
def generate_debug_report() -> str:
|
||||||
|
"""Generate debug report, returns str."""
|
||||||
|
platform_function_list = (
|
||||||
|
'architecture',
|
||||||
|
'machine',
|
||||||
|
'platform',
|
||||||
|
'python_version',
|
||||||
|
)
|
||||||
|
report = []
|
||||||
|
|
||||||
|
# Logging data
|
||||||
|
try:
|
||||||
|
log_path = get_root_logger_path()
|
||||||
|
except RuntimeError:
|
||||||
|
# Assuming logging wasn't started
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
report.append('------ Start Log -------')
|
||||||
|
report.append('')
|
||||||
|
with open(log_path, 'r', encoding='utf-8') as log_file:
|
||||||
|
report.extend(log_file.read().splitlines())
|
||||||
|
report.append('')
|
||||||
|
report.append('------- End Log --------')
|
||||||
|
|
||||||
|
# System
|
||||||
|
report.append('--- Start debug info ---')
|
||||||
|
report.append('')
|
||||||
|
report.append('[System]')
|
||||||
|
report.append(f' {"FQDN":<24} {socket.getfqdn()}')
|
||||||
|
for func in platform_function_list:
|
||||||
|
func_name = func.replace('_', ' ').capitalize()
|
||||||
|
func_result = getattr(platform, func)()
|
||||||
|
report.append(f' {func_name:<24} {func_result}')
|
||||||
|
report.append(f' {"Python sys.argv":<24} {sys.argv}')
|
||||||
|
report.append('')
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
report.append('[Environment Variables]')
|
||||||
|
for key, value in sorted(os.environ.items()):
|
||||||
|
report.append(f' {key:<24} {value}')
|
||||||
|
report.append('')
|
||||||
|
|
||||||
|
# Done
|
||||||
|
report.append('---- End debug info ----')
|
||||||
|
return '\n'.join(report)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_object_report(obj: Any, indent: int = 0) -> list[str]:
|
||||||
"""Generate debug report for obj, returns list."""
|
"""Generate debug report for obj, returns list."""
|
||||||
report = []
|
report = []
|
||||||
attr_list = []
|
attr_list = []
|
||||||
|
|
@ -46,5 +112,78 @@ def generate_object_report(obj, indent=0):
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def save_pickles(
|
||||||
|
obj_dict: dict[Any, Any],
|
||||||
|
out_path: pathlib.Path | str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Save dict of objects using pickle."""
|
||||||
|
LOG.info('Saving pickles')
|
||||||
|
|
||||||
|
# Set path
|
||||||
|
if not out_path:
|
||||||
|
out_path = get_root_logger_path()
|
||||||
|
out_path = out_path.parent.joinpath('../debug').resolve()
|
||||||
|
|
||||||
|
# Save pickles
|
||||||
|
try:
|
||||||
|
for name, obj in obj_dict.copy().items():
|
||||||
|
if name.startswith('__') or inspect.ismodule(obj):
|
||||||
|
continue
|
||||||
|
with open(f'{out_path}/{name}.pickle', 'wb') as _f:
|
||||||
|
pickle.dump(obj, _f, protocol=pickle.HIGHEST_PROTOCOL)
|
||||||
|
except Exception:
|
||||||
|
LOG.error('Failed to save all the pickles', exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_debug_report(
|
||||||
|
report: str,
|
||||||
|
compress: bool = True,
|
||||||
|
reason: str = 'DEBUG',
|
||||||
|
) -> None:
|
||||||
|
"""Upload debug report to CRASH_SERVER as specified in wk.cfg.main."""
|
||||||
|
LOG.info('Uploading debug report to %s', CRASH_SERVER.get('Name', '?'))
|
||||||
|
headers = CRASH_SERVER.get('Headers', {'X-Requested-With': 'XMLHttpRequest'})
|
||||||
|
if compress:
|
||||||
|
headers['Content-Type'] = 'application/octet-stream'
|
||||||
|
|
||||||
|
# Check if the required server details are available
|
||||||
|
if not all(CRASH_SERVER.get(key, False) for key in ('Name', 'Url', 'User')):
|
||||||
|
msg = 'Server details missing, aborting upload.'
|
||||||
|
print(msg)
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
# Set filename (based on the logging config if possible)
|
||||||
|
filename = 'Unknown'
|
||||||
|
try:
|
||||||
|
log_path = get_root_logger_path()
|
||||||
|
except RuntimeError:
|
||||||
|
# Assuming logging wasn't started
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Strip everything but the prefix
|
||||||
|
filename = re.sub(r'^(.*)_(\d{4}-\d{2}-\d{2}.*)', r'\1', log_path.name)
|
||||||
|
filename = f'{filename}_{reason}_{time.strftime("%Y-%m-%d_%H%M%S%z")}.log'
|
||||||
|
LOG.debug('filename: %s', filename)
|
||||||
|
|
||||||
|
# Compress report
|
||||||
|
if compress:
|
||||||
|
filename += '.xz'
|
||||||
|
xz_report = lzma.compress(report.encode('utf8'))
|
||||||
|
|
||||||
|
# Upload report
|
||||||
|
url = f'{CRASH_SERVER["Url"]}/{filename}'
|
||||||
|
response = requests.put(
|
||||||
|
url,
|
||||||
|
auth=(CRASH_SERVER['User'], CRASH_SERVER.get('Pass', '')),
|
||||||
|
data=xz_report if compress else report,
|
||||||
|
headers=headers,
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check response
|
||||||
|
if not response.ok:
|
||||||
|
raise RuntimeError('Failed to upload report')
|
||||||
|
|
||||||
|
|
||||||
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.")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
"""WizardKit: Execution functions"""
|
"""WizardKit: Execution functions"""
|
||||||
#vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from threading import Thread
|
from io import IOBase
|
||||||
from queue import Queue, Empty
|
from queue import Queue, Empty
|
||||||
|
from threading import Thread
|
||||||
|
from typing import Any, Callable, Iterable
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
|
|
@ -25,11 +28,11 @@ class NonBlockingStreamReader():
|
||||||
## https://gist.github.com/EyalAr/7915597
|
## https://gist.github.com/EyalAr/7915597
|
||||||
## https://stackoverflow.com/a/4896288
|
## https://stackoverflow.com/a/4896288
|
||||||
|
|
||||||
def __init__(self, stream):
|
def __init__(self, stream: IOBase):
|
||||||
self.stream = stream
|
self.stream: IOBase = stream
|
||||||
self.queue = Queue()
|
self.queue: Queue = Queue()
|
||||||
|
|
||||||
def populate_queue(stream, queue):
|
def populate_queue(stream: IOBase, queue: Queue) -> None:
|
||||||
"""Collect lines from stream and put them in queue."""
|
"""Collect lines from stream and put them in queue."""
|
||||||
while not stream.closed:
|
while not stream.closed:
|
||||||
try:
|
try:
|
||||||
|
|
@ -45,18 +48,18 @@ class NonBlockingStreamReader():
|
||||||
args=(self.stream, self.queue),
|
args=(self.stream, self.queue),
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
"""Stop reading from input stream."""
|
"""Stop reading from input stream."""
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
|
|
||||||
def read(self, timeout=None):
|
def read(self, timeout: float | int | None = None) -> Any:
|
||||||
"""Read from queue if possible, returns item from queue."""
|
"""Read from queue if possible, returns item from queue."""
|
||||||
try:
|
try:
|
||||||
return self.queue.get(block=timeout is not None, timeout=timeout)
|
return self.queue.get(block=timeout is not None, timeout=timeout)
|
||||||
except Empty:
|
except Empty:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def save_to_file(self, proc, out_path):
|
def save_to_file(self, proc: subprocess.Popen, out_path: pathlib.Path | str) -> None:
|
||||||
"""Continuously save output to file while proc is running."""
|
"""Continuously save output to file while proc is running."""
|
||||||
LOG.debug('Saving process %s output to %s', proc, out_path)
|
LOG.debug('Saving process %s output to %s', proc, out_path)
|
||||||
while proc.poll() is None:
|
while proc.poll() is None:
|
||||||
|
|
@ -74,7 +77,12 @@ class NonBlockingStreamReader():
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def build_cmd_kwargs(cmd, minimized=False, pipe=True, shell=False, **kwargs):
|
def build_cmd_kwargs(
|
||||||
|
cmd: list[str],
|
||||||
|
minimized: bool = False,
|
||||||
|
pipe: bool = True,
|
||||||
|
shell: bool = False,
|
||||||
|
**kwargs) -> dict[str, Any]:
|
||||||
"""Build kwargs for use by subprocess functions, returns dict.
|
"""Build kwargs for use by subprocess functions, returns dict.
|
||||||
|
|
||||||
Specifically subprocess.run() and subprocess.Popen().
|
Specifically subprocess.run() and subprocess.Popen().
|
||||||
|
|
@ -91,7 +99,7 @@ def build_cmd_kwargs(cmd, minimized=False, pipe=True, shell=False, **kwargs):
|
||||||
|
|
||||||
# Strip sudo if appropriate
|
# Strip sudo if appropriate
|
||||||
if cmd[0] == 'sudo':
|
if cmd[0] == 'sudo':
|
||||||
if os.name == 'posix' and os.geteuid() == 0: # pylint: disable=no-member
|
if os.name == 'posix' and os.geteuid() == 0:
|
||||||
cmd.pop(0)
|
cmd.pop(0)
|
||||||
|
|
||||||
# Add additional kwargs if applicable
|
# Add additional kwargs if applicable
|
||||||
|
|
@ -106,8 +114,8 @@ def build_cmd_kwargs(cmd, minimized=False, pipe=True, shell=False, **kwargs):
|
||||||
|
|
||||||
# Start minimized
|
# Start minimized
|
||||||
if minimized:
|
if minimized:
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO() # type: ignore
|
||||||
startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW # type: ignore
|
||||||
startupinfo.wShowWindow = 6
|
startupinfo.wShowWindow = 6
|
||||||
cmd_kwargs['startupinfo'] = startupinfo
|
cmd_kwargs['startupinfo'] = startupinfo
|
||||||
|
|
||||||
|
|
@ -122,7 +130,12 @@ def build_cmd_kwargs(cmd, minimized=False, pipe=True, shell=False, **kwargs):
|
||||||
return cmd_kwargs
|
return cmd_kwargs
|
||||||
|
|
||||||
|
|
||||||
def get_json_from_command(cmd, check=True, encoding='utf-8', errors='ignore'):
|
def get_json_from_command(
|
||||||
|
cmd: list[str],
|
||||||
|
check: bool = True,
|
||||||
|
encoding: str = 'utf-8',
|
||||||
|
errors: str = 'ignore',
|
||||||
|
) -> dict[Any, Any]:
|
||||||
"""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
|
||||||
|
|
@ -141,7 +154,11 @@ def get_json_from_command(cmd, check=True, encoding='utf-8', errors='ignore'):
|
||||||
return json_data
|
return json_data
|
||||||
|
|
||||||
|
|
||||||
def get_procs(name, exact=True, try_again=True):
|
def get_procs(
|
||||||
|
name: str,
|
||||||
|
exact: bool = True,
|
||||||
|
try_again: bool = True,
|
||||||
|
) -> list[psutil.Process]:
|
||||||
"""Get process object(s) based on name, returns list of proc objects."""
|
"""Get process object(s) based on name, returns list of proc objects."""
|
||||||
LOG.debug('name: %s, exact: %s', name, exact)
|
LOG.debug('name: %s, exact: %s', name, exact)
|
||||||
processes = []
|
processes = []
|
||||||
|
|
@ -161,7 +178,12 @@ def get_procs(name, exact=True, try_again=True):
|
||||||
return processes
|
return processes
|
||||||
|
|
||||||
|
|
||||||
def kill_procs(name, exact=True, force=False, timeout=30):
|
def kill_procs(
|
||||||
|
name: str,
|
||||||
|
exact: bool = True,
|
||||||
|
force: bool = False,
|
||||||
|
timeout: float | int = 30,
|
||||||
|
) -> None:
|
||||||
"""Kill all processes matching name (case-insensitively).
|
"""Kill all processes matching name (case-insensitively).
|
||||||
|
|
||||||
NOTE: Under Posix systems this will send SIGINT to allow processes
|
NOTE: Under Posix systems this will send SIGINT to allow processes
|
||||||
|
|
@ -185,7 +207,13 @@ def kill_procs(name, exact=True, force=False, timeout=30):
|
||||||
proc.kill()
|
proc.kill()
|
||||||
|
|
||||||
|
|
||||||
def popen_program(cmd, minimized=False, pipe=False, shell=False, **kwargs):
|
def popen_program(
|
||||||
|
cmd: list[str],
|
||||||
|
minimized: bool = False,
|
||||||
|
pipe: bool = False,
|
||||||
|
shell: bool = False,
|
||||||
|
**kwargs,
|
||||||
|
) -> subprocess.Popen:
|
||||||
"""Run program and return a subprocess.Popen object."""
|
"""Run program and return a subprocess.Popen object."""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
'cmd: %s, minimized: %s, pipe: %s, shell: %s',
|
'cmd: %s, minimized: %s, pipe: %s, shell: %s',
|
||||||
|
|
@ -209,7 +237,13 @@ def popen_program(cmd, minimized=False, pipe=False, shell=False, **kwargs):
|
||||||
return proc
|
return proc
|
||||||
|
|
||||||
|
|
||||||
def run_program(cmd, check=True, pipe=True, shell=False, **kwargs):
|
def run_program(
|
||||||
|
cmd: list[str],
|
||||||
|
check: bool = True,
|
||||||
|
pipe: bool = True,
|
||||||
|
shell: bool = False,
|
||||||
|
**kwargs,
|
||||||
|
) -> subprocess.CompletedProcess:
|
||||||
"""Run program and return a subprocess.CompletedProcess object."""
|
"""Run program and return a subprocess.CompletedProcess object."""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
'cmd: %s, check: %s, pipe: %s, shell: %s',
|
'cmd: %s, check: %s, pipe: %s, shell: %s',
|
||||||
|
|
@ -222,8 +256,9 @@ def run_program(cmd, check=True, pipe=True, shell=False, **kwargs):
|
||||||
pipe=pipe,
|
pipe=pipe,
|
||||||
shell=shell,
|
shell=shell,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
check = cmd_kwargs.pop('check', True) # Avoids linting warning
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(**cmd_kwargs)
|
proc = subprocess.run(check=check, **cmd_kwargs)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
LOG.error('Command not found: %s', cmd)
|
LOG.error('Command not found: %s', cmd)
|
||||||
raise
|
raise
|
||||||
|
|
@ -233,7 +268,11 @@ def run_program(cmd, check=True, pipe=True, shell=False, **kwargs):
|
||||||
return proc
|
return proc
|
||||||
|
|
||||||
|
|
||||||
def start_thread(function, args=None, daemon=True):
|
def start_thread(
|
||||||
|
function: Callable,
|
||||||
|
args: Iterable[Any] | None = None,
|
||||||
|
daemon: bool = True,
|
||||||
|
) -> Thread:
|
||||||
"""Run function as thread in background, returns Thread object."""
|
"""Run function as thread in background, returns Thread object."""
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
'Starting background thread for function: %s, args: %s, daemon: %s',
|
'Starting background thread for function: %s, args: %s, daemon: %s',
|
||||||
|
|
@ -245,7 +284,7 @@ def start_thread(function, args=None, daemon=True):
|
||||||
return thread
|
return thread
|
||||||
|
|
||||||
|
|
||||||
def stop_process(proc, graceful=True):
|
def stop_process(proc: subprocess.Popen, graceful: bool = True) -> None:
|
||||||
"""Stop process.
|
"""Stop process.
|
||||||
|
|
||||||
NOTES: proc should be a subprocess.Popen obj.
|
NOTES: proc should be a subprocess.Popen obj.
|
||||||
|
|
@ -254,20 +293,24 @@ def stop_process(proc, graceful=True):
|
||||||
|
|
||||||
# Graceful exit
|
# Graceful exit
|
||||||
if graceful:
|
if graceful:
|
||||||
if os.name == 'posix' and os.geteuid() != 0: # pylint: disable=no-member
|
if os.name == 'posix' and os.geteuid() != 0:
|
||||||
run_program(['sudo', 'kill', str(proc.pid)], check=False)
|
run_program(['sudo', 'kill', str(proc.pid)], check=False)
|
||||||
else:
|
else:
|
||||||
proc.terminate()
|
proc.terminate()
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
# Force exit
|
# Force exit
|
||||||
if os.name == 'posix' and os.geteuid() != 0: # pylint: disable=no-member
|
if os.name == 'posix' and os.geteuid() != 0:
|
||||||
run_program(['sudo', 'kill', '-9', str(proc.pid)], check=False)
|
run_program(['sudo', 'kill', '-9', str(proc.pid)], check=False)
|
||||||
else:
|
else:
|
||||||
proc.kill()
|
proc.kill()
|
||||||
|
|
||||||
|
|
||||||
def wait_for_procs(name, exact=True, timeout=None):
|
def wait_for_procs(
|
||||||
|
name: str,
|
||||||
|
exact: bool = True,
|
||||||
|
timeout: float | int | None = None,
|
||||||
|
) -> None:
|
||||||
"""Wait for all process matching name."""
|
"""Wait for all process matching name."""
|
||||||
LOG.debug('name: %s, exact: %s, timeout: %s', name, exact, timeout)
|
LOG.debug('name: %s, exact: %s, timeout: %s', name, exact, timeout)
|
||||||
target_procs = get_procs(name, exact=exact)
|
target_procs = get_procs(name, exact=exact)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from wk.std import color_string
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -33,7 +33,10 @@ THRESH_GREAT = 750 * 1024**2
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def generate_horizontal_graph(rate_list, graph_width=40, oneline=False):
|
def generate_horizontal_graph(
|
||||||
|
rate_list: list[float],
|
||||||
|
graph_width: int = 40,
|
||||||
|
oneline: bool = False) -> list[str]:
|
||||||
"""Generate horizontal graph from rate_list, returns list."""
|
"""Generate horizontal graph from rate_list, returns list."""
|
||||||
graph = ['', '', '', '']
|
graph = ['', '', '', '']
|
||||||
scale = 8 if oneline else 32
|
scale = 8 if oneline else 32
|
||||||
|
|
@ -52,27 +55,27 @@ def generate_horizontal_graph(rate_list, graph_width=40, oneline=False):
|
||||||
rate_color = 'GREEN'
|
rate_color = 'GREEN'
|
||||||
|
|
||||||
# Build graph
|
# Build graph
|
||||||
full_block = color_string((GRAPH_HORIZONTAL[-1],), (rate_color,))
|
full_block = ansi.color_string((GRAPH_HORIZONTAL[-1],), (rate_color,))
|
||||||
if step >= 24:
|
if step >= 24:
|
||||||
graph[0] += color_string((GRAPH_HORIZONTAL[step-24],), (rate_color,))
|
graph[0] += ansi.color_string((GRAPH_HORIZONTAL[step-24],), (rate_color,))
|
||||||
graph[1] += full_block
|
graph[1] += full_block
|
||||||
graph[2] += full_block
|
graph[2] += full_block
|
||||||
graph[3] += full_block
|
graph[3] += full_block
|
||||||
elif step >= 16:
|
elif step >= 16:
|
||||||
graph[0] += ' '
|
graph[0] += ' '
|
||||||
graph[1] += color_string((GRAPH_HORIZONTAL[step-16],), (rate_color,))
|
graph[1] += ansi.color_string((GRAPH_HORIZONTAL[step-16],), (rate_color,))
|
||||||
graph[2] += full_block
|
graph[2] += full_block
|
||||||
graph[3] += full_block
|
graph[3] += full_block
|
||||||
elif step >= 8:
|
elif step >= 8:
|
||||||
graph[0] += ' '
|
graph[0] += ' '
|
||||||
graph[1] += ' '
|
graph[1] += ' '
|
||||||
graph[2] += color_string((GRAPH_HORIZONTAL[step-8],), (rate_color,))
|
graph[2] += ansi.color_string((GRAPH_HORIZONTAL[step-8],), (rate_color,))
|
||||||
graph[3] += full_block
|
graph[3] += full_block
|
||||||
else:
|
else:
|
||||||
graph[0] += ' '
|
graph[0] += ' '
|
||||||
graph[1] += ' '
|
graph[1] += ' '
|
||||||
graph[2] += ' '
|
graph[2] += ' '
|
||||||
graph[3] += color_string((GRAPH_HORIZONTAL[step],), (rate_color,))
|
graph[3] += ansi.color_string((GRAPH_HORIZONTAL[step],), (rate_color,))
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
if oneline:
|
if oneline:
|
||||||
|
|
@ -80,7 +83,7 @@ def generate_horizontal_graph(rate_list, graph_width=40, oneline=False):
|
||||||
return graph
|
return graph
|
||||||
|
|
||||||
|
|
||||||
def get_graph_step(rate, scale=16):
|
def get_graph_step(rate: float, scale: int = 16) -> int:
|
||||||
"""Get graph step based on rate and scale, returns int."""
|
"""Get graph step based on rate and scale, returns int."""
|
||||||
rate_in_mb = rate / (1024**2)
|
rate_in_mb = rate / (1024**2)
|
||||||
step = 0
|
step = 0
|
||||||
|
|
@ -95,14 +98,17 @@ def get_graph_step(rate, scale=16):
|
||||||
return step
|
return step
|
||||||
|
|
||||||
|
|
||||||
def merge_rates(rates, graph_width=40):
|
def merge_rates(
|
||||||
|
rates: list[float],
|
||||||
|
graph_width: int = 40,
|
||||||
|
) -> list[int | float]:
|
||||||
"""Merge rates to have entries equal to the width, returns list."""
|
"""Merge rates to have entries equal to the width, returns list."""
|
||||||
merged_rates = []
|
merged_rates = []
|
||||||
offset = 0
|
offset = 0
|
||||||
slice_width = int(len(rates) / graph_width)
|
slice_width = int(len(rates) / graph_width)
|
||||||
|
|
||||||
# Merge rates
|
# Merge rates
|
||||||
for _i in range(graph_width):
|
for _ in range(graph_width):
|
||||||
merged_rates.append(sum(rates[offset:offset+slice_width])/slice_width)
|
merged_rates.append(sum(rates[offset:offset+slice_width])/slice_width)
|
||||||
offset += slice_width
|
offset += slice_width
|
||||||
|
|
||||||
|
|
@ -110,7 +116,7 @@ def merge_rates(rates, graph_width=40):
|
||||||
return merged_rates
|
return merged_rates
|
||||||
|
|
||||||
|
|
||||||
def vertical_graph_line(percent, rate, scale=32):
|
def vertical_graph_line(percent: float, rate: float, scale: int = 32) -> str:
|
||||||
"""Build colored graph string using thresholds, returns str."""
|
"""Build colored graph string using thresholds, returns str."""
|
||||||
color_bar = None
|
color_bar = None
|
||||||
color_rate = None
|
color_rate = None
|
||||||
|
|
@ -128,7 +134,7 @@ def vertical_graph_line(percent, rate, scale=32):
|
||||||
color_rate = 'GREEN'
|
color_rate = 'GREEN'
|
||||||
|
|
||||||
# Build string
|
# Build string
|
||||||
line = color_string(
|
line = ansi.color_string(
|
||||||
strings=(
|
strings=(
|
||||||
f'{percent:5.1f}%',
|
f'{percent:5.1f}%',
|
||||||
f'{GRAPH_VERTICAL[step]:<4}',
|
f'{GRAPH_VERTICAL[step]:<4}',
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,8 @@ from wk.cfg.hw import (
|
||||||
THRESH_SSD_MIN,
|
THRESH_SSD_MIN,
|
||||||
)
|
)
|
||||||
from wk.exe import run_program
|
from wk.exe import run_program
|
||||||
from wk.std import (
|
from wk.std import PLATFORM
|
||||||
PLATFORM,
|
from wk.ui import ansi
|
||||||
strip_colors,
|
|
||||||
color_string,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -116,7 +113,7 @@ def check_io_results(test_obj, rate_list, graph_width) -> None:
|
||||||
|
|
||||||
# Add horizontal graph to report
|
# Add horizontal graph to report
|
||||||
for line in graph.generate_horizontal_graph(rate_list, graph_width):
|
for line in graph.generate_horizontal_graph(rate_list, graph_width):
|
||||||
if not strip_colors(line).strip():
|
if not ansi.strip_colors(line).strip():
|
||||||
# Skip empty lines
|
# Skip empty lines
|
||||||
continue
|
continue
|
||||||
test_obj.report.append(line)
|
test_obj.report.append(line)
|
||||||
|
|
@ -154,7 +151,7 @@ def run_io_test(test_obj, log_path, test_mode=False) -> None:
|
||||||
LOG.info('Using %s for better performance', dev_path)
|
LOG.info('Using %s for better performance', dev_path)
|
||||||
offset = 0
|
offset = 0
|
||||||
read_rates = []
|
read_rates = []
|
||||||
test_obj.report.append(color_string('I/O Benchmark', 'BLUE'))
|
test_obj.report.append(ansi.color_string('I/O Benchmark', 'BLUE'))
|
||||||
|
|
||||||
# Get dd values or bail
|
# Get dd values or bail
|
||||||
try:
|
try:
|
||||||
|
|
@ -162,7 +159,7 @@ def run_io_test(test_obj, log_path, test_mode=False) -> None:
|
||||||
except DeviceTooSmallError:
|
except DeviceTooSmallError:
|
||||||
test_obj.set_status('N/A')
|
test_obj.set_status('N/A')
|
||||||
test_obj.report.append(
|
test_obj.report.append(
|
||||||
color_string('Disk too small to test', 'YELLOW'),
|
ansi.color_string('Disk too small to test', 'YELLOW'),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,10 @@ import subprocess
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
|
|
||||||
from wk import exe
|
from wk import exe
|
||||||
from wk.cfg.hw import CPU_FAILURE_TEMP
|
from wk.cfg.hw import CPU_TEMPS
|
||||||
from wk.os.mac import set_fans as macos_set_fans
|
from wk.os.mac import set_fans as macos_set_fans
|
||||||
from wk.std import (
|
from wk.std import PLATFORM
|
||||||
PLATFORM,
|
from wk.ui import ansi
|
||||||
color_string,
|
|
||||||
print_error,
|
|
||||||
print_warning,
|
|
||||||
)
|
|
||||||
from wk.tmux import respawn_pane as tmux_respawn_pane
|
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -25,32 +20,75 @@ SysbenchType = tuple[subprocess.Popen, TextIO]
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def check_cooling_results(test_obj, sensors, run_sysbench=False) -> None:
|
def check_cooling_results(sensors, test_object) -> None:
|
||||||
"""Check cooling results and update test_obj."""
|
"""Check cooling result via sensor data."""
|
||||||
max_temp = sensors.cpu_max_temp()
|
idle_temp = sensors.get_cpu_temp('Idle')
|
||||||
temp_labels = ['Idle', 'Max', 'Cooldown']
|
cooldown_temp = sensors.get_cpu_temp('Cooldown')
|
||||||
if run_sysbench:
|
max_temp = sensors.get_cpu_temp('Max')
|
||||||
temp_labels.append('Sysbench')
|
test_object.report.append(ansi.color_string('Temps', 'BLUE'))
|
||||||
|
|
||||||
# Check temps
|
# Check temps
|
||||||
if not max_temp:
|
if max_temp > CPU_TEMPS['Critical']:
|
||||||
test_obj.set_status('Unknown')
|
test_object.failed = True
|
||||||
elif max_temp >= CPU_FAILURE_TEMP:
|
test_object.set_status('Failed')
|
||||||
test_obj.failed = True
|
test_object.report.extend([
|
||||||
test_obj.set_status('Failed')
|
ansi.color_string(
|
||||||
elif 'Aborted' not in test_obj.status:
|
f' WARNING: Critical CPU temp of {CPU_TEMPS["Critical"]} exceeded.',
|
||||||
test_obj.passed = True
|
'RED',
|
||||||
test_obj.set_status('Passed')
|
),
|
||||||
|
'',
|
||||||
|
])
|
||||||
|
elif idle_temp >= CPU_TEMPS['Idle High']:
|
||||||
|
test_object.failed = True
|
||||||
|
test_object.set_status('Failed')
|
||||||
|
test_object.report.extend([
|
||||||
|
ansi.color_string(
|
||||||
|
f' WARNING: Max idle temp of {CPU_TEMPS["Idle High"]} exceeded.',
|
||||||
|
'YELLOW',
|
||||||
|
),
|
||||||
|
'',
|
||||||
|
])
|
||||||
|
elif (
|
||||||
|
cooldown_temp <= CPU_TEMPS['Cooling Low Cutoff']
|
||||||
|
or max_temp - cooldown_temp >= CPU_TEMPS['Cooling Delta']
|
||||||
|
):
|
||||||
|
test_object.passed = True
|
||||||
|
test_object.set_status('Passed')
|
||||||
|
else:
|
||||||
|
test_object.passed = False
|
||||||
|
test_object.set_status('Unknown')
|
||||||
|
if cooldown_temp - idle_temp >= CPU_TEMPS['Idle Delta']:
|
||||||
|
test_object.report.extend([
|
||||||
|
ansi.color_string(
|
||||||
|
f' WARNING: Cooldown temp at least {CPU_TEMPS["Idle Delta"]}° over idle.',
|
||||||
|
'YELLOW',
|
||||||
|
),
|
||||||
|
'',
|
||||||
|
])
|
||||||
|
|
||||||
# Add temps to report
|
# Build report
|
||||||
for line in sensors.generate_report(*temp_labels, only_cpu=True):
|
report_labels = ['Idle']
|
||||||
test_obj.report.append(f' {line}')
|
average_labels = []
|
||||||
|
if 'Sysbench' in sensors.temp_labels:
|
||||||
|
average_labels.append('Sysbench')
|
||||||
|
report_labels.extend(['Sysbench', 'Cooldown'])
|
||||||
|
if 'Prime95' in sensors.temp_labels:
|
||||||
|
average_labels.append('Prime95')
|
||||||
|
report_labels.append('Prime95')
|
||||||
|
if 'Cooldown' not in report_labels:
|
||||||
|
report_labels.append('Cooldown')
|
||||||
|
if len(sensors.temp_labels.intersection(['Prime95', 'Sysbench'])) < 1:
|
||||||
|
# Include overall max temp if needed
|
||||||
|
report_labels.append('Max')
|
||||||
|
for line in sensors.generate_report(
|
||||||
|
*report_labels, only_cpu=True, include_avg_for=average_labels):
|
||||||
|
test_object.report.append(f' {line}')
|
||||||
|
|
||||||
|
|
||||||
def check_mprime_results(test_obj, working_dir) -> None:
|
def check_mprime_results(test_obj, working_dir) -> None:
|
||||||
"""Check mprime log files and update test_obj."""
|
"""Check mprime log files and update test_obj."""
|
||||||
passing_lines = {}
|
passing_lines = set()
|
||||||
warning_lines = {}
|
warning_lines = set()
|
||||||
|
|
||||||
def _read_file(log_name) -> list[str]:
|
def _read_file(log_name) -> list[str]:
|
||||||
"""Read file and split into lines, returns list."""
|
"""Read file and split into lines, returns list."""
|
||||||
|
|
@ -68,9 +106,9 @@ def check_mprime_results(test_obj, working_dir) -> None:
|
||||||
for line in _read_file('results.txt'):
|
for line in _read_file('results.txt'):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if re.search(r'(error|fail)', line, re.IGNORECASE):
|
if re.search(r'(error|fail)', line, re.IGNORECASE):
|
||||||
warning_lines[line] = None
|
warning_lines.add(line)
|
||||||
|
|
||||||
# print.log (check if passed)
|
# prime.log (check if passed)
|
||||||
for line in _read_file('prime.log'):
|
for line in _read_file('prime.log'):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
match = re.search(
|
match = re.search(
|
||||||
|
|
@ -78,10 +116,10 @@ def check_mprime_results(test_obj, working_dir) -> None:
|
||||||
if match:
|
if match:
|
||||||
if int(match.group(2)) + int(match.group(3)) > 0:
|
if int(match.group(2)) + int(match.group(3)) > 0:
|
||||||
# Errors and/or warnings encountered
|
# Errors and/or warnings encountered
|
||||||
warning_lines[match.group(1).capitalize()] = None
|
warning_lines.add(match.group(1).capitalize())
|
||||||
else:
|
else:
|
||||||
# No errors/warnings
|
# No errors/warnings
|
||||||
passing_lines[match.group(1).capitalize()] = None
|
passing_lines.add(match.group(1).capitalize())
|
||||||
|
|
||||||
# Update status
|
# Update status
|
||||||
if warning_lines:
|
if warning_lines:
|
||||||
|
|
@ -97,29 +135,31 @@ def check_mprime_results(test_obj, working_dir) -> None:
|
||||||
for line in passing_lines:
|
for line in passing_lines:
|
||||||
test_obj.report.append(f' {line}')
|
test_obj.report.append(f' {line}')
|
||||||
for line in warning_lines:
|
for line in warning_lines:
|
||||||
test_obj.report.append(color_string(f' {line}', 'YELLOW'))
|
test_obj.report.append(ansi.color_string(f' {line}', 'YELLOW'))
|
||||||
if not (passing_lines or warning_lines):
|
if not (passing_lines or warning_lines):
|
||||||
test_obj.report.append(color_string(' Unknown result', 'YELLOW'))
|
test_obj.report.append(ansi.color_string(' Unknown result', 'YELLOW'))
|
||||||
|
|
||||||
|
|
||||||
def start_mprime(working_dir, log_path) -> subprocess.Popen:
|
def start_mprime(working_dir, log_path) -> subprocess.Popen:
|
||||||
"""Start mprime and save filtered output to log, returns Popen object."""
|
"""Start mprime and save filtered output to log, returns Popen object."""
|
||||||
set_apple_fan_speed('max')
|
set_apple_fan_speed('max')
|
||||||
proc_mprime = subprocess.Popen( # pylint: disable=consider-using-with
|
proc_mprime = subprocess.Popen(
|
||||||
['mprime', '-t'],
|
['mprime', '-t'],
|
||||||
cwd=working_dir,
|
cwd=working_dir,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
)
|
)
|
||||||
proc_grep = subprocess.Popen( # pylint: disable=consider-using-with
|
proc_grep = subprocess.Popen(
|
||||||
'grep --ignore-case --invert-match --line-buffered stress.txt'.split(),
|
'grep --ignore-case --invert-match --line-buffered stress.txt'.split(),
|
||||||
stdin=proc_mprime.stdout,
|
stdin=proc_mprime.stdout,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
proc_mprime.stdout.close()
|
proc_mprime.stdout.close() # type: ignore[reportOptionalMemberAccess]
|
||||||
save_nsbr = exe.NonBlockingStreamReader(proc_grep.stdout)
|
save_nbsr = exe.NonBlockingStreamReader(
|
||||||
|
proc_grep.stdout, # type: ignore[reportGeneralTypeIssues]
|
||||||
|
)
|
||||||
exe.start_thread(
|
exe.start_thread(
|
||||||
save_nsbr.save_to_file,
|
save_nbsr.save_to_file,
|
||||||
args=(proc_grep, log_path),
|
args=(proc_grep, log_path),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -127,38 +167,6 @@ def start_mprime(working_dir, log_path) -> subprocess.Popen:
|
||||||
return proc_mprime
|
return proc_mprime
|
||||||
|
|
||||||
|
|
||||||
def start_sysbench(sensors, sensors_out, log_path, pane) -> SysbenchType:
|
|
||||||
"""Start sysbench, returns tuple with Popen object and file handle."""
|
|
||||||
set_apple_fan_speed('max')
|
|
||||||
sysbench_cmd = [
|
|
||||||
'sysbench',
|
|
||||||
f'--threads={exe.psutil.cpu_count()}',
|
|
||||||
'--cpu-max-prime=1000000000',
|
|
||||||
'cpu',
|
|
||||||
'run',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Restart background monitor for Sysbench
|
|
||||||
sensors.stop_background_monitor()
|
|
||||||
sensors.start_background_monitor(
|
|
||||||
sensors_out,
|
|
||||||
alt_max='Sysbench',
|
|
||||||
thermal_action=('killall', 'sysbench', '-INT'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update bottom pane
|
|
||||||
tmux_respawn_pane(pane, watch_file=log_path, watch_cmd='tail')
|
|
||||||
|
|
||||||
# Start sysbench
|
|
||||||
filehandle_sysbench = open( # pylint: disable=consider-using-with
|
|
||||||
log_path, 'a', encoding='utf-8',
|
|
||||||
)
|
|
||||||
proc_sysbench = exe.popen_program(sysbench_cmd, stdout=filehandle_sysbench)
|
|
||||||
|
|
||||||
# Done
|
|
||||||
return (proc_sysbench, filehandle_sysbench)
|
|
||||||
|
|
||||||
|
|
||||||
def set_apple_fan_speed(speed) -> None:
|
def set_apple_fan_speed(speed) -> None:
|
||||||
"""Set Apple fan speed."""
|
"""Set Apple fan speed."""
|
||||||
cmd = None
|
cmd = None
|
||||||
|
|
@ -174,14 +182,35 @@ def set_apple_fan_speed(speed) -> None:
|
||||||
except (RuntimeError, ValueError, subprocess.CalledProcessError) as err:
|
except (RuntimeError, ValueError, subprocess.CalledProcessError) as err:
|
||||||
LOG.error('Failed to set fans to %s', speed)
|
LOG.error('Failed to set fans to %s', speed)
|
||||||
LOG.error('Error: %s', err)
|
LOG.error('Error: %s', err)
|
||||||
print_error(f'Failed to set fans to {speed}')
|
#ui.print_error(f'Failed to set fans to {speed}')
|
||||||
for line in str(err).splitlines():
|
#for line in str(err).splitlines():
|
||||||
print_warning(f' {line.strip()}')
|
# ui.print_warning(f' {line.strip()}')
|
||||||
elif PLATFORM == 'Linux':
|
elif PLATFORM == 'Linux':
|
||||||
cmd = ['apple-fans', speed]
|
cmd = ['apple-fans', speed]
|
||||||
exe.run_program(cmd, check=False)
|
exe.run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def start_sysbench(log_path) -> SysbenchType:
|
||||||
|
"""Start sysbench, returns tuple with Popen object and file handle."""
|
||||||
|
set_apple_fan_speed('max')
|
||||||
|
cmd = [
|
||||||
|
'sysbench',
|
||||||
|
f'--threads={exe.psutil.cpu_count()}',
|
||||||
|
'--cpu-max-prime=1000000000',
|
||||||
|
'cpu',
|
||||||
|
'run',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Start sysbench
|
||||||
|
filehandle = open(
|
||||||
|
log_path, 'a', encoding='utf-8',
|
||||||
|
)
|
||||||
|
proc = exe.popen_program(cmd, stdout=filehandle)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return (proc, filehandle)
|
||||||
|
|
||||||
|
|
||||||
def stop_mprime(proc_mprime) -> None:
|
def stop_mprime(proc_mprime) -> None:
|
||||||
"""Stop mprime gracefully, then forcefully as needed."""
|
"""Stop mprime gracefully, then forcefully as needed."""
|
||||||
proc_mprime.terminate()
|
proc_mprime.terminate()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,6 @@
|
||||||
"""WizardKit: Disk object and functions"""
|
"""WizardKit: Disk object and functions"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
import copy
|
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
|
|
@ -9,17 +8,17 @@ import plistlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Union
|
from typing import Any
|
||||||
|
|
||||||
from wk.cfg.main import KIT_NAME_SHORT
|
from wk.cfg.main import KIT_NAME_SHORT
|
||||||
from wk.cfg.python import DATACLASS_DECORATOR_KWARGS
|
|
||||||
from wk.exe import get_json_from_command, run_program
|
from wk.exe import get_json_from_command, run_program
|
||||||
from wk.hw.test import Test
|
from wk.hw.test import Test
|
||||||
from wk.hw.smart import (
|
from wk.hw.smart import (
|
||||||
generate_attribute_report,
|
generate_attribute_report,
|
||||||
get_known_disk_attributes,
|
get_known_disk_attributes,
|
||||||
)
|
)
|
||||||
from wk.std import PLATFORM, color_string, strip_colors
|
from wk.std import PLATFORM
|
||||||
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -31,7 +30,7 @@ WK_LABEL_REGEX = re.compile(
|
||||||
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
@dataclass(**DATACLASS_DECORATOR_KWARGS)
|
@dataclass(slots=True)
|
||||||
class Disk:
|
class Disk:
|
||||||
"""Object for tracking disk specific data."""
|
"""Object for tracking disk specific data."""
|
||||||
attributes: dict[Any, dict] = field(init=False, default_factory=dict)
|
attributes: dict[Any, dict] = field(init=False, default_factory=dict)
|
||||||
|
|
@ -39,41 +38,36 @@ class Disk:
|
||||||
children: list[dict] = field(init=False, default_factory=list)
|
children: list[dict] = field(init=False, default_factory=list)
|
||||||
description: str = field(init=False)
|
description: str = field(init=False)
|
||||||
filesystem: str = field(init=False)
|
filesystem: str = field(init=False)
|
||||||
initial_attributes: dict[Any, dict] = field(init=False)
|
initial_attributes: dict[Any, dict] = field(init=False, default_factory=dict)
|
||||||
known_attributes: dict[Any, dict] = field(init=False, default_factory=dict)
|
known_attributes: dict[Any, dict] = field(init=False, default_factory=dict)
|
||||||
log_sec: int = field(init=False)
|
log_sec: int = field(init=False)
|
||||||
model: str = field(init=False)
|
model: str = field(init=False)
|
||||||
name: str = field(init=False)
|
name: str = field(init=False)
|
||||||
notes: list[str] = field(init=False, default_factory=list)
|
notes: list[str] = field(init=False, default_factory=list)
|
||||||
path: Union[pathlib.Path, str]
|
path: pathlib.Path = field(init=False)
|
||||||
|
path_str: pathlib.Path | str
|
||||||
parent: str = field(init=False)
|
parent: str = field(init=False)
|
||||||
phy_sec: int = field(init=False)
|
phy_sec: int = field(init=False)
|
||||||
raw_details: dict[str, Any] = field(init=False)
|
raw_details: dict[str, Any] = field(init=False)
|
||||||
raw_smartctl: dict[str, Any] = field(init=False)
|
raw_smartctl: dict[str, Any] = field(init=False, default_factory=dict)
|
||||||
serial: str = field(init=False)
|
serial: str = field(init=False)
|
||||||
size: int = field(init=False)
|
size: int = field(init=False)
|
||||||
ssd: bool = field(init=False)
|
ssd: bool = field(init=False)
|
||||||
tests: list[Test] = field(init=False, default_factory=list)
|
tests: list[Test] = field(init=False, default_factory=list)
|
||||||
use_sat: bool = field(init=False, default=False)
|
trim: bool = field(init=False)
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self):
|
||||||
self.path = pathlib.Path(self.path).resolve()
|
self.path = pathlib.Path(self.path_str).resolve()
|
||||||
self.update_details()
|
self.update_details()
|
||||||
self.set_description()
|
self.set_description()
|
||||||
self.known_attributes = get_known_disk_attributes(self.model)
|
self.known_attributes = get_known_disk_attributes(self.model)
|
||||||
if not self.attributes and self.bus == 'USB':
|
|
||||||
# Try using SAT
|
|
||||||
LOG.warning('Using SAT for smartctl for %s', self.path)
|
|
||||||
self.notes = []
|
|
||||||
self.use_sat = True
|
|
||||||
self.initial_attributes = copy.deepcopy(self.attributes)
|
|
||||||
if not self.is_4k_aligned():
|
if not self.is_4k_aligned():
|
||||||
self.add_note('One or more partitions are not 4K aligned', 'YELLOW')
|
self.add_note('One or more partitions are not 4K aligned', 'YELLOW')
|
||||||
|
|
||||||
def add_note(self, note, color=None) -> None:
|
def add_note(self, note, color=None) -> None:
|
||||||
"""Add note that will be included in the disk report."""
|
"""Add note that will be included in the disk report."""
|
||||||
if color:
|
if color:
|
||||||
note = color_string(note, color)
|
note = ansi.color_string(note, color)
|
||||||
if note not in self.notes:
|
if note not in self.notes:
|
||||||
self.notes.append(note)
|
self.notes.append(note)
|
||||||
self.notes.sort()
|
self.notes.sort()
|
||||||
|
|
@ -82,7 +76,7 @@ class Disk:
|
||||||
"""Check if note is already present."""
|
"""Check if note is already present."""
|
||||||
present = False
|
present = False
|
||||||
for note in self.notes:
|
for note in self.notes:
|
||||||
if note_str == strip_colors(note):
|
if note_str == ansi.strip_colors(note):
|
||||||
present = True
|
present = True
|
||||||
return present
|
return present
|
||||||
|
|
||||||
|
|
@ -98,18 +92,18 @@ class Disk:
|
||||||
"""Generate Disk report, returns list."""
|
"""Generate Disk report, returns list."""
|
||||||
report = []
|
report = []
|
||||||
if header:
|
if header:
|
||||||
report.append(color_string(f'Device ({self.path.name})', 'BLUE'))
|
report.append(ansi.color_string(f'Device ({self.path.name})', 'BLUE'))
|
||||||
report.append(f' {self.description}')
|
report.append(f' {self.description}')
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
if self.attributes:
|
if self.attributes:
|
||||||
if header:
|
if header:
|
||||||
report.append(color_string('Attributes', 'BLUE'))
|
report.append(ansi.color_string('Attributes', 'BLUE'))
|
||||||
report.extend(generate_attribute_report(self))
|
report.extend(generate_attribute_report(self))
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
if self.notes:
|
if self.notes:
|
||||||
report.append(color_string('Notes', 'BLUE'))
|
report.append(ansi.color_string('Notes', 'BLUE'))
|
||||||
for note in self.notes:
|
for note in self.notes:
|
||||||
report.append(f' {note}')
|
report.append(f' {note}')
|
||||||
|
|
||||||
|
|
@ -211,6 +205,7 @@ class Disk:
|
||||||
self.serial = self.raw_details.get('serial', 'Unknown Serial')
|
self.serial = self.raw_details.get('serial', 'Unknown Serial')
|
||||||
self.size = self.raw_details.get('size', -1)
|
self.size = self.raw_details.get('size', -1)
|
||||||
self.ssd = self.raw_details.get('ssd', False)
|
self.ssd = self.raw_details.get('ssd', False)
|
||||||
|
self.trim = self.raw_details.get('trim', False)
|
||||||
|
|
||||||
# Ensure certain attributes types
|
# Ensure certain attributes types
|
||||||
## NOTE: This is ugly, deal.
|
## NOTE: This is ugly, deal.
|
||||||
|
|
@ -224,6 +219,10 @@ class Disk:
|
||||||
if attr == 'size':
|
if attr == 'size':
|
||||||
setattr(self, attr, -1)
|
setattr(self, attr, -1)
|
||||||
|
|
||||||
|
# Add TRIM note
|
||||||
|
if self.trim:
|
||||||
|
self.add_note('TRIM support detected', 'YELLOW')
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def get_disk_details_linux(disk_path, skip_children=True) -> dict[Any, Any]:
|
def get_disk_details_linux(disk_path, skip_children=True) -> dict[Any, Any]:
|
||||||
|
|
@ -247,10 +246,12 @@ def get_disk_details_linux(disk_path, skip_children=True) -> dict[Any, Any]:
|
||||||
dev['bus'] = dev.pop('tran', '???')
|
dev['bus'] = dev.pop('tran', '???')
|
||||||
dev['parent'] = dev.pop('pkname', None)
|
dev['parent'] = dev.pop('pkname', None)
|
||||||
dev['ssd'] = not dev.pop('rota', True)
|
dev['ssd'] = not dev.pop('rota', True)
|
||||||
|
dev['trim'] = bool(dev.pop('disc-max', 0))
|
||||||
if 'loop' in str(disk_path) and dev['bus'] is None:
|
if 'loop' in str(disk_path) and dev['bus'] is None:
|
||||||
dev['bus'] = 'Image'
|
dev['bus'] = 'Image'
|
||||||
dev['model'] = ''
|
dev['model'] = ''
|
||||||
dev['serial'] = ''
|
dev['serial'] = ''
|
||||||
|
dev['trim'] = False # NOTE: This check is just for physical devices
|
||||||
|
|
||||||
# Convert to dict
|
# Convert to dict
|
||||||
details = dev_list.pop(0)
|
details = dev_list.pop(0)
|
||||||
|
|
@ -308,6 +309,7 @@ def get_disk_details_macos(disk_path, skip_children=True) -> dict:
|
||||||
dev['serial'] = get_disk_serial_macos(dev['path'])
|
dev['serial'] = get_disk_serial_macos(dev['path'])
|
||||||
dev['size'] = dev.pop('Size', -1)
|
dev['size'] = dev.pop('Size', -1)
|
||||||
dev['ssd'] = dev.pop('SolidState', False)
|
dev['ssd'] = dev.pop('SolidState', False)
|
||||||
|
dev['trim'] = False # TODO: ACtually check for TRIM
|
||||||
dev['vendor'] = ''
|
dev['vendor'] = ''
|
||||||
if dev.get('WholeDisk', True):
|
if dev.get('WholeDisk', True):
|
||||||
dev['parent'] = None
|
dev['parent'] = None
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from wk.exe import run_program
|
from wk.exe import run_program
|
||||||
from wk.std import PLATFORM, print_warning
|
from wk.std import PLATFORM
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -17,7 +17,8 @@ def keyboard_test() -> None:
|
||||||
if PLATFORM == 'Linux':
|
if PLATFORM == 'Linux':
|
||||||
run_xev()
|
run_xev()
|
||||||
else:
|
else:
|
||||||
print_warning(f'Not supported under this OS: {PLATFORM}')
|
LOG.error('Not supported under this OS: %s', PLATFORM)
|
||||||
|
raise NotImplementedError(f'Not supported under this OS: {PLATFORM}')
|
||||||
|
|
||||||
|
|
||||||
def run_xev() -> None:
|
def run_xev() -> None:
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,7 @@ from wk.net import (
|
||||||
show_valid_addresses,
|
show_valid_addresses,
|
||||||
speedtest,
|
speedtest,
|
||||||
)
|
)
|
||||||
from wk.std import (
|
from wk.ui import cli as ui
|
||||||
TryAndPrint,
|
|
||||||
pause,
|
|
||||||
print_warning,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -24,7 +20,7 @@ LOG = logging.getLogger(__name__)
|
||||||
def network_test() -> None:
|
def network_test() -> None:
|
||||||
"""Run network tests."""
|
"""Run network tests."""
|
||||||
LOG.info('Network Test')
|
LOG.info('Network Test')
|
||||||
try_and_print = TryAndPrint()
|
try_and_print = ui.TryAndPrint()
|
||||||
result = try_and_print.run(
|
result = try_and_print.run(
|
||||||
message='Network connection...',
|
message='Network connection...',
|
||||||
function=connected_to_private_network,
|
function=connected_to_private_network,
|
||||||
|
|
@ -34,8 +30,8 @@ def network_test() -> None:
|
||||||
|
|
||||||
# Bail if not connected
|
# Bail if not connected
|
||||||
if result['Failed']:
|
if result['Failed']:
|
||||||
print_warning('Please connect to a network and try again')
|
ui.print_warning('Please connect to a network and try again')
|
||||||
pause('Press Enter to return to main menu...')
|
ui.pause('Press Enter to return to main menu...')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Show IP address(es)
|
# Show IP address(es)
|
||||||
|
|
@ -51,7 +47,7 @@ def network_test() -> None:
|
||||||
try_and_print.run('Speedtest...', speedtest)
|
try_and_print.run('Speedtest...', speedtest)
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
pause('Press Enter to return to main menu...')
|
ui.pause('Press Enter to return to main menu...')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
from subprocess import PIPE
|
from subprocess import PIPE
|
||||||
|
|
||||||
from wk.exe import run_program
|
from wk.exe import run_program
|
||||||
from wk.tmux import zoom_pane as tmux_zoom_pane
|
from wk.ui import tmux
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -31,9 +31,9 @@ def screensaver(name) -> None:
|
||||||
]
|
]
|
||||||
|
|
||||||
# Switch pane to fullscreen and start screensaver
|
# Switch pane to fullscreen and start screensaver
|
||||||
tmux_zoom_pane()
|
tmux.zoom_pane()
|
||||||
run_program(cmd, check=False, pipe=False, stderr=PIPE)
|
run_program(cmd, check=False, pipe=False, stderr=PIPE)
|
||||||
tmux_zoom_pane()
|
tmux.zoom_pane()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@ import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
|
from threading import Thread
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from wk.cfg.hw import CPU_CRITICAL_TEMP, SMC_IDS, TEMP_COLORS
|
from wk.cfg.hw import CPU_TEMPS, SMC_IDS, TEMP_COLORS
|
||||||
from wk.exe import run_program, start_thread
|
from wk.exe import run_program, start_thread
|
||||||
from wk.io import non_clobber_path
|
from wk.io import non_clobber_path
|
||||||
from wk.std import PLATFORM, color_string, sleep
|
from wk.std import PLATFORM, sleep
|
||||||
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -34,39 +37,83 @@ class ThermalLimitReachedError(RuntimeError):
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
class Sensors():
|
class Sensors():
|
||||||
"""Class for holding sensor specific data."""
|
"""Class for holding sensor specific data.
|
||||||
def __init__(self):
|
|
||||||
self.background_thread = None
|
|
||||||
self.data = get_sensor_data()
|
|
||||||
self.out_path = None
|
|
||||||
|
|
||||||
def clear_temps(self) -> None:
|
# Sensor data structure
|
||||||
|
#
|
||||||
|
# Section # CPUTemps / Other
|
||||||
|
# Adapters # coretemp / acpi / nvme / etc
|
||||||
|
# Sources # Core 1 / SODIMM / Sensor X / etc
|
||||||
|
# Label # temp1_input / etc (i.e. lm_sensor label)
|
||||||
|
# Max # 99.0
|
||||||
|
# X # 55.0 (where X is Idle/Current/Sysbench/etc)
|
||||||
|
# Temps # [39.0, 38.0, 40.0, 39.0, 38.0, ...]
|
||||||
|
#
|
||||||
|
# e.g.
|
||||||
|
# { 'CPUTemps': { 'coretemp-isa-0000': { 'Core 0': { 'Average': 44.5,
|
||||||
|
# 'Current': 44.0,
|
||||||
|
# 'Idle': 44.5,
|
||||||
|
# 'Label': 'temp2_input',
|
||||||
|
# 'Max': 45.0,
|
||||||
|
# 'Temps': [ 45.0,
|
||||||
|
# 45.0,
|
||||||
|
# ...,
|
||||||
|
# 42.0]}}}}
|
||||||
|
#
|
||||||
|
# Sensor history data structure
|
||||||
|
# [ ('Name of "run"', sensor_data_structure_described_above), ]
|
||||||
|
#
|
||||||
|
# e.g.
|
||||||
|
# [
|
||||||
|
# ( 'Idle',
|
||||||
|
# { 'CPUTemps': { 'coretemp-isa-0000': { 'Core 0': { 'Max': 45.0, ..., }}}}
|
||||||
|
# ),
|
||||||
|
# ( 'Sysbench',
|
||||||
|
# { 'CPUTemps': { 'coretemp-isa-0000': { 'Core 0': { 'Max': 85.0, ..., }}}}
|
||||||
|
# ),
|
||||||
|
# ]
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.background_thread: Thread | None = None
|
||||||
|
self.data: dict[Any, Any] = get_sensor_data()
|
||||||
|
self.history: list[tuple[str, dict]] = []
|
||||||
|
self.history_index: dict[str, int] = {}
|
||||||
|
self.history_next_label: str = 'Idle'
|
||||||
|
self.out_path: pathlib.Path | str | None = None
|
||||||
|
self.temp_labels: set = set(['Current', 'Max'])
|
||||||
|
|
||||||
|
def clear_temps(self, next_label: str, save_history: bool = True) -> None:
|
||||||
"""Clear saved temps but keep structure"""
|
"""Clear saved temps but keep structure"""
|
||||||
|
prev_label = self.history_next_label
|
||||||
|
self.history_next_label = next_label
|
||||||
|
|
||||||
|
# Save history
|
||||||
|
if save_history:
|
||||||
|
cur_data = deepcopy(self.data)
|
||||||
|
|
||||||
|
# Calculate averages
|
||||||
|
for adapters in cur_data.values():
|
||||||
|
for sources in adapters.values():
|
||||||
|
for name in sources:
|
||||||
|
temp_list = sources[name]['Temps']
|
||||||
|
try:
|
||||||
|
sources[name]['Average'] = sum(temp_list) / len(temp_list)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
LOG.error('Failed to calculate averate temp for %s', name)
|
||||||
|
sources[name]['Average'] = 0
|
||||||
|
|
||||||
|
# Add to history
|
||||||
|
self.history.append((prev_label, cur_data))
|
||||||
|
self.history_index[prev_label] = len(self.history) - 1
|
||||||
|
|
||||||
|
# Clear data
|
||||||
for adapters in self.data.values():
|
for adapters in self.data.values():
|
||||||
for sources in adapters.values():
|
for sources in adapters.values():
|
||||||
for source_data in sources.values():
|
for source_data in sources.values():
|
||||||
source_data['Temps'] = []
|
source_data['Temps'] = []
|
||||||
|
|
||||||
def cpu_max_temp(self) -> float:
|
|
||||||
"""Get max temp from any CPU source, returns float.
|
|
||||||
|
|
||||||
NOTE: If no temps are found this returns zero.
|
|
||||||
"""
|
|
||||||
max_temp = 0.0
|
|
||||||
|
|
||||||
# Check all CPU Temps
|
|
||||||
for section, adapters in self.data.items():
|
|
||||||
if not section.startswith('CPU'):
|
|
||||||
continue
|
|
||||||
for sources in adapters.values():
|
|
||||||
for source_data in sources.values():
|
|
||||||
max_temp = max(max_temp, source_data.get('Max', 0))
|
|
||||||
|
|
||||||
# Done
|
|
||||||
return max_temp
|
|
||||||
|
|
||||||
def cpu_reached_critical_temp(self) -> bool:
|
def cpu_reached_critical_temp(self) -> bool:
|
||||||
"""Check if CPU reached CPU_CRITICAL_TEMP, returns bool."""
|
"""Check if CPU exceeded critical temp, returns bool."""
|
||||||
for section, adapters in self.data.items():
|
for section, adapters in self.data.items():
|
||||||
if not section.startswith('CPU'):
|
if not section.startswith('CPU'):
|
||||||
# Limit to CPU temps
|
# Limit to CPU temps
|
||||||
|
|
@ -75,16 +122,22 @@ class Sensors():
|
||||||
# Ugly section
|
# Ugly section
|
||||||
for sources in adapters.values():
|
for sources in adapters.values():
|
||||||
for source_data in sources.values():
|
for source_data in sources.values():
|
||||||
if source_data.get('Max', -1) >= CPU_CRITICAL_TEMP:
|
if source_data.get('Max', -1) > CPU_TEMPS['Critical']:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Didn't return above so temps are within the threshold
|
# Didn't return above so temps are within the threshold
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def generate_report(
|
def generate_report(
|
||||||
self, *temp_labels, colored=True, only_cpu=False) -> list[str]:
|
self,
|
||||||
|
*temp_labels: str,
|
||||||
|
colored: bool = True,
|
||||||
|
only_cpu: bool = False,
|
||||||
|
include_avg_for: list[str] | None = None,
|
||||||
|
) -> list[str]:
|
||||||
"""Generate report based on given temp_labels, returns list."""
|
"""Generate report based on given temp_labels, returns list."""
|
||||||
report = []
|
report = []
|
||||||
|
include_avg_for = include_avg_for if include_avg_for else []
|
||||||
|
|
||||||
for section, adapters in sorted(self.data.items()):
|
for section, adapters in sorted(self.data.items()):
|
||||||
if only_cpu and not section.startswith('CPU'):
|
if only_cpu and not section.startswith('CPU'):
|
||||||
|
|
@ -98,6 +151,10 @@ class Sensors():
|
||||||
for label in temp_labels:
|
for label in temp_labels:
|
||||||
if label != 'Current':
|
if label != 'Current':
|
||||||
line += f' {label.lower()}: '
|
line += f' {label.lower()}: '
|
||||||
|
if label in include_avg_for:
|
||||||
|
avg_temp = self.get_avg_temp(
|
||||||
|
label, section, adapter, source, colored)
|
||||||
|
line += f'{avg_temp} / '
|
||||||
line += get_temp_str(
|
line += get_temp_str(
|
||||||
source_data.get(label, '???'),
|
source_data.get(label, '???'),
|
||||||
colored=colored,
|
colored=colored,
|
||||||
|
|
@ -109,7 +166,7 @@ class Sensors():
|
||||||
# Handle empty reports
|
# Handle empty reports
|
||||||
if not report:
|
if not report:
|
||||||
report = [
|
report = [
|
||||||
color_string('WARNING: No sensors found', 'YELLOW'),
|
ansi.color_string('WARNING: No sensors found', 'YELLOW'),
|
||||||
'',
|
'',
|
||||||
'Please monitor temps manually',
|
'Please monitor temps manually',
|
||||||
]
|
]
|
||||||
|
|
@ -117,6 +174,32 @@ class Sensors():
|
||||||
# Done
|
# Done
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
def get_avg_temp(self, label, section, adapter, source, colored) -> str:
|
||||||
|
"""Get average temp from history, return str."""
|
||||||
|
# NOTE: This is Super-ugly
|
||||||
|
label_index = self.history_index[label]
|
||||||
|
avg_temp = self.history[label_index][1][section][adapter][source]['Average']
|
||||||
|
return get_temp_str(avg_temp, colored=colored)
|
||||||
|
|
||||||
|
def get_cpu_temp(self, label) -> float:
|
||||||
|
"""Get temp for label from any CPU source, returns float.
|
||||||
|
|
||||||
|
NOTE: This returns the highest value for the label.
|
||||||
|
NOTE 2: If no temps are found this returns zero.
|
||||||
|
"""
|
||||||
|
max_temp = 0.0
|
||||||
|
|
||||||
|
# Check all CPU Temps
|
||||||
|
for section, adapters in self.data.items():
|
||||||
|
if not section.startswith('CPU'):
|
||||||
|
continue
|
||||||
|
for sources in adapters.values():
|
||||||
|
for source_data in sources.values():
|
||||||
|
max_temp = max(max_temp, source_data.get(label, 0))
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return float(max_temp)
|
||||||
|
|
||||||
def monitor_to_file(
|
def monitor_to_file(
|
||||||
self, out_path, alt_max=None,
|
self, out_path, alt_max=None,
|
||||||
exit_on_thermal_limit=True, temp_labels=None,
|
exit_on_thermal_limit=True, temp_labels=None,
|
||||||
|
|
@ -134,6 +217,7 @@ class Sensors():
|
||||||
temp_labels = ['Current', 'Max']
|
temp_labels = ['Current', 'Max']
|
||||||
if alt_max:
|
if alt_max:
|
||||||
temp_labels.append(alt_max)
|
temp_labels.append(alt_max)
|
||||||
|
self.temp_labels.add(alt_max)
|
||||||
|
|
||||||
# Start loop
|
# Start loop
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -153,9 +237,15 @@ class Sensors():
|
||||||
# Sleep before next loop
|
# Sleep before next loop
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
|
|
||||||
def save_average_temps(self, temp_label, seconds=10) -> None:
|
def save_average_temps(
|
||||||
|
self,
|
||||||
|
temp_label: str,
|
||||||
|
seconds: int = 10,
|
||||||
|
save_history: bool = True,
|
||||||
|
) -> None:
|
||||||
"""Save average temps under temp_label over provided seconds.."""
|
"""Save average temps under temp_label over provided seconds.."""
|
||||||
self.clear_temps()
|
self.clear_temps(next_label=temp_label, save_history=save_history)
|
||||||
|
self.temp_labels.add(temp_label)
|
||||||
|
|
||||||
# Get temps
|
# Get temps
|
||||||
for _ in range(seconds):
|
for _ in range(seconds):
|
||||||
|
|
@ -198,6 +288,10 @@ class Sensors():
|
||||||
|
|
||||||
def stop_background_monitor(self) -> None:
|
def stop_background_monitor(self) -> None:
|
||||||
"""Stop background thread."""
|
"""Stop background thread."""
|
||||||
|
# Bail early
|
||||||
|
if self.background_thread is None:
|
||||||
|
return
|
||||||
|
|
||||||
self.out_path.with_suffix('.stop').touch()
|
self.out_path.with_suffix('.stop').touch()
|
||||||
self.background_thread.join()
|
self.background_thread.join()
|
||||||
|
|
||||||
|
|
@ -208,6 +302,8 @@ class Sensors():
|
||||||
def update_sensor_data(
|
def update_sensor_data(
|
||||||
self, alt_max=None, exit_on_thermal_limit=True) -> None:
|
self, alt_max=None, exit_on_thermal_limit=True) -> None:
|
||||||
"""Update sensor data via OS-specific means."""
|
"""Update sensor data via OS-specific means."""
|
||||||
|
if alt_max:
|
||||||
|
self.temp_labels.add(alt_max)
|
||||||
if PLATFORM == 'Darwin':
|
if PLATFORM == 'Darwin':
|
||||||
self.update_sensor_data_macos(alt_max, exit_on_thermal_limit)
|
self.update_sensor_data_macos(alt_max, exit_on_thermal_limit)
|
||||||
elif PLATFORM == 'Linux':
|
elif PLATFORM == 'Linux':
|
||||||
|
|
@ -234,7 +330,7 @@ class Sensors():
|
||||||
|
|
||||||
# Raise exception if thermal limit reached
|
# Raise exception if thermal limit reached
|
||||||
if exit_on_thermal_limit and section == 'CPUTemps':
|
if exit_on_thermal_limit and section == 'CPUTemps':
|
||||||
if source_data['Current'] >= CPU_CRITICAL_TEMP:
|
if source_data['Current'] > CPU_TEMPS['Critical']:
|
||||||
raise ThermalLimitReachedError('CPU temps reached limit')
|
raise ThermalLimitReachedError('CPU temps reached limit')
|
||||||
|
|
||||||
def update_sensor_data_macos(
|
def update_sensor_data_macos(
|
||||||
|
|
@ -261,7 +357,7 @@ class Sensors():
|
||||||
|
|
||||||
# Raise exception if thermal limit reached
|
# Raise exception if thermal limit reached
|
||||||
if exit_on_thermal_limit and section == 'CPUTemps':
|
if exit_on_thermal_limit and section == 'CPUTemps':
|
||||||
if source_data['Current'] >= CPU_CRITICAL_TEMP:
|
if source_data['Current'] > CPU_TEMPS['Critical']:
|
||||||
raise ThermalLimitReachedError('CPU temps reached limit')
|
raise ThermalLimitReachedError('CPU temps reached limit')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -418,14 +514,14 @@ def get_sensor_data_macos() -> dict[Any, Any]:
|
||||||
|
|
||||||
def get_temp_str(temp, colored=True) -> str:
|
def get_temp_str(temp, colored=True) -> str:
|
||||||
"""Get colored string based on temp, returns str."""
|
"""Get colored string based on temp, returns str."""
|
||||||
temp_color = None
|
temp_color = ''
|
||||||
|
|
||||||
# Safety check
|
# Safety check
|
||||||
try:
|
try:
|
||||||
temp = float(temp)
|
temp = float(temp)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
# Invalid temp?
|
# Invalid temp?
|
||||||
return color_string(temp, 'PURPLE')
|
return ansi.color_string(temp, 'PURPLE')
|
||||||
|
|
||||||
# Determine color
|
# Determine color
|
||||||
if colored:
|
if colored:
|
||||||
|
|
@ -435,7 +531,7 @@ def get_temp_str(temp, colored=True) -> str:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return color_string(f'{"-" if temp < 0 else ""}{temp:2.0f}°C', temp_color)
|
return ansi.color_string(f'{"-" if temp < 0 else ""}{temp:2.0f}°C', temp_color)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ from wk.cfg.hw import (
|
||||||
SMART_SELF_TEST_START_TIMEOUT_IN_SECONDS,
|
SMART_SELF_TEST_START_TIMEOUT_IN_SECONDS,
|
||||||
)
|
)
|
||||||
from wk.exe import get_json_from_command, run_program
|
from wk.exe import get_json_from_command, run_program
|
||||||
from wk.std import bytes_to_string, color_string, sleep
|
from wk.std import bytes_to_string, sleep
|
||||||
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -40,26 +41,25 @@ def build_self_test_report(test_obj, aborted=False) -> None:
|
||||||
For instance if the test was aborted the report should include the
|
For instance if the test was aborted the report should include the
|
||||||
last known progress instead of just "was aborted by host."
|
last known progress instead of just "was aborted by host."
|
||||||
"""
|
"""
|
||||||
report = [color_string('Self-Test', 'BLUE')]
|
report = [ansi.color_string('Self-Test', 'BLUE')]
|
||||||
test_details = get_smart_self_test_details(test_obj.dev)
|
test_result = get_smart_self_test_last_result(test_obj.dev)
|
||||||
test_result = test_details.get('status', {}).get('string', 'Unknown')
|
|
||||||
|
|
||||||
# Build report
|
# Build report
|
||||||
if test_obj.disabled or test_obj.status == 'Denied':
|
if test_obj.disabled or test_obj.status == 'Denied':
|
||||||
report.append(color_string(f' {test_obj.status}', 'RED'))
|
report.append(ansi.color_string(f' {test_obj.status}', 'RED'))
|
||||||
elif test_obj.status == 'N/A' or not test_obj.dev.attributes:
|
elif test_obj.status == 'N/A' or not test_obj.dev.attributes:
|
||||||
report.append(color_string(f' {test_obj.status}', 'YELLOW'))
|
report.append(ansi.color_string(f' {test_obj.status}', 'YELLOW'))
|
||||||
elif test_obj.status == 'TestInProgress':
|
|
||||||
report.append(color_string(' Failed to stop previous test', 'RED'))
|
|
||||||
test_obj.set_status('Failed')
|
|
||||||
else:
|
else:
|
||||||
# Other cases include self-test result string
|
# Other cases include self-test result string
|
||||||
report.append(f' {test_result.capitalize()}')
|
if test_obj.status == 'TestInProgress':
|
||||||
if aborted and not (test_obj.passed or test_obj.failed):
|
report.append(ansi.color_string(' Failed to stop previous test', 'RED'))
|
||||||
report.append(color_string(' Aborted', 'YELLOW'))
|
test_obj.set_status('Failed')
|
||||||
test_obj.set_status('Aborted')
|
|
||||||
elif test_obj.status == 'TimedOut':
|
elif test_obj.status == 'TimedOut':
|
||||||
report.append(color_string(' TimedOut', 'YELLOW'))
|
report.append(ansi.color_string(' TimedOut', 'YELLOW'))
|
||||||
|
elif aborted and not (test_obj.passed or test_obj.failed):
|
||||||
|
report.append(ansi.color_string(' Aborted', 'YELLOW'))
|
||||||
|
test_obj.set_status('Aborted')
|
||||||
|
report.append(f' {test_result}')
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
test_obj.report.extend(report)
|
test_obj.report.extend(report)
|
||||||
|
|
@ -104,7 +104,7 @@ def enable_smart(dev) -> None:
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
'smartctl',
|
'smartctl',
|
||||||
f'--device={"sat,auto" if dev.use_sat else "auto"}',
|
'--device=auto',
|
||||||
'--tolerance=permissive',
|
'--tolerance=permissive',
|
||||||
'--smart=on',
|
'--smart=on',
|
||||||
dev.path,
|
dev.path,
|
||||||
|
|
@ -136,7 +136,7 @@ def generate_attribute_report(dev, only_failed=False) -> list[str]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Build colored string and append to report
|
# Build colored string and append to report
|
||||||
line = color_string(
|
line = ansi.color_string(
|
||||||
[label, get_attribute_value_string(dev, attr), note],
|
[label, get_attribute_value_string(dev, attr), note],
|
||||||
[None, value_color, 'YELLOW'],
|
[None, value_color, 'YELLOW'],
|
||||||
)
|
)
|
||||||
|
|
@ -200,7 +200,7 @@ def get_attribute_value_string(dev, attr) -> str:
|
||||||
return value_str
|
return value_str
|
||||||
|
|
||||||
|
|
||||||
def get_known_disk_attributes(model) -> None:
|
def get_known_disk_attributes(model) -> dict[str | int, dict[str, Any]]:
|
||||||
"""Get known disk attributes based on the device model."""
|
"""Get known disk attributes based on the device model."""
|
||||||
known_attributes = copy.deepcopy(KNOWN_DISK_ATTRIBUTES)
|
known_attributes = copy.deepcopy(KNOWN_DISK_ATTRIBUTES)
|
||||||
|
|
||||||
|
|
@ -218,7 +218,7 @@ def get_known_disk_attributes(model) -> None:
|
||||||
return known_attributes
|
return known_attributes
|
||||||
|
|
||||||
|
|
||||||
def get_smart_self_test_details(dev) -> dict[Any, Any]:
|
def get_smart_self_test_details(dev) -> dict[str, Any]:
|
||||||
"""Shorthand to get deeply nested self-test details, returns dict."""
|
"""Shorthand to get deeply nested self-test details, returns dict."""
|
||||||
details = {}
|
details = {}
|
||||||
try:
|
try:
|
||||||
|
|
@ -231,6 +231,33 @@ def get_smart_self_test_details(dev) -> dict[Any, Any]:
|
||||||
return details
|
return details
|
||||||
|
|
||||||
|
|
||||||
|
def get_smart_self_test_last_result(dev) -> str:
|
||||||
|
"""Get last SMART self-test result, returns str."""
|
||||||
|
result = 'Unknown'
|
||||||
|
|
||||||
|
# Parse SMART data
|
||||||
|
data = dev.raw_smartctl.get(
|
||||||
|
'ata_smart_self_test_log', {}).get(
|
||||||
|
'standard', {}).get(
|
||||||
|
'table', [])
|
||||||
|
try:
|
||||||
|
data = data[0]
|
||||||
|
except IndexError:
|
||||||
|
# No results found
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Build result string
|
||||||
|
result = (
|
||||||
|
f'Power-on hours: {data.get("lifetime_hours", "?")}'
|
||||||
|
f', Type: {data.get("type", {}).get("string", "?")}'
|
||||||
|
f', Passed: {data.get("status", {}).get("passed", "?")}'
|
||||||
|
f', Result: {data.get("status", {}).get("string", "?")}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def monitor_smart_self_test(test_obj, header_str, log_path) -> bool:
|
def monitor_smart_self_test(test_obj, header_str, log_path) -> bool:
|
||||||
"""Monitor SMART self-test status and update test_obj, returns bool."""
|
"""Monitor SMART self-test status and update test_obj, returns bool."""
|
||||||
started = False
|
started = False
|
||||||
|
|
@ -262,6 +289,9 @@ def monitor_smart_self_test(test_obj, header_str, log_path) -> bool:
|
||||||
if _i * 5 >= SMART_SELF_TEST_START_TIMEOUT_IN_SECONDS:
|
if _i * 5 >= SMART_SELF_TEST_START_TIMEOUT_IN_SECONDS:
|
||||||
# Test didn't start within limit, stop waiting
|
# Test didn't start within limit, stop waiting
|
||||||
abort_self_test(test_obj.dev)
|
abort_self_test(test_obj.dev)
|
||||||
|
result = get_smart_self_test_last_result(test_obj.dev)
|
||||||
|
if result == 'Unknown':
|
||||||
|
result = 'SMART self-test failed to start'
|
||||||
test_obj.failed = True
|
test_obj.failed = True
|
||||||
test_obj.set_status('TimedOut')
|
test_obj.set_status('TimedOut')
|
||||||
break
|
break
|
||||||
|
|
@ -277,6 +307,11 @@ def monitor_smart_self_test(test_obj, header_str, log_path) -> bool:
|
||||||
finished = True
|
finished = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Check if timed out
|
||||||
|
if started and not finished:
|
||||||
|
test_obj.failed = True
|
||||||
|
test_obj.set_status('TimedOut')
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return finished
|
return finished
|
||||||
|
|
||||||
|
|
@ -290,15 +325,15 @@ def run_self_test(test_obj, log_path) -> None:
|
||||||
run_smart_self_test(test_obj, log_path)
|
run_smart_self_test(test_obj, log_path)
|
||||||
|
|
||||||
|
|
||||||
def run_smart_self_test(test_obj, log_path) -> bool:
|
def run_smart_self_test(test_obj, log_path) -> None:
|
||||||
"""Run SMART self-test and check if it passed, returns bool.
|
"""Run SMART self-test and check if it passed, returns None.
|
||||||
|
|
||||||
NOTE: An exception will be raised if the disk lacks SMART support.
|
NOTE: An exception will be raised if the disk lacks SMART support.
|
||||||
"""
|
"""
|
||||||
finished = False
|
finished = False
|
||||||
test_details = get_smart_self_test_details(test_obj.dev)
|
test_details = get_smart_self_test_details(test_obj.dev)
|
||||||
size_str = bytes_to_string(test_obj.dev.size, use_binary=False)
|
size_str = bytes_to_string(test_obj.dev.size, use_binary=False)
|
||||||
header_str = color_string(
|
header_str = ansi.color_string(
|
||||||
['[', test_obj.dev.path.name, ' ', size_str, ']'],
|
['[', test_obj.dev.path.name, ' ', size_str, ']'],
|
||||||
[None, 'BLUE', None, 'CYAN', None],
|
[None, 'BLUE', None, 'CYAN', None],
|
||||||
sep='',
|
sep='',
|
||||||
|
|
@ -348,11 +383,15 @@ def run_smart_self_test(test_obj, log_path) -> bool:
|
||||||
|
|
||||||
# Check result
|
# Check result
|
||||||
if finished:
|
if finished:
|
||||||
|
test_details = get_smart_self_test_details(test_obj.dev)
|
||||||
test_obj.passed = test_details.get('status', {}).get('passed', False)
|
test_obj.passed = test_details.get('status', {}).get('passed', False)
|
||||||
test_obj.failed = test_obj.failed or not test_obj.passed
|
test_obj.failed = test_obj.failed or not test_obj.passed
|
||||||
|
|
||||||
# Set status
|
# Set status
|
||||||
if test_obj.failed and test_obj.status != 'TimedOut':
|
if test_obj.status == 'TimedOut':
|
||||||
|
# Preserve TimedOut status
|
||||||
|
pass
|
||||||
|
elif test_obj.failed:
|
||||||
test_obj.set_status('Failed')
|
test_obj.set_status('Failed')
|
||||||
elif test_obj.passed:
|
elif test_obj.passed:
|
||||||
test_obj.set_status('Passed')
|
test_obj.set_status('Passed')
|
||||||
|
|
@ -422,7 +461,7 @@ def update_smart_details(dev) -> None:
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
'smartctl',
|
'smartctl',
|
||||||
f'--device={"sat,auto" if dev.use_sat else "auto"}',
|
'--device=auto',
|
||||||
'--tolerance=verypermissive',
|
'--tolerance=verypermissive',
|
||||||
'--all',
|
'--all',
|
||||||
'--json',
|
'--json',
|
||||||
|
|
@ -467,6 +506,10 @@ def update_smart_details(dev) -> None:
|
||||||
if not updated_attributes:
|
if not updated_attributes:
|
||||||
dev.add_note('No NVMe or SMART data available', 'YELLOW')
|
dev.add_note('No NVMe or SMART data available', 'YELLOW')
|
||||||
|
|
||||||
|
# Update iniital_attributes if needed
|
||||||
|
if not dev.initial_attributes:
|
||||||
|
dev.initial_attributes = copy.deepcopy(updated_attributes)
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
dev.attributes.update(updated_attributes)
|
dev.attributes.update(updated_attributes)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,8 @@ from wk.cfg.hw import (
|
||||||
TEST_MODE_BADBLOCKS_LIMIT,
|
TEST_MODE_BADBLOCKS_LIMIT,
|
||||||
)
|
)
|
||||||
from wk.exe import run_program
|
from wk.exe import run_program
|
||||||
from wk.std import (
|
from wk.std import PLATFORM, bytes_to_string
|
||||||
PLATFORM,
|
from wk.ui import ansi
|
||||||
bytes_to_string,
|
|
||||||
color_string,
|
|
||||||
strip_colors,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -31,7 +27,7 @@ def check_surface_scan_results(test_obj, log_path) -> None:
|
||||||
"""Check results and set test status."""
|
"""Check results and set test status."""
|
||||||
with open(log_path, 'r', encoding='utf-8') as _f:
|
with open(log_path, 'r', encoding='utf-8') as _f:
|
||||||
for line in _f.readlines():
|
for line in _f.readlines():
|
||||||
line = strip_colors(line.strip())
|
line = ansi.strip_colors(line.strip())
|
||||||
if not line or BADBLOCKS_SKIP_REGEX.match(line):
|
if not line or BADBLOCKS_SKIP_REGEX.match(line):
|
||||||
# Skip
|
# Skip
|
||||||
continue
|
continue
|
||||||
|
|
@ -48,10 +44,10 @@ def check_surface_scan_results(test_obj, log_path) -> None:
|
||||||
test_obj.set_status('Passed')
|
test_obj.set_status('Passed')
|
||||||
else:
|
else:
|
||||||
test_obj.failed = True
|
test_obj.failed = True
|
||||||
test_obj.report.append(f' {color_string(line, "YELLOW")}')
|
test_obj.report.append(f' {ansi.color_string(line, "YELLOW")}')
|
||||||
test_obj.set_status('Failed')
|
test_obj.set_status('Failed')
|
||||||
else:
|
else:
|
||||||
test_obj.report.append(f' {color_string(line, "YELLOW")}')
|
test_obj.report.append(f' {ansi.color_string(line, "YELLOW")}')
|
||||||
if not (test_obj.passed or test_obj.failed):
|
if not (test_obj.passed or test_obj.failed):
|
||||||
test_obj.set_status('Unknown')
|
test_obj.set_status('Unknown')
|
||||||
|
|
||||||
|
|
@ -65,7 +61,7 @@ def run_scan(test_obj, log_path, test_mode=False) -> None:
|
||||||
# Use "RAW" disks under macOS
|
# Use "RAW" disks under macOS
|
||||||
dev_path = dev_path.with_name(f'r{dev_path.name}')
|
dev_path = dev_path.with_name(f'r{dev_path.name}')
|
||||||
LOG.info('Using %s for better performance', dev_path)
|
LOG.info('Using %s for better performance', dev_path)
|
||||||
test_obj.report.append(color_string('badblocks', 'BLUE'))
|
test_obj.report.append(ansi.color_string('badblocks', 'BLUE'))
|
||||||
test_obj.set_status('Working')
|
test_obj.set_status('Working')
|
||||||
|
|
||||||
# Increase block size if necessary
|
# Increase block size if necessary
|
||||||
|
|
@ -84,7 +80,7 @@ def run_scan(test_obj, log_path, test_mode=False) -> None:
|
||||||
with open(log_path, 'a', encoding='utf-8') as _f:
|
with open(log_path, 'a', encoding='utf-8') as _f:
|
||||||
size_str = bytes_to_string(dev.size, use_binary=False)
|
size_str = bytes_to_string(dev.size, use_binary=False)
|
||||||
_f.write(
|
_f.write(
|
||||||
color_string(
|
ansi.color_string(
|
||||||
['[', dev.path.name, ' ', size_str, ']\n'],
|
['[', dev.path.name, ' ', size_str, ']\n'],
|
||||||
[None, 'BLUE', None, 'CYAN', None],
|
[None, 'BLUE', None, 'CYAN', None],
|
||||||
sep='',
|
sep='',
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,17 @@ from dataclasses import dataclass, field
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from wk.cfg.hw import KNOWN_RAM_VENDOR_IDS
|
from wk.cfg.hw import KNOWN_RAM_VENDOR_IDS
|
||||||
from wk.cfg.python import DATACLASS_DECORATOR_KWARGS
|
|
||||||
from wk.exe import get_json_from_command, run_program
|
from wk.exe import get_json_from_command, run_program
|
||||||
from wk.hw.test import Test
|
from wk.hw.test import Test
|
||||||
from wk.std import (
|
from wk.std import PLATFORM, bytes_to_string, string_to_bytes
|
||||||
PLATFORM,
|
from wk.ui import ansi
|
||||||
bytes_to_string,
|
|
||||||
color_string,
|
|
||||||
string_to_bytes,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(**DATACLASS_DECORATOR_KWARGS)
|
@dataclass(slots=True)
|
||||||
class System:
|
class System:
|
||||||
"""Object for tracking system specific hardware data."""
|
"""Object for tracking system specific hardware data."""
|
||||||
cpu_description: str = field(init=False)
|
cpu_description: str = field(init=False)
|
||||||
|
|
@ -41,11 +36,11 @@ class System:
|
||||||
def generate_report(self) -> list[str]:
|
def generate_report(self) -> list[str]:
|
||||||
"""Generate CPU & RAM report, returns list."""
|
"""Generate CPU & RAM report, returns list."""
|
||||||
report = []
|
report = []
|
||||||
report.append(color_string('Device', 'BLUE'))
|
report.append(ansi.color_string('Device', 'BLUE'))
|
||||||
report.append(f' {self.cpu_description}')
|
report.append(f' {self.cpu_description}')
|
||||||
|
|
||||||
# Include RAM details
|
# Include RAM details
|
||||||
report.append(color_string('RAM', 'BLUE'))
|
report.append(ansi.color_string('RAM', 'BLUE'))
|
||||||
report.append(f' {self.ram_total} ({", ".join(self.ram_dimms)})')
|
report.append(f' {self.ram_total} ({", ".join(self.ram_dimms)})')
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from wk.cfg.python import DATACLASS_DECORATOR_KWARGS
|
@dataclass(slots=True)
|
||||||
|
|
||||||
@dataclass(**DATACLASS_DECORATOR_KWARGS)
|
|
||||||
class Test:
|
class Test:
|
||||||
"""Object for tracking test specific data."""
|
"""Object for tracking test specific data."""
|
||||||
dev: Any
|
dev: Any
|
||||||
|
|
@ -14,7 +12,6 @@ class Test:
|
||||||
name: str
|
name: str
|
||||||
disabled: bool = field(init=False, default=False)
|
disabled: bool = field(init=False, default=False)
|
||||||
failed: bool = field(init=False, default=False)
|
failed: bool = field(init=False, default=False)
|
||||||
hidden: bool = False
|
|
||||||
passed: bool = field(init=False, default=False)
|
passed: bool = field(init=False, default=False)
|
||||||
report: list[str] = field(init=False, default_factory=list)
|
report: list[str] = field(init=False, default_factory=list)
|
||||||
status: str = field(init=False, default='Pending')
|
status: str = field(init=False, default='Pending')
|
||||||
|
|
@ -28,7 +25,7 @@ class Test:
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
|
|
||||||
@dataclass(**DATACLASS_DECORATOR_KWARGS)
|
@dataclass(slots=True)
|
||||||
class TestGroup:
|
class TestGroup:
|
||||||
"""Object for tracking groups of tests."""
|
"""Object for tracking groups of tests."""
|
||||||
name: str
|
name: str
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def case_insensitive_path(path):
|
def case_insensitive_path(path: pathlib.Path | str) -> pathlib.Path:
|
||||||
"""Find path case-insensitively, returns pathlib.Path obj."""
|
"""Find path case-insensitively, returns pathlib.Path obj."""
|
||||||
given_path = pathlib.Path(path).resolve()
|
given_path = pathlib.Path(path).resolve()
|
||||||
real_path = None
|
real_path = None
|
||||||
|
|
@ -37,12 +37,13 @@ def case_insensitive_path(path):
|
||||||
return real_path
|
return real_path
|
||||||
|
|
||||||
|
|
||||||
def case_insensitive_search(path, item):
|
def case_insensitive_search(
|
||||||
|
path: pathlib.Path | str, item: str) -> pathlib.Path:
|
||||||
"""Search path for item case insensitively, returns pathlib.Path obj."""
|
"""Search path for item case insensitively, returns pathlib.Path obj."""
|
||||||
path = pathlib.Path(path).resolve()
|
path = pathlib.Path(path).resolve()
|
||||||
given_path = path.joinpath(item)
|
given_path = path.joinpath(item)
|
||||||
real_path = None
|
real_path = None
|
||||||
regex = fr'^{item}'
|
regex = fr'^{item}$'
|
||||||
|
|
||||||
# Quick check
|
# Quick check
|
||||||
if given_path.exists():
|
if given_path.exists():
|
||||||
|
|
@ -61,7 +62,10 @@ def case_insensitive_search(path, item):
|
||||||
return real_path
|
return real_path
|
||||||
|
|
||||||
|
|
||||||
def copy_file(source, dest, overwrite=False):
|
def copy_file(
|
||||||
|
source: pathlib.Path | str,
|
||||||
|
dest: pathlib.Path | str,
|
||||||
|
overwrite: bool = False) -> None:
|
||||||
"""Copy file and optionally overwrite the destination."""
|
"""Copy file and optionally overwrite the destination."""
|
||||||
source = case_insensitive_path(source)
|
source = case_insensitive_path(source)
|
||||||
dest = pathlib.Path(dest).resolve()
|
dest = pathlib.Path(dest).resolve()
|
||||||
|
|
@ -72,7 +76,7 @@ def copy_file(source, dest, overwrite=False):
|
||||||
shutil.copy2(source, dest)
|
shutil.copy2(source, dest)
|
||||||
|
|
||||||
|
|
||||||
def delete_empty_folders(path):
|
def delete_empty_folders(path: pathlib.Path | str) -> None:
|
||||||
"""Recursively delete all empty folders in path."""
|
"""Recursively delete all empty folders in path."""
|
||||||
LOG.debug('path: %s', path)
|
LOG.debug('path: %s', path)
|
||||||
|
|
||||||
|
|
@ -89,7 +93,11 @@ def delete_empty_folders(path):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def delete_folder(path, force=False, ignore_errors=False):
|
def delete_folder(
|
||||||
|
path: pathlib.Path | str,
|
||||||
|
force: bool = False,
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
) -> None:
|
||||||
"""Delete folder if empty or if forced.
|
"""Delete folder if empty or if forced.
|
||||||
|
|
||||||
NOTE: Exceptions are not caught by this function,
|
NOTE: Exceptions are not caught by this function,
|
||||||
|
|
@ -106,7 +114,11 @@ def delete_folder(path, force=False, ignore_errors=False):
|
||||||
os.rmdir(path)
|
os.rmdir(path)
|
||||||
|
|
||||||
|
|
||||||
def delete_item(path, force=False, ignore_errors=False):
|
def delete_item(
|
||||||
|
path: pathlib.Path | str,
|
||||||
|
force: bool = False,
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
) -> None:
|
||||||
"""Delete file or folder, optionally recursively.
|
"""Delete file or folder, optionally recursively.
|
||||||
|
|
||||||
NOTE: Exceptions are not caught by this function,
|
NOTE: Exceptions are not caught by this function,
|
||||||
|
|
@ -124,7 +136,11 @@ def delete_item(path, force=False, ignore_errors=False):
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
|
|
||||||
def get_path_obj(path, expanduser=True, resolve=True):
|
def get_path_obj(
|
||||||
|
path: pathlib.Path | str,
|
||||||
|
expanduser: bool = True,
|
||||||
|
resolve: bool = True,
|
||||||
|
) -> pathlib.Path:
|
||||||
"""Get based on path, returns pathlib.Path."""
|
"""Get based on path, returns pathlib.Path."""
|
||||||
path = pathlib.Path(path)
|
path = pathlib.Path(path)
|
||||||
if expanduser:
|
if expanduser:
|
||||||
|
|
@ -134,7 +150,7 @@ def get_path_obj(path, expanduser=True, resolve=True):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def non_clobber_path(path):
|
def non_clobber_path(path: pathlib.Path | str) -> pathlib.Path:
|
||||||
"""Update path as needed to non-existing path, returns pathlib.Path."""
|
"""Update path as needed to non-existing path, returns pathlib.Path."""
|
||||||
LOG.debug('path: %s', path)
|
LOG.debug('path: %s', path)
|
||||||
path = pathlib.Path(path)
|
path = pathlib.Path(path)
|
||||||
|
|
@ -163,7 +179,10 @@ def non_clobber_path(path):
|
||||||
return new_path
|
return new_path
|
||||||
|
|
||||||
|
|
||||||
def recursive_copy(source, dest, overwrite=False):
|
def recursive_copy(
|
||||||
|
source: pathlib.Path | str,
|
||||||
|
dest: pathlib.Path | str,
|
||||||
|
overwrite: bool = False) -> None:
|
||||||
"""Copy source to dest recursively.
|
"""Copy source to dest recursively.
|
||||||
|
|
||||||
NOTE: This uses rsync style source/dest syntax.
|
NOTE: This uses rsync style source/dest syntax.
|
||||||
|
|
@ -213,7 +232,10 @@ def recursive_copy(source, dest, overwrite=False):
|
||||||
raise FileExistsError(f'Refusing to delete file: {dest}')
|
raise FileExistsError(f'Refusing to delete file: {dest}')
|
||||||
|
|
||||||
|
|
||||||
def rename_item(path, new_path):
|
def rename_item(
|
||||||
|
path: pathlib.Path | str,
|
||||||
|
new_path: pathlib.Path | str,
|
||||||
|
) -> pathlib.Path:
|
||||||
"""Rename item, returns pathlib.Path."""
|
"""Rename item, returns pathlib.Path."""
|
||||||
path = pathlib.Path(path)
|
path = pathlib.Path(path)
|
||||||
return path.rename(new_path)
|
return path.rename(new_path)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ NOTE: This script is meant to be called from within a new kit in ConEmu.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from wk.cfg.launchers import LAUNCHERS
|
from wk.cfg.launchers import LAUNCHERS
|
||||||
|
|
@ -21,16 +22,8 @@ from wk.kit.tools import (
|
||||||
get_tool_path,
|
get_tool_path,
|
||||||
)
|
)
|
||||||
from wk.log import update_log_path
|
from wk.log import update_log_path
|
||||||
from wk.std import (
|
from wk.std import GenericError
|
||||||
GenericError,
|
from wk.ui import cli as ui
|
||||||
TryAndPrint,
|
|
||||||
clear_screen,
|
|
||||||
pause,
|
|
||||||
print_info,
|
|
||||||
print_success,
|
|
||||||
set_title,
|
|
||||||
sleep,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -52,7 +45,7 @@ WIDTH = 50
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def compress_cbin_dirs():
|
def compress_cbin_dirs() -> None:
|
||||||
"""Compress CBIN_DIR items using ARCHIVE_PASSWORD."""
|
"""Compress CBIN_DIR items using ARCHIVE_PASSWORD."""
|
||||||
current_dir = os.getcwd()
|
current_dir = os.getcwd()
|
||||||
for item in CBIN_DIR.iterdir():
|
for item in CBIN_DIR.iterdir():
|
||||||
|
|
@ -70,25 +63,25 @@ def compress_cbin_dirs():
|
||||||
delete_item(item, force=True, ignore_errors=True)
|
delete_item(item, force=True, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def delete_from_temp(item_path):
|
def delete_from_temp(item_path) -> None:
|
||||||
"""Delete item from temp."""
|
"""Delete item from temp."""
|
||||||
delete_item(TMP_DIR.joinpath(item_path), force=True, ignore_errors=True)
|
delete_item(TMP_DIR.joinpath(item_path), force=True, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def download_to_temp(filename, source_url, referer=None):
|
def download_to_temp(filename, source_url, referer=None) -> pathlib.Path:
|
||||||
"""Download file to temp dir, returns pathlib.Path."""
|
"""Download file to temp dir, returns pathlib.Path."""
|
||||||
out_path = TMP_DIR.joinpath(filename)
|
out_path = TMP_DIR.joinpath(filename)
|
||||||
download_file(out_path, source_url, referer=referer)
|
download_file(out_path, source_url, referer=referer)
|
||||||
return out_path
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
def extract_to_bin(archive, folder):
|
def extract_to_bin(archive, folder) -> None:
|
||||||
"""Extract archive to folder under BIN_DIR."""
|
"""Extract archive to folder under BIN_DIR."""
|
||||||
out_path = BIN_DIR.joinpath(folder)
|
out_path = BIN_DIR.joinpath(folder)
|
||||||
extract_archive(archive, out_path)
|
extract_archive(archive, out_path)
|
||||||
|
|
||||||
|
|
||||||
def generate_launcher(section, name, options):
|
def generate_launcher(section, name, options) -> None:
|
||||||
"""Generate launcher script."""
|
"""Generate launcher script."""
|
||||||
dest = ROOT_DIR.joinpath(f'{section+"/" if section else ""}{name}.cmd')
|
dest = ROOT_DIR.joinpath(f'{section+"/" if section else ""}{name}.cmd')
|
||||||
out_text = []
|
out_text = []
|
||||||
|
|
@ -115,27 +108,27 @@ def generate_launcher(section, name, options):
|
||||||
|
|
||||||
|
|
||||||
# Download functions
|
# Download functions
|
||||||
def download_adobe_reader():
|
def download_adobe_reader() -> None:
|
||||||
"""Download Adobe Reader."""
|
"""Download Adobe Reader."""
|
||||||
out_path = INSTALLERS_DIR.joinpath('Adobe Reader DC.exe')
|
out_path = INSTALLERS_DIR.joinpath('Adobe Reader DC.exe')
|
||||||
download_file(out_path, SOURCES['Adobe Reader DC'])
|
download_file(out_path, SOURCES['Adobe Reader DC'])
|
||||||
|
|
||||||
|
|
||||||
def download_aida64():
|
def download_aida64() -> None:
|
||||||
"""Download AIDA64."""
|
"""Download AIDA64."""
|
||||||
archive = download_to_temp('AIDA64.zip', SOURCES['AIDA64'])
|
archive = download_to_temp('AIDA64.zip', SOURCES['AIDA64'])
|
||||||
extract_to_bin(archive, 'AIDA64')
|
extract_to_bin(archive, 'AIDA64')
|
||||||
delete_from_temp('AIDA64.zip')
|
delete_from_temp('AIDA64.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_autoruns():
|
def download_autoruns() -> None:
|
||||||
"""Download Autoruns."""
|
"""Download Autoruns."""
|
||||||
for item in ('Autoruns32', 'Autoruns64'):
|
for item in ('Autoruns32', 'Autoruns64'):
|
||||||
out_path = BIN_DIR.joinpath(f'Sysinternals/{item}.exe')
|
out_path = BIN_DIR.joinpath(f'Sysinternals/{item}.exe')
|
||||||
download_file(out_path, SOURCES[item])
|
download_file(out_path, SOURCES[item])
|
||||||
|
|
||||||
|
|
||||||
def download_bleachbit():
|
def download_bleachbit() -> None:
|
||||||
"""Download BleachBit."""
|
"""Download BleachBit."""
|
||||||
out_path = BIN_DIR.joinpath('BleachBit')
|
out_path = BIN_DIR.joinpath('BleachBit')
|
||||||
archive = download_to_temp('BleachBit.zip', SOURCES['BleachBit'])
|
archive = download_to_temp('BleachBit.zip', SOURCES['BleachBit'])
|
||||||
|
|
@ -150,7 +143,7 @@ def download_bleachbit():
|
||||||
delete_from_temp('BleachBit.zip')
|
delete_from_temp('BleachBit.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_bluescreenview():
|
def download_bluescreenview() -> None:
|
||||||
"""Download BlueScreenView."""
|
"""Download BlueScreenView."""
|
||||||
archive_32 = download_to_temp(
|
archive_32 = download_to_temp(
|
||||||
'bluescreenview32.zip', SOURCES['BlueScreenView32'],
|
'bluescreenview32.zip', SOURCES['BlueScreenView32'],
|
||||||
|
|
@ -169,14 +162,37 @@ def download_bluescreenview():
|
||||||
delete_from_temp('bluescreenview64.zip')
|
delete_from_temp('bluescreenview64.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_erunt():
|
def download_ddu() -> None:
|
||||||
|
"""Download Display Driver Uninstaller."""
|
||||||
|
archive = download_to_temp('DDU.exe', SOURCES['DDU'])
|
||||||
|
out_path = BIN_DIR.joinpath('DDU')
|
||||||
|
extract_archive(archive, out_path, 'DDU*/*.*', mode='e')
|
||||||
|
out_path = out_path.joinpath('Settings')
|
||||||
|
for item in ('AMD', 'INTEL', 'Languages', 'NVIDIA', 'REALTEK'):
|
||||||
|
extract_archive(
|
||||||
|
archive,
|
||||||
|
out_path.joinpath(item),
|
||||||
|
f'DDU*/Settings/{item}/*',
|
||||||
|
mode='e',
|
||||||
|
)
|
||||||
|
delete_from_temp('DDU.exe')
|
||||||
|
|
||||||
|
|
||||||
|
def download_bcuninstaller() -> None:
|
||||||
|
"""Download Bulk Crap Uninstaller."""
|
||||||
|
archive = download_to_temp('BCU.zip', SOURCES['BCUninstaller'])
|
||||||
|
extract_to_bin(archive, 'BCUninstaller')
|
||||||
|
delete_from_temp('BCU.zip')
|
||||||
|
|
||||||
|
|
||||||
|
def download_erunt() -> None:
|
||||||
"""Download ERUNT."""
|
"""Download ERUNT."""
|
||||||
archive = download_to_temp('erunt.zip', SOURCES['ERUNT'])
|
archive = download_to_temp('erunt.zip', SOURCES['ERUNT'])
|
||||||
extract_to_bin(archive, 'ERUNT')
|
extract_to_bin(archive, 'ERUNT')
|
||||||
delete_from_temp('erunt.zip')
|
delete_from_temp('erunt.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_everything():
|
def download_everything() -> None:
|
||||||
"""Download Everything."""
|
"""Download Everything."""
|
||||||
archive_32 = download_to_temp('everything32.zip', SOURCES['Everything32'])
|
archive_32 = download_to_temp('everything32.zip', SOURCES['Everything32'])
|
||||||
archive_64 = download_to_temp('everything64.zip', SOURCES['Everything64'])
|
archive_64 = download_to_temp('everything64.zip', SOURCES['Everything64'])
|
||||||
|
|
@ -191,7 +207,7 @@ def download_everything():
|
||||||
delete_from_temp('everything64.zip')
|
delete_from_temp('everything64.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_fastcopy():
|
def download_fastcopy() -> None:
|
||||||
"""Download FastCopy."""
|
"""Download FastCopy."""
|
||||||
installer = download_to_temp('FastCopyInstaller.exe', SOURCES['FastCopy'])
|
installer = download_to_temp('FastCopyInstaller.exe', SOURCES['FastCopy'])
|
||||||
out_path = BIN_DIR.joinpath('FastCopy')
|
out_path = BIN_DIR.joinpath('FastCopy')
|
||||||
|
|
@ -207,7 +223,7 @@ def download_fastcopy():
|
||||||
delete_item(BIN_DIR.joinpath('FastCopy/setup.exe'))
|
delete_item(BIN_DIR.joinpath('FastCopy/setup.exe'))
|
||||||
|
|
||||||
|
|
||||||
def download_furmark():
|
def download_furmark() -> None:
|
||||||
"""Download FurMark."""
|
"""Download FurMark."""
|
||||||
installer = download_to_temp(
|
installer = download_to_temp(
|
||||||
'FurMark_Setup.exe',
|
'FurMark_Setup.exe',
|
||||||
|
|
@ -227,28 +243,32 @@ def download_furmark():
|
||||||
delete_from_temp('FurMarkInstall')
|
delete_from_temp('FurMarkInstall')
|
||||||
|
|
||||||
|
|
||||||
def download_hwinfo():
|
def download_hwinfo() -> None:
|
||||||
"""Download HWiNFO."""
|
"""Download HWiNFO."""
|
||||||
archive = download_to_temp('HWiNFO.zip', SOURCES['HWiNFO'])
|
archive = download_to_temp('HWiNFO.zip', SOURCES['HWiNFO'])
|
||||||
extract_to_bin(archive, 'HWiNFO')
|
extract_to_bin(archive, 'HWiNFO')
|
||||||
delete_from_temp('HWiNFO.zip')
|
delete_from_temp('HWiNFO.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_macs_fan_control():
|
def download_macs_fan_control() -> None:
|
||||||
"""Download Macs Fan Control."""
|
"""Download Macs Fan Control."""
|
||||||
out_path = INSTALLERS_DIR.joinpath('Macs Fan Control.exe')
|
out_path = INSTALLERS_DIR.joinpath('Macs Fan Control.exe')
|
||||||
download_file(out_path, SOURCES['Macs Fan Control'])
|
download_file(out_path, SOURCES['Macs Fan Control'])
|
||||||
|
|
||||||
|
|
||||||
def download_libreoffice():
|
def download_libreoffice() -> None:
|
||||||
"""Download LibreOffice."""
|
"""Download LibreOffice."""
|
||||||
for arch in 32, 64:
|
for arch in 32, 64:
|
||||||
out_path = INSTALLERS_DIR.joinpath(f'LibreOffice{arch}.msi')
|
out_path = INSTALLERS_DIR.joinpath(f'LibreOffice{arch}.msi')
|
||||||
download_file(out_path, SOURCES[f'LibreOffice{arch}'])
|
download_file(
|
||||||
sleep(1)
|
out_path,
|
||||||
|
SOURCES[f'LibreOffice{arch}'],
|
||||||
|
referer='https://www.libreoffice.org/download/download-libreoffice/',
|
||||||
|
)
|
||||||
|
ui.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
def download_neutron():
|
def download_neutron() -> None:
|
||||||
"""Download Neutron."""
|
"""Download Neutron."""
|
||||||
archive = download_to_temp('neutron.zip', SOURCES['Neutron'])
|
archive = download_to_temp('neutron.zip', SOURCES['Neutron'])
|
||||||
out_path = BIN_DIR.joinpath('Neutron')
|
out_path = BIN_DIR.joinpath('Neutron')
|
||||||
|
|
@ -256,7 +276,7 @@ def download_neutron():
|
||||||
delete_from_temp('neutron.zip')
|
delete_from_temp('neutron.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_notepad_plus_plus():
|
def download_notepad_plus_plus() -> None:
|
||||||
"""Download Notepad++."""
|
"""Download Notepad++."""
|
||||||
archive = download_to_temp('npp.7z', SOURCES['Notepad++'])
|
archive = download_to_temp('npp.7z', SOURCES['Notepad++'])
|
||||||
extract_to_bin(archive, 'NotepadPlusPlus')
|
extract_to_bin(archive, 'NotepadPlusPlus')
|
||||||
|
|
@ -268,21 +288,21 @@ def download_notepad_plus_plus():
|
||||||
delete_from_temp('npp.7z')
|
delete_from_temp('npp.7z')
|
||||||
|
|
||||||
|
|
||||||
def download_openshell():
|
def download_openshell() -> None:
|
||||||
"""Download OpenShell installer and Fluent-Metro skin."""
|
"""Download OpenShell installer and Fluent-Metro skin."""
|
||||||
for name in ('OpenShell.exe', 'Fluent-Metro.zip'):
|
for name in ('OpenShell.exe', 'Fluent-Metro.zip'):
|
||||||
out_path = BIN_DIR.joinpath(f'OpenShell/{name}')
|
out_path = BIN_DIR.joinpath(f'OpenShell/{name}')
|
||||||
download_file(out_path, SOURCES[name[:-4]])
|
download_file(out_path, SOURCES[name[:-4]])
|
||||||
|
|
||||||
|
|
||||||
def download_putty():
|
def download_putty() -> None:
|
||||||
"""Download PuTTY."""
|
"""Download PuTTY."""
|
||||||
archive = download_to_temp('putty.zip', SOURCES['PuTTY'])
|
archive = download_to_temp('putty.zip', SOURCES['PuTTY'])
|
||||||
extract_to_bin(archive, 'PuTTY')
|
extract_to_bin(archive, 'PuTTY')
|
||||||
delete_from_temp('putty.zip')
|
delete_from_temp('putty.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_snappy_driver_installer_origin():
|
def download_snappy_driver_installer_origin() -> None:
|
||||||
"""Download Snappy Driver Installer Origin."""
|
"""Download Snappy Driver Installer Origin."""
|
||||||
archive = download_to_temp('aria2.zip', SOURCES['Aria2'])
|
archive = download_to_temp('aria2.zip', SOURCES['Aria2'])
|
||||||
aria2c = TMP_DIR.joinpath('aria2/aria2c.exe')
|
aria2c = TMP_DIR.joinpath('aria2/aria2c.exe')
|
||||||
|
|
@ -315,7 +335,7 @@ def download_snappy_driver_installer_origin():
|
||||||
cmd.append('-new_console:n')
|
cmd.append('-new_console:n')
|
||||||
cmd.append('-new_console:s33V')
|
cmd.append('-new_console:s33V')
|
||||||
popen_program(cmd, cwd=aria2c.parent)
|
popen_program(cmd, cwd=aria2c.parent)
|
||||||
sleep(1)
|
ui.sleep(1)
|
||||||
wait_for_procs('aria2c.exe')
|
wait_for_procs('aria2c.exe')
|
||||||
else:
|
else:
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
@ -352,29 +372,14 @@ def download_snappy_driver_installer_origin():
|
||||||
delete_from_temp('fake.7z')
|
delete_from_temp('fake.7z')
|
||||||
|
|
||||||
|
|
||||||
def download_uninstallview():
|
def download_wiztree() -> None:
|
||||||
"""Download UninstallView."""
|
|
||||||
archive_32 = download_to_temp('uninstallview32.zip', SOURCES['UninstallView32'])
|
|
||||||
archive_64 = download_to_temp('uninstallview64.zip', SOURCES['UninstallView64'])
|
|
||||||
out_path = BIN_DIR.joinpath('UninstallView')
|
|
||||||
extract_archive(archive_64, out_path, 'UninstallView.exe')
|
|
||||||
rename_item(
|
|
||||||
out_path.joinpath('UninstallView.exe'),
|
|
||||||
out_path.joinpath('UninstallView64.exe'),
|
|
||||||
)
|
|
||||||
extract_archive(archive_32, out_path)
|
|
||||||
delete_from_temp('uninstallview32.zip')
|
|
||||||
delete_from_temp('uninstallview64.zip')
|
|
||||||
|
|
||||||
|
|
||||||
def download_wiztree():
|
|
||||||
"""Download WizTree."""
|
"""Download WizTree."""
|
||||||
archive = download_to_temp('wiztree.zip', SOURCES['WizTree'])
|
archive = download_to_temp('wiztree.zip', SOURCES['WizTree'])
|
||||||
extract_to_bin(archive, 'WizTree')
|
extract_to_bin(archive, 'WizTree')
|
||||||
delete_from_temp('wiztree.zip')
|
delete_from_temp('wiztree.zip')
|
||||||
|
|
||||||
|
|
||||||
def download_xmplay():
|
def download_xmplay() -> None:
|
||||||
"""Download XMPlay."""
|
"""Download XMPlay."""
|
||||||
archives = [
|
archives = [
|
||||||
download_to_temp('xmplay.zip', SOURCES['XMPlay']),
|
download_to_temp('xmplay.zip', SOURCES['XMPlay']),
|
||||||
|
|
@ -390,7 +395,7 @@ def download_xmplay():
|
||||||
args = [archive, BIN_DIR.joinpath('XMPlay/plugins')]
|
args = [archive, BIN_DIR.joinpath('XMPlay/plugins')]
|
||||||
if archive.name == 'Innocuous.zip':
|
if archive.name == 'Innocuous.zip':
|
||||||
args.append(
|
args.append(
|
||||||
'Innocuous (v1.5)/Innocuous (Hue Shifted)/'
|
'Innocuous (v1.7)/Innocuous (Hue Shifted)/'
|
||||||
'Innocuous (Dark Skies - Purple-80) [L1].xmpskin'
|
'Innocuous (Dark Skies - Purple-80) [L1].xmpskin'
|
||||||
)
|
)
|
||||||
extract_archive(*args, mode='e')
|
extract_archive(*args, mode='e')
|
||||||
|
|
@ -402,7 +407,7 @@ def download_xmplay():
|
||||||
delete_from_temp('xmp-rar.zip')
|
delete_from_temp('xmp-rar.zip')
|
||||||
delete_from_temp('Innocuous.zip')
|
delete_from_temp('Innocuous.zip')
|
||||||
|
|
||||||
def download_xmplay_music():
|
def download_xmplay_music() -> None:
|
||||||
"""Download XMPlay Music."""
|
"""Download XMPlay Music."""
|
||||||
music_tmp = TMP_DIR.joinpath('music')
|
music_tmp = TMP_DIR.joinpath('music')
|
||||||
music_tmp.mkdir(exist_ok=True)
|
music_tmp.mkdir(exist_ok=True)
|
||||||
|
|
@ -455,17 +460,17 @@ def download_xmplay_music():
|
||||||
|
|
||||||
|
|
||||||
# "Main" Function
|
# "Main" Function
|
||||||
def build_kit():
|
def build_kit() -> None:
|
||||||
"""Build Kit."""
|
"""Build Kit."""
|
||||||
update_log_path(dest_name='Build Tool', timestamp=True)
|
update_log_path(dest_name='Build Tool', timestamp=True)
|
||||||
title = f'{KIT_NAME_FULL}: Build Tool'
|
title = f'{KIT_NAME_FULL}: Build Tool'
|
||||||
clear_screen()
|
ui.clear_screen()
|
||||||
set_title(title)
|
ui.set_title(title)
|
||||||
print_info(title)
|
ui.print_info(title)
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
# Set up TryAndPrint
|
# Set up TryAndPrint
|
||||||
try_print = TryAndPrint()
|
try_print = ui.TryAndPrint()
|
||||||
try_print.width = WIDTH
|
try_print.width = WIDTH
|
||||||
try_print.verbose = True
|
try_print.verbose = True
|
||||||
for error in ('CalledProcessError', 'FileNotFoundError'):
|
for error in ('CalledProcessError', 'FileNotFoundError'):
|
||||||
|
|
@ -478,6 +483,8 @@ def build_kit():
|
||||||
try_print.run('BleachBit...', download_bleachbit)
|
try_print.run('BleachBit...', download_bleachbit)
|
||||||
try_print.run('BlueScreenView...', download_bluescreenview)
|
try_print.run('BlueScreenView...', download_bluescreenview)
|
||||||
try_print.run('ERUNT...', download_erunt)
|
try_print.run('ERUNT...', download_erunt)
|
||||||
|
try_print.run('BulkCrapUninstaller...', download_bcuninstaller)
|
||||||
|
try_print.run('DDU...', download_ddu)
|
||||||
try_print.run('Everything...', download_everything)
|
try_print.run('Everything...', download_everything)
|
||||||
try_print.run('FastCopy...', download_fastcopy)
|
try_print.run('FastCopy...', download_fastcopy)
|
||||||
try_print.run('FurMark...', download_furmark)
|
try_print.run('FurMark...', download_furmark)
|
||||||
|
|
@ -489,22 +496,21 @@ def build_kit():
|
||||||
try_print.run('OpenShell...', download_openshell)
|
try_print.run('OpenShell...', download_openshell)
|
||||||
try_print.run('PuTTY...', download_putty)
|
try_print.run('PuTTY...', download_putty)
|
||||||
try_print.run('Snappy Driver Installer...', download_snappy_driver_installer_origin)
|
try_print.run('Snappy Driver Installer...', download_snappy_driver_installer_origin)
|
||||||
try_print.run('UninstallView...', download_uninstallview)
|
|
||||||
try_print.run('WizTree...', download_wiztree)
|
try_print.run('WizTree...', download_wiztree)
|
||||||
try_print.run('XMPlay...', download_xmplay)
|
try_print.run('XMPlay...', download_xmplay)
|
||||||
try_print.run('XMPlay Music...', download_xmplay_music)
|
try_print.run('XMPlay Music...', download_xmplay_music)
|
||||||
|
|
||||||
# Pause
|
# Pause
|
||||||
print('', flush=True)
|
print('', flush=True)
|
||||||
pause('Please review and press Enter to continue...')
|
ui.pause('Please review and press Enter to continue...')
|
||||||
|
|
||||||
# Compress .cbin
|
# Compress .cbin
|
||||||
try_print.run('Compress cbin...', compress_cbin_dirs)
|
try_print.run('Compress cbin...', compress_cbin_dirs)
|
||||||
|
|
||||||
# Generate launcher scripts
|
# Generate launcher scripts
|
||||||
print_success('Generating launchers')
|
ui.print_success('Generating launchers')
|
||||||
for section, launchers in sorted(LAUNCHERS.items()):
|
for section, launchers in sorted(LAUNCHERS.items()):
|
||||||
print_info(f' {section if section else "(Root)"}')
|
ui.print_info(f' {section if section else "(Root)"}')
|
||||||
for name, options in sorted(launchers.items()):
|
for name, options in sorted(launchers.items()):
|
||||||
try_print.run(
|
try_print.run(
|
||||||
f' {name}...', generate_launcher,
|
f' {name}...', generate_launcher,
|
||||||
|
|
@ -514,7 +520,7 @@ def build_kit():
|
||||||
# Done
|
# Done
|
||||||
print('')
|
print('')
|
||||||
print('Done.')
|
print('Done.')
|
||||||
pause('Press Enter to exit...')
|
ui.pause('Press Enter to exit...')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
"""WizardKit: Tool Functions"""
|
"""WizardKit: Tool Functions"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from subprocess import CompletedProcess, Popen
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from wk.cfg.main import ARCHIVE_PASSWORD
|
from wk.cfg.main import ARCHIVE_PASSWORD
|
||||||
|
|
@ -30,7 +32,9 @@ CACHED_DIRS = {}
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def download_file(out_path, source_url, as_new=False, overwrite=False, referer=None):
|
def download_file(
|
||||||
|
out_path, source_url,
|
||||||
|
as_new=False, overwrite=False, referer=None) -> pathlib.Path:
|
||||||
"""Download a file using requests, returns pathlib.Path."""
|
"""Download a file using requests, returns pathlib.Path."""
|
||||||
out_path = pathlib.Path(out_path).resolve()
|
out_path = pathlib.Path(out_path).resolve()
|
||||||
name = out_path.name
|
name = out_path.name
|
||||||
|
|
@ -38,6 +42,7 @@ def download_file(out_path, source_url, as_new=False, overwrite=False, referer=N
|
||||||
download_msg = f'Downloading {name}...'
|
download_msg = f'Downloading {name}...'
|
||||||
if as_new:
|
if as_new:
|
||||||
out_path = out_path.with_suffix(f'{out_path.suffix}.new')
|
out_path = out_path.with_suffix(f'{out_path.suffix}.new')
|
||||||
|
overwrite = True
|
||||||
print(download_msg, end='', flush=True)
|
print(download_msg, end='', flush=True)
|
||||||
|
|
||||||
# Avoid clobbering
|
# Avoid clobbering
|
||||||
|
|
@ -94,7 +99,7 @@ def download_file(out_path, source_url, as_new=False, overwrite=False, referer=N
|
||||||
return out_path
|
return out_path
|
||||||
|
|
||||||
|
|
||||||
def download_tool(folder, name, suffix=None):
|
def download_tool(folder, name, suffix=None) -> None:
|
||||||
"""Download tool."""
|
"""Download tool."""
|
||||||
name_arch = f'{name}{ARCH}'
|
name_arch = f'{name}{ARCH}'
|
||||||
out_path = get_tool_path(folder, name, check=False, suffix=suffix)
|
out_path = get_tool_path(folder, name, check=False, suffix=suffix)
|
||||||
|
|
@ -129,7 +134,7 @@ def download_tool(folder, name, suffix=None):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def extract_archive(archive, out_path, *args, mode='x', silent=True):
|
def extract_archive(archive, out_path, *args, mode='x', silent=True) -> None:
|
||||||
"""Extract an archive to out_path."""
|
"""Extract an archive to out_path."""
|
||||||
out_path = pathlib.Path(out_path).resolve()
|
out_path = pathlib.Path(out_path).resolve()
|
||||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -141,7 +146,7 @@ def extract_archive(archive, out_path, *args, mode='x', silent=True):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def extract_tool(folder):
|
def extract_tool(folder) -> None:
|
||||||
"""Extract tool."""
|
"""Extract tool."""
|
||||||
extract_archive(
|
extract_archive(
|
||||||
find_kit_dir('.cbin').joinpath(folder).with_suffix('.7z'),
|
find_kit_dir('.cbin').joinpath(folder).with_suffix('.7z'),
|
||||||
|
|
@ -150,7 +155,7 @@ def extract_tool(folder):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def find_kit_dir(name=None):
|
def find_kit_dir(name=None) -> pathlib.Path:
|
||||||
"""Find folder in kit, returns pathlib.Path.
|
"""Find folder in kit, returns pathlib.Path.
|
||||||
|
|
||||||
Search is performed in the script's path and then recursively upwards.
|
Search is performed in the script's path and then recursively upwards.
|
||||||
|
|
@ -177,7 +182,7 @@ def find_kit_dir(name=None):
|
||||||
return cur_path
|
return cur_path
|
||||||
|
|
||||||
|
|
||||||
def get_tool_path(folder, name, check=True, suffix=None):
|
def get_tool_path(folder, name, check=True, suffix=None) -> pathlib.Path:
|
||||||
"""Get tool path, returns pathlib.Path"""
|
"""Get tool path, returns pathlib.Path"""
|
||||||
bin_dir = find_kit_dir('.bin')
|
bin_dir = find_kit_dir('.bin')
|
||||||
if not suffix:
|
if not suffix:
|
||||||
|
|
@ -202,7 +207,7 @@ def run_tool(
|
||||||
folder, name, *run_args,
|
folder, name, *run_args,
|
||||||
cbin=False, cwd=False, download=False, popen=False,
|
cbin=False, cwd=False, download=False, popen=False,
|
||||||
**run_kwargs,
|
**run_kwargs,
|
||||||
):
|
) -> CompletedProcess | Popen:
|
||||||
"""Run tool from the kit or the Internet, returns proc obj.
|
"""Run tool from the kit or the Internet, returns proc obj.
|
||||||
|
|
||||||
proc will be either subprocess.CompletedProcess or subprocess.Popen."""
|
proc will be either subprocess.CompletedProcess or subprocess.Popen."""
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,33 @@
|
||||||
"""WizardKit: UFD Functions"""
|
"""WizardKit: UFD Functions"""
|
||||||
# vim: sts=2 sw=2 ts=2
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
|
|
||||||
from collections import OrderedDict
|
from wk import io, log
|
||||||
from docopt import docopt
|
|
||||||
|
|
||||||
from wk import io, log, std
|
|
||||||
from wk.cfg.main import KIT_NAME_FULL, KIT_NAME_SHORT
|
from wk.cfg.main import KIT_NAME_FULL, KIT_NAME_SHORT
|
||||||
from wk.cfg.ufd import (
|
from wk.cfg.ufd import (
|
||||||
BOOT_ENTRIES,
|
BOOT_ENTRIES,
|
||||||
BOOT_FILES,
|
BOOT_FILES,
|
||||||
IMAGE_BOOT_ENTRIES,
|
IMAGE_BOOT_ENTRIES,
|
||||||
ITEMS,
|
ITEMS,
|
||||||
|
ITEMS_FROM_LIVE,
|
||||||
ITEMS_HIDDEN,
|
ITEMS_HIDDEN,
|
||||||
SOURCES,
|
SOURCES,
|
||||||
)
|
)
|
||||||
from wk.exe import get_json_from_command, run_program
|
from wk.exe import get_json_from_command, run_program
|
||||||
from wk.os import linux
|
from wk.os import linux
|
||||||
|
|
||||||
|
from wk.ui import cli as ui
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
DOCSTRING = '''WizardKit: Build UFD
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
build-ufd [options] --ufd-device PATH
|
|
||||||
[--linux PATH]
|
|
||||||
[--main-kit PATH]
|
|
||||||
[--winpe PATH]
|
|
||||||
[--extra-dir PATH]
|
|
||||||
[EXTRA_IMAGES...]
|
|
||||||
build-ufd (-h | --help)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-e PATH, --extra-dir PATH
|
|
||||||
-k PATH, --main-kit PATH
|
|
||||||
-l PATH, --linux PATH
|
|
||||||
-u PATH, --ufd-device PATH
|
|
||||||
-w PATH, --winpe PATH
|
|
||||||
|
|
||||||
-d --debug Enable debug mode
|
|
||||||
-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
|
|
||||||
'''
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
EXTRA_IMAGES_LIST = '/mnt/UFD/arch/extra_images.list'
|
EXTRA_IMAGES_LIST = '/mnt/UFD/arch/extra_images.list'
|
||||||
MIB = 1024 ** 2
|
MIB = 1024 ** 2
|
||||||
|
|
@ -57,7 +36,55 @@ UFD_LABEL = f'{KIT_NAME_SHORT}_UFD'
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def apply_image(part_path, image_path, hide_macos_boot=True):
|
def argparse_helper() -> dict[str, None|bool|str]:
|
||||||
|
"""Helper function to setup and return args, returns dict.
|
||||||
|
|
||||||
|
NOTE: A dict is used to match the legacy code.
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='build-ufd',
|
||||||
|
description=f'{KIT_NAME_FULL}: Build UFD',
|
||||||
|
)
|
||||||
|
parser.add_argument('-u', '--ufd-device', required=True)
|
||||||
|
parser.add_argument('-l', '--linux', required=False)
|
||||||
|
parser.add_argument('-e', '--extra-dir', required=False)
|
||||||
|
parser.add_argument('-k', '--main-kit', required=False)
|
||||||
|
parser.add_argument('-w', '--winpe', required=False)
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--debug', action='store_true',
|
||||||
|
help='Enable debug mode',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-M', '--use-mbr', action='store_true',
|
||||||
|
help='Use real MBR instead of GPT w/ Protective MBR',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-F', '--force', action='store_true',
|
||||||
|
help='Bypass all confirmation messages. USE WITH EXTREME CAUTION!',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-U', '--update', action='store_true',
|
||||||
|
help="Don't format device, just update",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'EXTRA_IMAGES', nargs='*',
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
legacy_args = {
|
||||||
|
'--debug': args.debug,
|
||||||
|
'--extra-dir': args.extra_dir,
|
||||||
|
'--force': args.force,
|
||||||
|
'--linux': args.linux,
|
||||||
|
'--main-kit': args.main_kit,
|
||||||
|
'--ufd-device': args.ufd_device,
|
||||||
|
'--update': args.update,
|
||||||
|
'--use-mbr': args.use_mbr,
|
||||||
|
'--winpe': args.winpe,
|
||||||
|
'EXTRA_IMAGES': args.EXTRA_IMAGES,
|
||||||
|
}
|
||||||
|
return legacy_args
|
||||||
|
|
||||||
|
def apply_image(part_path, image_path, hide_macos_boot=True) -> None:
|
||||||
"""Apply raw image to dev_path using dd."""
|
"""Apply raw image to dev_path using dd."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
|
|
@ -87,16 +114,21 @@ def apply_image(part_path, image_path, hide_macos_boot=True):
|
||||||
linux.unmount(source_or_mountpoint='/mnt/TMP')
|
linux.unmount(source_or_mountpoint='/mnt/TMP')
|
||||||
|
|
||||||
|
|
||||||
def build_ufd():
|
def build_ufd() -> None:
|
||||||
"""Build UFD using selected sources."""
|
"""Build UFD using selected sources."""
|
||||||
args = docopt(DOCSTRING)
|
try:
|
||||||
|
args = argparse_helper()
|
||||||
|
except SystemExit:
|
||||||
|
print('')
|
||||||
|
ui.pause('Press Enter to exit...')
|
||||||
|
raise
|
||||||
if args['--debug']:
|
if args['--debug']:
|
||||||
log.enable_debug_mode()
|
log.enable_debug_mode()
|
||||||
if args['--update'] and args['EXTRA_IMAGES']:
|
if args['--update'] and args['EXTRA_IMAGES']:
|
||||||
std.print_warning('Extra images are ignored when updating')
|
ui.print_warning('Extra images are ignored when updating')
|
||||||
args['EXTRA_IMAGES'] = []
|
args['EXTRA_IMAGES'] = []
|
||||||
log.update_log_path(dest_name='build-ufd', timestamp=True)
|
log.update_log_path(dest_name='build-ufd', timestamp=True)
|
||||||
try_print = std.TryAndPrint()
|
try_print = ui.TryAndPrint()
|
||||||
try_print.add_error('FileNotFoundError')
|
try_print.add_error('FileNotFoundError')
|
||||||
try_print.catch_all = False
|
try_print.catch_all = False
|
||||||
try_print.indent = 2
|
try_print.indent = 2
|
||||||
|
|
@ -104,9 +136,9 @@ def build_ufd():
|
||||||
try_print.width = 64
|
try_print.width = 64
|
||||||
|
|
||||||
# Show header
|
# Show header
|
||||||
std.print_success(KIT_NAME_FULL)
|
ui.print_success(KIT_NAME_FULL)
|
||||||
std.print_warning('UFD Build Tool')
|
ui.print_warning('UFD Build Tool')
|
||||||
std.print_warning(' ')
|
ui.print_warning(' ')
|
||||||
|
|
||||||
# Verify selections
|
# Verify selections
|
||||||
ufd_dev = verify_ufd(args['--ufd-device'])
|
ufd_dev = verify_ufd(args['--ufd-device'])
|
||||||
|
|
@ -118,9 +150,9 @@ def build_ufd():
|
||||||
|
|
||||||
# Prep UFD
|
# Prep UFD
|
||||||
if not args['--update']:
|
if not args['--update']:
|
||||||
std.print_info('Prep UFD')
|
ui.print_info('Prep UFD')
|
||||||
try_print.run(
|
try_print.run(
|
||||||
message='Zeroing first 64MiB...',
|
message='Zeroing first 1MiB...',
|
||||||
function=zero_device,
|
function=zero_device,
|
||||||
dev_path=ufd_dev,
|
dev_path=ufd_dev,
|
||||||
)
|
)
|
||||||
|
|
@ -143,6 +175,13 @@ def build_ufd():
|
||||||
dev_path=ufd_dev,
|
dev_path=ufd_dev,
|
||||||
label=UFD_LABEL,
|
label=UFD_LABEL,
|
||||||
)
|
)
|
||||||
|
try_print.run(
|
||||||
|
message='Hiding extra partition(s)...',
|
||||||
|
function=hide_extra_partitions,
|
||||||
|
dev_path=ufd_dev,
|
||||||
|
num_parts=len(extra_images),
|
||||||
|
use_mbr=args['--use-mbr'],
|
||||||
|
)
|
||||||
ufd_dev_first_partition = find_first_partition(ufd_dev)
|
ufd_dev_first_partition = find_first_partition(ufd_dev)
|
||||||
|
|
||||||
# Mount UFD
|
# Mount UFD
|
||||||
|
|
@ -168,14 +207,25 @@ def build_ufd():
|
||||||
message='Removing Linux...',
|
message='Removing Linux...',
|
||||||
function=remove_arch,
|
function=remove_arch,
|
||||||
)
|
)
|
||||||
|
# Copy boot files
|
||||||
|
ui.print_standard(' ')
|
||||||
|
ui.print_info('Boot Files')
|
||||||
|
for s_section, s_items in ITEMS_FROM_LIVE.items():
|
||||||
|
s_section = pathlib.Path(s_section)
|
||||||
|
try_print.run(
|
||||||
|
message=f'Copying {s_section}...',
|
||||||
|
function=copy_source,
|
||||||
|
source=s_section,
|
||||||
|
items=s_items,
|
||||||
|
from_live=True,
|
||||||
|
overwrite=True,
|
||||||
|
)
|
||||||
|
os.rename('/mnt/UFD/EFI/Boot/refind_x64.efi', '/mnt/UFD/EFI/Boot/bootx64.efi')
|
||||||
|
|
||||||
|
|
||||||
# Copy sources
|
# Copy sources
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
std.print_info('Copy Sources')
|
ui.print_info('Copy Sources')
|
||||||
try_print.run(
|
|
||||||
'Copying Memtest86...', io.recursive_copy,
|
|
||||||
'/usr/share/memtest86-efi/', '/mnt/UFD/EFI/Memtest86/', overwrite=True,
|
|
||||||
)
|
|
||||||
for s_label, s_path in sources.items():
|
for s_label, s_path in sources.items():
|
||||||
try_print.run(
|
try_print.run(
|
||||||
message=f'Copying {s_label}...',
|
message=f'Copying {s_label}...',
|
||||||
|
|
@ -187,8 +237,8 @@ def build_ufd():
|
||||||
|
|
||||||
# Apply extra images
|
# Apply extra images
|
||||||
if not args['--update']:
|
if not args['--update']:
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
std.print_info('Apply Extra Images')
|
ui.print_info('Apply Extra Images')
|
||||||
for part_num, image_path in enumerate(extra_images):
|
for part_num, image_path in enumerate(extra_images):
|
||||||
try_print.run(
|
try_print.run(
|
||||||
message=f'Applying {image_path.name}...',
|
message=f'Applying {image_path.name}...',
|
||||||
|
|
@ -203,8 +253,8 @@ def build_ufd():
|
||||||
_f.write('\n'.join([image.name for image in extra_images]))
|
_f.write('\n'.join([image.name for image in extra_images]))
|
||||||
|
|
||||||
# Update boot entries
|
# Update boot entries
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
std.print_info('Boot Setup')
|
ui.print_info('Boot Setup')
|
||||||
try_print.run(
|
try_print.run(
|
||||||
message='Updating boot entries...',
|
message='Updating boot entries...',
|
||||||
function=update_boot_entries,
|
function=update_boot_entries,
|
||||||
|
|
@ -235,8 +285,8 @@ def build_ufd():
|
||||||
)
|
)
|
||||||
|
|
||||||
# Hide items
|
# Hide items
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
std.print_info('Final Touches')
|
ui.print_info('Final Touches')
|
||||||
try_print.run(
|
try_print.run(
|
||||||
message='Hiding items...',
|
message='Hiding items...',
|
||||||
function=hide_items,
|
function=hide_items,
|
||||||
|
|
@ -245,35 +295,35 @@ def build_ufd():
|
||||||
)
|
)
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
std.print_standard('\nDone.')
|
ui.print_standard('\nDone.')
|
||||||
if not args['--force']:
|
if not args['--force']:
|
||||||
std.pause('Press Enter to exit...')
|
ui.pause('Press Enter to exit...')
|
||||||
|
|
||||||
|
|
||||||
def confirm_selections(update=False):
|
def confirm_selections(update=False) -> None:
|
||||||
"""Ask tech to confirm selections, twice if necessary."""
|
"""Ask tech to confirm selections, twice if necessary."""
|
||||||
if not std.ask('Is the above information correct?'):
|
if not ui.ask('Is the above information correct?'):
|
||||||
std.abort()
|
ui.abort()
|
||||||
|
|
||||||
# Safety check
|
# Safety check
|
||||||
if not update:
|
if not update:
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
std.print_warning('SAFETY CHECK')
|
ui.print_warning('SAFETY CHECK')
|
||||||
std.print_standard(
|
ui.print_standard(
|
||||||
'All data will be DELETED from the disk and partition(s) listed above.')
|
'All data will be DELETED from the disk and partition(s) listed above.')
|
||||||
std.print_colored(
|
ui.print_colored(
|
||||||
['This is irreversible and will lead to', 'DATA LOSS'],
|
['This is irreversible and will lead to', 'DATA LOSS'],
|
||||||
[None, 'RED'],
|
[None, 'RED'],
|
||||||
)
|
)
|
||||||
if not std.ask('Asking again to confirm, is this correct?'):
|
if not ui.ask('Asking again to confirm, is this correct?'):
|
||||||
std.abort()
|
ui.abort()
|
||||||
|
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
|
|
||||||
|
|
||||||
def copy_source(source, items, overwrite=False):
|
def copy_source(source, items, from_live=False, overwrite=False) -> None:
|
||||||
"""Copy source items to /mnt/UFD."""
|
"""Copy source items to /mnt/UFD."""
|
||||||
is_image = source.is_file()
|
is_image = not from_live and (source.is_file() or source.is_block_device())
|
||||||
items_not_found = False
|
items_not_found = False
|
||||||
|
|
||||||
# Mount source if necessary
|
# Mount source if necessary
|
||||||
|
|
@ -282,7 +332,14 @@ def copy_source(source, items, overwrite=False):
|
||||||
|
|
||||||
# Copy items
|
# Copy items
|
||||||
for i_source, i_dest in items:
|
for i_source, i_dest in items:
|
||||||
i_source = f'{"/mnt/Source" if is_image else source}{i_source}'
|
if from_live:
|
||||||
|
# Don't prepend source
|
||||||
|
pass
|
||||||
|
elif is_image:
|
||||||
|
i_source = f'/mnt/Source{i_source}'
|
||||||
|
else:
|
||||||
|
# Prepend source
|
||||||
|
i_source = f'{source}{i_source}'
|
||||||
i_dest = f'/mnt/UFD{i_dest}'
|
i_dest = f'/mnt/UFD{i_dest}'
|
||||||
try:
|
try:
|
||||||
io.recursive_copy(i_source, i_dest, overwrite=overwrite)
|
io.recursive_copy(i_source, i_dest, overwrite=overwrite)
|
||||||
|
|
@ -298,7 +355,7 @@ def copy_source(source, items, overwrite=False):
|
||||||
raise FileNotFoundError('One or more items not found')
|
raise FileNotFoundError('One or more items not found')
|
||||||
|
|
||||||
|
|
||||||
def create_table(dev_path, use_mbr=False, images=None):
|
def create_table(dev_path, use_mbr=False, images=None) -> None:
|
||||||
"""Create GPT or DOS partition table."""
|
"""Create GPT or DOS partition table."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
|
|
@ -328,7 +385,7 @@ def create_table(dev_path, use_mbr=False, images=None):
|
||||||
for part, real in zip(part_sizes, images):
|
for part, real in zip(part_sizes, images):
|
||||||
end = start + real
|
end = start + real
|
||||||
cmd.append(
|
cmd.append(
|
||||||
f'mkpart primary {"fat32" if start==MIB else "hfs+"} {start}B {end-1}B',
|
f'mkpart primary "fat32" {start}B {end-1}B',
|
||||||
)
|
)
|
||||||
start += part
|
start += part
|
||||||
|
|
||||||
|
|
@ -336,7 +393,7 @@ def create_table(dev_path, use_mbr=False, images=None):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def find_first_partition(dev_path):
|
def find_first_partition(dev_path) -> str:
|
||||||
"""Find path to first partition of dev, returns str."""
|
"""Find path to first partition of dev, returns str."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'lsblk',
|
'lsblk',
|
||||||
|
|
@ -355,7 +412,7 @@ def find_first_partition(dev_path):
|
||||||
return part_path
|
return part_path
|
||||||
|
|
||||||
|
|
||||||
def format_partition(dev_path, label):
|
def format_partition(dev_path, label) -> None:
|
||||||
"""Format first partition on device FAT32."""
|
"""Format first partition on device FAT32."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
|
|
@ -367,7 +424,7 @@ def format_partition(dev_path, label):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def get_block_device_size(dev_path):
|
def get_block_device_size(dev_path) -> int:
|
||||||
"""Get block device size via lsblk, returns int."""
|
"""Get block device size via lsblk, returns int."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'lsblk',
|
'lsblk',
|
||||||
|
|
@ -386,7 +443,7 @@ def get_block_device_size(dev_path):
|
||||||
return int(proc.stdout.strip())
|
return int(proc.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
def get_uuid(path):
|
def get_uuid(path) -> str:
|
||||||
"""Get filesystem UUID via findmnt, returns str."""
|
"""Get filesystem UUID via findmnt, returns str."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'findmnt',
|
'findmnt',
|
||||||
|
|
@ -402,7 +459,7 @@ def get_uuid(path):
|
||||||
return proc.stdout.strip()
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def hide_items(ufd_dev_first_partition, items):
|
def hide_items(ufd_dev_first_partition, items) -> None:
|
||||||
"""Set FAT32 hidden flag for items."""
|
"""Set FAT32 hidden flag for items."""
|
||||||
with open('/root/.mtoolsrc', 'w', encoding='utf-8') as _f:
|
with open('/root/.mtoolsrc', 'w', encoding='utf-8') as _f:
|
||||||
_f.write(f'drive U: file="{ufd_dev_first_partition}"\n')
|
_f.write(f'drive U: file="{ufd_dev_first_partition}"\n')
|
||||||
|
|
@ -414,7 +471,18 @@ def hide_items(ufd_dev_first_partition, items):
|
||||||
run_program(cmd, shell=True, check=False)
|
run_program(cmd, shell=True, check=False)
|
||||||
|
|
||||||
|
|
||||||
def install_syslinux_to_dev(ufd_dev, use_mbr):
|
def hide_extra_partitions(dev_path, num_parts, use_mbr) -> None:
|
||||||
|
if use_mbr:
|
||||||
|
# Bail early
|
||||||
|
return
|
||||||
|
|
||||||
|
for part_id in range(num_parts):
|
||||||
|
part_id += 2 # Extra partitions start at 2
|
||||||
|
cmd = ['sfdisk', '--part-attrs', dev_path, str(part_id), 'RequiredPartition,62,63']
|
||||||
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def install_syslinux_to_dev(ufd_dev, use_mbr) -> None:
|
||||||
"""Install Syslinux to UFD (dev)."""
|
"""Install Syslinux to UFD (dev)."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
|
|
@ -427,7 +495,7 @@ def install_syslinux_to_dev(ufd_dev, use_mbr):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def install_syslinux_to_partition(partition):
|
def install_syslinux_to_partition(partition) -> None:
|
||||||
"""Install Syslinux to UFD (partition)."""
|
"""Install Syslinux to UFD (partition)."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
|
|
@ -440,7 +508,7 @@ def install_syslinux_to_partition(partition):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def is_valid_path(path_obj, path_type):
|
def is_valid_path(path_obj, path_type) -> bool:
|
||||||
"""Verify path_obj is valid by type, returns bool."""
|
"""Verify path_obj is valid by type, returns bool."""
|
||||||
valid_path = False
|
valid_path = False
|
||||||
if path_type == 'DIR':
|
if path_type == 'DIR':
|
||||||
|
|
@ -451,13 +519,14 @@ def is_valid_path(path_obj, path_type):
|
||||||
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.img'
|
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.img'
|
||||||
elif path_type == 'ISO':
|
elif path_type == 'ISO':
|
||||||
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso'
|
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso'
|
||||||
|
valid_path = valid_path or re.match(r'^/dev/sr\d+$', str(path_obj))
|
||||||
elif path_type == 'UFD':
|
elif path_type == 'UFD':
|
||||||
valid_path = path_obj.is_block_device()
|
valid_path = path_obj.is_block_device()
|
||||||
|
|
||||||
return valid_path
|
return valid_path
|
||||||
|
|
||||||
|
|
||||||
def set_boot_flag(dev_path, use_mbr=False):
|
def set_boot_flag(dev_path, use_mbr=False) -> None:
|
||||||
"""Set modern or legacy boot flag."""
|
"""Set modern or legacy boot flag."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
|
|
@ -469,7 +538,7 @@ def set_boot_flag(dev_path, use_mbr=False):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def remove_arch():
|
def remove_arch() -> None:
|
||||||
"""Remove arch dir from UFD.
|
"""Remove arch dir from UFD.
|
||||||
|
|
||||||
This ensures a clean installation to the UFD and resets the boot files
|
This ensures a clean installation to the UFD and resets the boot files
|
||||||
|
|
@ -477,16 +546,16 @@ def remove_arch():
|
||||||
shutil.rmtree(io.case_insensitive_path('/mnt/UFD/arch'))
|
shutil.rmtree(io.case_insensitive_path('/mnt/UFD/arch'))
|
||||||
|
|
||||||
|
|
||||||
def show_selections(args, sources, ufd_dev, ufd_sources, extra_images):
|
def show_selections(args, sources, ufd_dev, ufd_sources, extra_images) -> None:
|
||||||
"""Show selections including non-specified options."""
|
"""Show selections including non-specified options."""
|
||||||
|
|
||||||
# Sources
|
# Sources
|
||||||
std.print_info('Sources')
|
ui.print_info('Sources')
|
||||||
for label in ufd_sources.keys():
|
for label in ufd_sources.keys():
|
||||||
if label in sources:
|
if label in sources:
|
||||||
std.print_standard(f' {label+":":<18} {sources[label]}')
|
ui.print_standard(f' {label+":":<18} {sources[label]}')
|
||||||
else:
|
else:
|
||||||
std.print_colored(
|
ui.print_colored(
|
||||||
[f' {label+":":<18}', 'Not Specified'],
|
[f' {label+":":<18}', 'Not Specified'],
|
||||||
[None, 'YELLOW'],
|
[None, 'YELLOW'],
|
||||||
)
|
)
|
||||||
|
|
@ -498,15 +567,15 @@ def show_selections(args, sources, ufd_dev, ufd_sources, extra_images):
|
||||||
print(f' {" ":<18} {image}')
|
print(f' {" ":<18} {image}')
|
||||||
|
|
||||||
# Destination
|
# Destination
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
std.print_info('Destination')
|
ui.print_info('Destination')
|
||||||
cmd = [
|
cmd = [
|
||||||
'lsblk', '--nodeps', '--noheadings', '--paths',
|
'lsblk', '--nodeps', '--noheadings', '--paths',
|
||||||
'--output', 'NAME,FSTYPE,TRAN,SIZE,VENDOR,MODEL,SERIAL',
|
'--output', 'NAME,FSTYPE,TRAN,SIZE,VENDOR,MODEL,SERIAL',
|
||||||
ufd_dev,
|
ufd_dev,
|
||||||
]
|
]
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
std.print_standard(proc.stdout.strip())
|
ui.print_standard(proc.stdout.strip())
|
||||||
cmd = [
|
cmd = [
|
||||||
'lsblk', '--noheadings', '--paths',
|
'lsblk', '--noheadings', '--paths',
|
||||||
'--output', 'NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT',
|
'--output', 'NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT',
|
||||||
|
|
@ -514,17 +583,17 @@ def show_selections(args, sources, ufd_dev, ufd_sources, extra_images):
|
||||||
]
|
]
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
for line in proc.stdout.splitlines()[1:]:
|
for line in proc.stdout.splitlines()[1:]:
|
||||||
std.print_standard(line)
|
ui.print_standard(line)
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
if args['--update']:
|
if args['--update']:
|
||||||
std.print_warning('Updating kit in-place')
|
ui.print_warning('Updating kit in-place')
|
||||||
elif args['--use-mbr']:
|
elif args['--use-mbr']:
|
||||||
std.print_warning('Formatting using legacy MBR')
|
ui.print_warning('Formatting using legacy MBR')
|
||||||
std.print_standard(' ')
|
ui.print_standard(' ')
|
||||||
|
|
||||||
|
|
||||||
def update_boot_entries(ufd_dev, images=None):
|
def update_boot_entries(ufd_dev, images=None) -> None:
|
||||||
"""Update boot files for UFD usage"""
|
"""Update boot files for UFD usage"""
|
||||||
configs = []
|
configs = []
|
||||||
uuids = [get_uuid('/mnt/UFD')]
|
uuids = [get_uuid('/mnt/UFD')]
|
||||||
|
|
@ -546,7 +615,7 @@ def update_boot_entries(ufd_dev, images=None):
|
||||||
'sed',
|
'sed',
|
||||||
'--in-place',
|
'--in-place',
|
||||||
'--regexp-extended',
|
'--regexp-extended',
|
||||||
f's#archisolabel={ISO_LABEL}#archisodevice=/dev/disk/by-uuid/{uuids[0]}#',
|
f's/___+/{uuids[0]}/',
|
||||||
*configs,
|
*configs,
|
||||||
]
|
]
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
@ -611,9 +680,9 @@ def update_boot_entries(ufd_dev, images=None):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def verify_sources(args, ufd_sources):
|
def verify_sources(args, ufd_sources) -> dict[str, pathlib.Path]:
|
||||||
"""Check all sources and abort if necessary, returns dict."""
|
"""Check all sources and abort if necessary, returns dict."""
|
||||||
sources = OrderedDict()
|
sources = {}
|
||||||
|
|
||||||
for label, data in ufd_sources.items():
|
for label, data in ufd_sources.items():
|
||||||
s_path = args[data['Arg']]
|
s_path = args[data['Arg']]
|
||||||
|
|
@ -621,40 +690,41 @@ def verify_sources(args, ufd_sources):
|
||||||
try:
|
try:
|
||||||
s_path_obj = io.case_insensitive_path(s_path)
|
s_path_obj = io.case_insensitive_path(s_path)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
std.print_error(f'ERROR: {label} not found: {s_path}')
|
ui.print_error(f'ERROR: {label} not found: {s_path}')
|
||||||
std.abort()
|
ui.abort()
|
||||||
if not is_valid_path(s_path_obj, data['Type']):
|
else:
|
||||||
std.print_error(f'ERROR: Invalid {label} source: {s_path}')
|
if not is_valid_path(s_path_obj, data['Type']):
|
||||||
std.abort()
|
ui.print_error(f'ERROR: Invalid {label} source: {s_path}')
|
||||||
sources[label] = s_path_obj
|
ui.abort()
|
||||||
|
sources[label] = s_path_obj
|
||||||
|
|
||||||
return sources
|
return sources
|
||||||
|
|
||||||
|
|
||||||
def verify_ufd(dev_path):
|
def verify_ufd(dev_path) -> pathlib.Path:
|
||||||
"""Check that dev_path is a valid UFD, returns pathlib.Path obj."""
|
"""Check that dev_path is a valid UFD, returns pathlib.Path obj."""
|
||||||
ufd_dev = None
|
ufd_dev = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ufd_dev = io.case_insensitive_path(dev_path)
|
ufd_dev = io.case_insensitive_path(dev_path)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
std.print_error(f'ERROR: UFD device not found: {dev_path}')
|
ui.print_error(f'ERROR: UFD device not found: {dev_path}')
|
||||||
std.abort()
|
ui.abort()
|
||||||
|
|
||||||
if not is_valid_path(ufd_dev, 'UFD'):
|
if not is_valid_path(ufd_dev, 'UFD'):
|
||||||
std.print_error(f'ERROR: Invalid UFD device: {ufd_dev}')
|
ui.print_error(f'ERROR: Invalid UFD device: {ufd_dev}')
|
||||||
std.abort()
|
ui.abort()
|
||||||
|
|
||||||
return ufd_dev
|
return ufd_dev # type: ignore[reportGeneralTypeIssues]
|
||||||
|
|
||||||
|
|
||||||
def zero_device(dev_path):
|
def zero_device(dev_path) -> None:
|
||||||
"""Zero-out first 64MB of device."""
|
"""Zero-out first 1MB of device."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'sudo',
|
'sudo',
|
||||||
'dd',
|
'dd',
|
||||||
'bs=4M',
|
'bs=1M',
|
||||||
'count=16',
|
'count=1',
|
||||||
'if=/dev/zero',
|
'if=/dev/zero',
|
||||||
f'of={dev_path}',
|
f'of={dev_path}',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ DEFAULT_LOG_NAME = cfg.main.KIT_NAME_FULL
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def enable_debug_mode():
|
def enable_debug_mode() -> None:
|
||||||
"""Configures logging for better debugging."""
|
"""Configures logging for better debugging."""
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
for handler in root_logger.handlers:
|
for handler in root_logger.handlers:
|
||||||
|
|
@ -39,13 +39,21 @@ def enable_debug_mode():
|
||||||
|
|
||||||
|
|
||||||
def format_log_path(
|
def format_log_path(
|
||||||
log_dir=None, log_name=None, timestamp=False,
|
log_dir: pathlib.Path | str | None = None,
|
||||||
kit=False, tool=False, append=False):
|
log_name: str | None = None,
|
||||||
|
append: bool = False,
|
||||||
|
kit: bool = False,
|
||||||
|
sub_dir: str | None = None,
|
||||||
|
timestamp: bool = False,
|
||||||
|
tool: bool = False,
|
||||||
|
) -> pathlib.Path:
|
||||||
"""Format path based on args passed, returns pathlib.Path obj."""
|
"""Format path based on args passed, returns pathlib.Path obj."""
|
||||||
log_path = pathlib.Path(
|
log_path = pathlib.Path(
|
||||||
f'{log_dir if log_dir else DEFAULT_LOG_DIR}/'
|
f'{log_dir if log_dir else DEFAULT_LOG_DIR}/'
|
||||||
f'{cfg.main.KIT_NAME_FULL+"/" if kit else ""}'
|
f'{cfg.main.KIT_NAME_FULL+"/" if kit else ""}'
|
||||||
f'{"Tools/" if tool else ""}'
|
f'{"Tools/" if tool else ""}'
|
||||||
|
f'{sub_dir+"_" if sub_dir else ""}'
|
||||||
|
f'{time.strftime("%Y-%m-%d_%H%M%S%z") if sub_dir else ""}/'
|
||||||
f'{log_name if log_name else DEFAULT_LOG_NAME}'
|
f'{log_name if log_name else DEFAULT_LOG_NAME}'
|
||||||
f'{"_" if timestamp else ""}'
|
f'{"_" if timestamp else ""}'
|
||||||
f'{time.strftime("%Y-%m-%d_%H%M%S%z") if timestamp else ""}'
|
f'{time.strftime("%Y-%m-%d_%H%M%S%z") if timestamp else ""}'
|
||||||
|
|
@ -61,22 +69,24 @@ def format_log_path(
|
||||||
return log_path
|
return log_path
|
||||||
|
|
||||||
|
|
||||||
def get_root_logger_path():
|
def get_root_logger_path() -> pathlib.Path:
|
||||||
"""Get path to log file from root logger, returns pathlib.Path obj."""
|
"""Get the log filepath from the root logger, returns pathlib.Path obj.
|
||||||
log_path = None
|
|
||||||
|
NOTE: This will use the first handler baseFilename it finds (if any).
|
||||||
|
"""
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
|
|
||||||
# Check all handlers and use the first fileHandler found
|
# Check handlers
|
||||||
for handler in root_logger.handlers:
|
for handler in root_logger.handlers:
|
||||||
if isinstance(handler, logging.FileHandler):
|
if hasattr(handler, 'baseFilename'):
|
||||||
log_path = pathlib.Path(handler.baseFilename).resolve()
|
log_file = handler.baseFilename # type: ignore[reportGeneralTypeIssues]
|
||||||
break
|
return pathlib.Path(log_file).resolve()
|
||||||
|
|
||||||
# Done
|
# No log file found
|
||||||
return log_path
|
raise RuntimeError('Log path not found.')
|
||||||
|
|
||||||
|
|
||||||
def remove_empty_log(log_path=None):
|
def remove_empty_log(log_path: None | pathlib.Path = None) -> None:
|
||||||
"""Remove log if empty.
|
"""Remove log if empty.
|
||||||
|
|
||||||
NOTE: Under Windows an empty log is 2 bytes long.
|
NOTE: Under Windows an empty log is 2 bytes long.
|
||||||
|
|
@ -99,7 +109,7 @@ def remove_empty_log(log_path=None):
|
||||||
log_path.unlink()
|
log_path.unlink()
|
||||||
|
|
||||||
|
|
||||||
def start(config=None):
|
def start(config: dict[str, str] | None = None) -> None:
|
||||||
"""Configure and start logging using safe defaults."""
|
"""Configure and start logging using safe defaults."""
|
||||||
log_path = format_log_path(timestamp=os.name != 'nt')
|
log_path = format_log_path(timestamp=os.name != 'nt')
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
|
|
@ -122,7 +132,12 @@ def start(config=None):
|
||||||
|
|
||||||
|
|
||||||
def update_log_path(
|
def update_log_path(
|
||||||
dest_dir=None, dest_name=None, keep_history=True, timestamp=True, append=False):
|
dest_dir: None | pathlib.Path | str = None,
|
||||||
|
dest_name: None | str = None,
|
||||||
|
append: bool = False,
|
||||||
|
keep_history: bool = True,
|
||||||
|
timestamp: bool = True,
|
||||||
|
) -> None:
|
||||||
"""Moves current log file to new path and updates the root logger."""
|
"""Moves current log file to new path and updates the root logger."""
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
new_path = format_log_path(dest_dir, dest_name, timestamp=timestamp, append=append)
|
new_path = format_log_path(dest_dir, dest_name, timestamp=timestamp, append=append)
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,16 @@ import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from subprocess import CompletedProcess
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from wk.exe import get_json_from_command, run_program
|
from wk.exe import get_json_from_command, run_program
|
||||||
from wk.std import PLATFORM, GenericError, show_data
|
from wk.std import PLATFORM, GenericError
|
||||||
|
|
||||||
from wk.cfg.net import BACKUP_SERVERS
|
from wk.cfg.net import BACKUP_SERVERS
|
||||||
|
from wk.ui import cli as ui
|
||||||
|
|
||||||
|
|
||||||
# REGEX
|
# REGEX
|
||||||
|
|
@ -22,7 +26,7 @@ REGEX_VALID_IP = re.compile(
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def connected_to_private_network(raise_on_error=False):
|
def connected_to_private_network(raise_on_error: bool = False) -> bool:
|
||||||
"""Check if connected to a private network, returns bool.
|
"""Check if connected to a private network, returns bool.
|
||||||
|
|
||||||
This checks for a valid private IP assigned to this system.
|
This checks for a valid private IP assigned to this system.
|
||||||
|
|
@ -48,12 +52,10 @@ def connected_to_private_network(raise_on_error=False):
|
||||||
raise GenericError('Not connected to a network')
|
raise GenericError('Not connected to a network')
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
if raise_on_error:
|
|
||||||
connected = None
|
|
||||||
return connected
|
return connected
|
||||||
|
|
||||||
|
|
||||||
def mount_backup_shares(read_write=False):
|
def mount_backup_shares(read_write: bool = False) -> list[str]:
|
||||||
"""Mount backup shares using OS specific methods."""
|
"""Mount backup shares using OS specific methods."""
|
||||||
report = []
|
report = []
|
||||||
for name, details in BACKUP_SERVERS.items():
|
for name, details in BACKUP_SERVERS.items():
|
||||||
|
|
@ -96,7 +98,10 @@ def mount_backup_shares(read_write=False):
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
def mount_network_share(details, mount_point=None, read_write=False):
|
def mount_network_share(
|
||||||
|
details: dict[str, Any],
|
||||||
|
mount_point: None | pathlib.Path | str = None,
|
||||||
|
read_write: bool = False) -> CompletedProcess:
|
||||||
"""Mount network share using OS specific methods."""
|
"""Mount network share using OS specific methods."""
|
||||||
cmd = None
|
cmd = None
|
||||||
address = details['Address']
|
address = details['Address']
|
||||||
|
|
@ -127,8 +132,8 @@ def mount_network_share(details, mount_point=None, read_write=False):
|
||||||
'-t', 'cifs',
|
'-t', 'cifs',
|
||||||
'-o', (
|
'-o', (
|
||||||
f'{"rw" if read_write else "ro"}'
|
f'{"rw" if read_write else "ro"}'
|
||||||
f',uid={os.getuid()}' # pylint: disable=no-member
|
f',uid={os.getuid()}'
|
||||||
f',gid={os.getgid()}' # pylint: disable=no-member
|
f',gid={os.getgid()}'
|
||||||
f',username={username}'
|
f',username={username}'
|
||||||
f',{"password=" if password else "guest"}{password}'
|
f',{"password=" if password else "guest"}{password}'
|
||||||
),
|
),
|
||||||
|
|
@ -147,7 +152,7 @@ def mount_network_share(details, mount_point=None, read_write=False):
|
||||||
return run_program(cmd, check=False)
|
return run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def ping(addr='google.com'):
|
def ping(addr: str = 'google.com') -> None:
|
||||||
"""Attempt to ping addr."""
|
"""Attempt to ping addr."""
|
||||||
cmd = (
|
cmd = (
|
||||||
'ping',
|
'ping',
|
||||||
|
|
@ -158,7 +163,7 @@ def ping(addr='google.com'):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def share_is_mounted(details):
|
def share_is_mounted(details: dict[str, Any]) -> bool:
|
||||||
"""Check if dev/share/etc is mounted, returns bool."""
|
"""Check if dev/share/etc is mounted, returns bool."""
|
||||||
mounted = False
|
mounted = False
|
||||||
|
|
||||||
|
|
@ -192,18 +197,20 @@ def share_is_mounted(details):
|
||||||
return mounted
|
return mounted
|
||||||
|
|
||||||
|
|
||||||
def show_valid_addresses():
|
def show_valid_addresses() -> None:
|
||||||
"""Show all valid private IP addresses assigned to the system."""
|
"""Show all valid private IP addresses assigned to the system."""
|
||||||
|
# TODO: Refactor to remove ui dependancy
|
||||||
devs = psutil.net_if_addrs()
|
devs = psutil.net_if_addrs()
|
||||||
for dev, families in sorted(devs.items()):
|
for dev, families in sorted(devs.items()):
|
||||||
for family in families:
|
for family in families:
|
||||||
if REGEX_VALID_IP.search(family.address):
|
if REGEX_VALID_IP.search(family.address):
|
||||||
# Valid IP found
|
# Valid IP found
|
||||||
show_data(message=dev, data=family.address)
|
ui.show_data(message=dev, data=family.address)
|
||||||
|
|
||||||
|
|
||||||
def speedtest():
|
def speedtest() -> list[str]:
|
||||||
"""Run a network speedtest using speedtest-cli."""
|
"""Run a network speedtest using speedtest-cli."""
|
||||||
|
# TODO: Refactor to use speedtest-cli's JSON output
|
||||||
cmd = ['speedtest-cli', '--simple']
|
cmd = ['speedtest-cli', '--simple']
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
output = [line.strip() for line in proc.stdout.splitlines() if line.strip()]
|
output = [line.strip() for line in proc.stdout.splitlines() if line.strip()]
|
||||||
|
|
@ -212,7 +219,7 @@ def speedtest():
|
||||||
return [f'{a:<10}{b:6.2f} {c}' for a, b, c in output]
|
return [f'{a:<10}{b:6.2f} {c}' for a, b, c in output]
|
||||||
|
|
||||||
|
|
||||||
def unmount_backup_shares():
|
def unmount_backup_shares() -> list[str]:
|
||||||
"""Unmount backup shares."""
|
"""Unmount backup shares."""
|
||||||
report = []
|
report = []
|
||||||
for name, details in BACKUP_SERVERS.items():
|
for name, details in BACKUP_SERVERS.items():
|
||||||
|
|
@ -241,7 +248,10 @@ def unmount_backup_shares():
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
def unmount_network_share(details=None, mount_point=None):
|
def unmount_network_share(
|
||||||
|
details: dict[str, Any] | None = None,
|
||||||
|
mount_point: None | pathlib.Path | str = None,
|
||||||
|
) -> CompletedProcess:
|
||||||
"""Unmount network share"""
|
"""Unmount network share"""
|
||||||
cmd = []
|
cmd = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import subprocess
|
||||||
from wk.cfg.hw import VOLUME_FAILURE_THRESHOLD, VOLUME_WARNING_THRESHOLD
|
from wk.cfg.hw import VOLUME_FAILURE_THRESHOLD, VOLUME_WARNING_THRESHOLD
|
||||||
from wk.exe import get_json_from_command, popen_program, run_program
|
from wk.exe import get_json_from_command, popen_program, run_program
|
||||||
from wk.log import format_log_path
|
from wk.log import format_log_path
|
||||||
from wk.std import bytes_to_string, color_string
|
from wk.std import bytes_to_string
|
||||||
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -19,12 +20,12 @@ UUID_CORESTORAGE = '53746f72-6167-11aa-aa11-00306543ecac'
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def build_volume_report(device_path=None) -> list:
|
def build_volume_report(device_path=None) -> list[str]:
|
||||||
"""Build volume report using lsblk, returns list.
|
"""Build volume report using lsblk, returns list.
|
||||||
|
|
||||||
If device_path is provided the report is limited to that device.
|
If device_path is provided the report is limited to that device.
|
||||||
"""
|
"""
|
||||||
def _get_volumes(dev, indent=0) -> list:
|
def _get_volumes(dev, indent=0) -> list[dict]:
|
||||||
"""Convert lsblk JSON tree to a flat list of items, returns list."""
|
"""Convert lsblk JSON tree to a flat list of items, returns list."""
|
||||||
dev['name'] = f'{" "*indent}{dev["name"]}'
|
dev['name'] = f'{" "*indent}{dev["name"]}'
|
||||||
volumes = [dev]
|
volumes = [dev]
|
||||||
|
|
@ -82,20 +83,20 @@ def build_volume_report(device_path=None) -> list:
|
||||||
vol['mountpoint'] = f'Mounted on {vol["mountpoint"]}'
|
vol['mountpoint'] = f'Mounted on {vol["mountpoint"]}'
|
||||||
|
|
||||||
# Name and size
|
# Name and size
|
||||||
line = color_string(
|
line = ansi.color_string(
|
||||||
[f'{vol["name"]:<20}', f'{vol["size"]:>9}'],
|
[f'{vol["name"]:<20}', f'{vol["size"]:>9}'],
|
||||||
[None, 'CYAN'],
|
[None, 'CYAN'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mountpoint and type
|
# Mountpoint and type
|
||||||
line = color_string(
|
line = ansi.color_string(
|
||||||
[line, f'{vol["mountpoint"]:<{m_width}}', f'{vol["fstype"]:<11}'],
|
[line, f'{vol["mountpoint"]:<{m_width}}', f'{vol["fstype"]:<11}'],
|
||||||
[None, None, 'BLUE'],
|
[None, None, 'BLUE'],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Used and free
|
# Used and free
|
||||||
if any([vol['fsused'], vol['fsavail']]):
|
if any([vol['fsused'], vol['fsavail']]):
|
||||||
line = color_string(
|
line = ansi.color_string(
|
||||||
[line, f'({vol["fsused"]:>9} used, {vol["fsavail"]:>9} free)'],
|
[line, f'({vol["fsused"]:>9} used, {vol["fsavail"]:>9} free)'],
|
||||||
[None, size_color],
|
[None, size_color],
|
||||||
)
|
)
|
||||||
|
|
@ -107,7 +108,7 @@ def build_volume_report(device_path=None) -> list:
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
def get_user_home(user):
|
def get_user_home(user) -> pathlib.Path:
|
||||||
"""Get path to user's home dir, returns pathlib.Path obj."""
|
"""Get path to user's home dir, returns pathlib.Path obj."""
|
||||||
home = None
|
home = None
|
||||||
|
|
||||||
|
|
@ -128,7 +129,7 @@ def get_user_home(user):
|
||||||
return pathlib.Path(home)
|
return pathlib.Path(home)
|
||||||
|
|
||||||
|
|
||||||
def get_user_name():
|
def get_user_name() -> str:
|
||||||
"""Get real user name, returns str."""
|
"""Get real user name, returns str."""
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
|
|
@ -145,7 +146,7 @@ def get_user_name():
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def make_temp_file(suffix=None):
|
def make_temp_file(suffix=None) -> pathlib.Path:
|
||||||
"""Make temporary file, returns pathlib.Path() obj."""
|
"""Make temporary file, returns pathlib.Path() obj."""
|
||||||
cmd = ['mktemp']
|
cmd = ['mktemp']
|
||||||
if suffix:
|
if suffix:
|
||||||
|
|
@ -154,7 +155,7 @@ def make_temp_file(suffix=None):
|
||||||
return pathlib.Path(proc.stdout.strip())
|
return pathlib.Path(proc.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
def mount(source, mount_point=None, read_write=False):
|
def mount(source, mount_point=None, read_write=False) -> None:
|
||||||
"""Mount source (on mount_point if provided).
|
"""Mount source (on mount_point if provided).
|
||||||
|
|
||||||
NOTE: If not running_as_root() then udevil will be used.
|
NOTE: If not running_as_root() then udevil will be used.
|
||||||
|
|
@ -177,13 +178,13 @@ def mount(source, mount_point=None, read_write=False):
|
||||||
raise RuntimeError(f'Failed to mount: {source} on {mount_point}')
|
raise RuntimeError(f'Failed to mount: {source} on {mount_point}')
|
||||||
|
|
||||||
|
|
||||||
def mount_volumes(device_path=None, read_write=False, scan_corestorage=False):
|
def mount_volumes(device_path=None, read_write=False, scan_corestorage=False) -> None:
|
||||||
"""Mount all detected volumes.
|
"""Mount all detected volumes.
|
||||||
|
|
||||||
NOTE: If device_path is specified then only volumes
|
NOTE: If device_path is specified then only volumes
|
||||||
under that path will be mounted.
|
under that path will be mounted.
|
||||||
"""
|
"""
|
||||||
def _get_volumes(dev) -> list:
|
def _get_volumes(dev) -> list[dict]:
|
||||||
"""Convert lsblk JSON tree to a flat list of items, returns list."""
|
"""Convert lsblk JSON tree to a flat list of items, returns list."""
|
||||||
volumes = [dev]
|
volumes = [dev]
|
||||||
for child in dev.get('children', []):
|
for child in dev.get('children', []):
|
||||||
|
|
@ -232,12 +233,12 @@ def mount_volumes(device_path=None, read_write=False, scan_corestorage=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def running_as_root():
|
def running_as_root() -> bool:
|
||||||
"""Check if running with effective UID of 0, returns bool."""
|
"""Check if running with effective UID of 0, returns bool."""
|
||||||
return os.geteuid() == 0
|
return os.geteuid() == 0
|
||||||
|
|
||||||
|
|
||||||
def scan_corestorage_container(container, timeout=300):
|
def scan_corestorage_container(container, timeout=300) -> list[dict]:
|
||||||
"""Scan CoreStorage container for inner volumes, returns list."""
|
"""Scan CoreStorage container for inner volumes, returns list."""
|
||||||
container_path = pathlib.Path(container)
|
container_path = pathlib.Path(container)
|
||||||
detected_volumes = {}
|
detected_volumes = {}
|
||||||
|
|
@ -284,7 +285,7 @@ def scan_corestorage_container(container, timeout=300):
|
||||||
return inner_volumes
|
return inner_volumes
|
||||||
|
|
||||||
|
|
||||||
def unmount(source_or_mountpoint):
|
def unmount(source_or_mountpoint) -> None:
|
||||||
"""Unmount source_or_mountpoint.
|
"""Unmount source_or_mountpoint.
|
||||||
|
|
||||||
NOTE: If not running_as_root() then udevil will be used.
|
NOTE: If not running_as_root() then udevil will be used.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ REGEX_FANS = re.compile(r'^.*\(bytes (?P<bytes>.*)\)$')
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def decode_smc_bytes(text):
|
def decode_smc_bytes(text) -> int:
|
||||||
"""Decode SMC bytes, returns int."""
|
"""Decode SMC bytes, returns int."""
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ def decode_smc_bytes(text):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def set_fans(mode):
|
def set_fans(mode) -> None:
|
||||||
"""Set fans to auto or max."""
|
"""Set fans to auto or max."""
|
||||||
if mode == 'auto':
|
if mode == 'auto':
|
||||||
set_fans_auto()
|
set_fans_auto()
|
||||||
|
|
@ -42,14 +42,14 @@ def set_fans(mode):
|
||||||
raise RuntimeError(f'Invalid fan mode: {mode}')
|
raise RuntimeError(f'Invalid fan mode: {mode}')
|
||||||
|
|
||||||
|
|
||||||
def set_fans_auto():
|
def set_fans_auto() -> None:
|
||||||
"""Set fans to auto."""
|
"""Set fans to auto."""
|
||||||
LOG.info('Setting fans to auto')
|
LOG.info('Setting fans to auto')
|
||||||
cmd = ['sudo', 'smc', '-k', 'FS! ', '-w', '0000']
|
cmd = ['sudo', 'smc', '-k', 'FS! ', '-w', '0000']
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def set_fans_max():
|
def set_fans_max() -> None:
|
||||||
"""Set fans to their max speeds."""
|
"""Set fans to their max speeds."""
|
||||||
LOG.info('Setting fans to max')
|
LOG.info('Setting fans to max')
|
||||||
num_fans = 0
|
num_fans = 0
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import pathlib
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -23,16 +25,16 @@ from wk.cfg.windows_builds import (
|
||||||
OUTDATED_BUILD_NUMBERS,
|
OUTDATED_BUILD_NUMBERS,
|
||||||
WINDOWS_BUILDS,
|
WINDOWS_BUILDS,
|
||||||
)
|
)
|
||||||
from wk.exe import get_json_from_command, run_program
|
from wk.exe import get_json_from_command, run_program, wait_for_procs
|
||||||
from wk.kit.tools import find_kit_dir
|
from wk.kit.tools import find_kit_dir
|
||||||
from wk.std import (
|
from wk.std import (
|
||||||
GenericError,
|
GenericError,
|
||||||
GenericWarning,
|
GenericWarning,
|
||||||
bytes_to_string,
|
bytes_to_string,
|
||||||
color_string,
|
|
||||||
input_text,
|
|
||||||
sleep,
|
sleep,
|
||||||
)
|
)
|
||||||
|
from wk.ui import cli as ui
|
||||||
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -88,7 +90,7 @@ else:
|
||||||
|
|
||||||
|
|
||||||
# Activation Functions
|
# Activation Functions
|
||||||
def activate_with_bios():
|
def activate_with_bios() -> None:
|
||||||
"""Attempt to activate Windows with a key stored in the BIOS."""
|
"""Attempt to activate Windows with a key stored in the BIOS."""
|
||||||
# Code borrowed from https://github.com/aeruder/get_win8key
|
# Code borrowed from https://github.com/aeruder/get_win8key
|
||||||
#####################################################
|
#####################################################
|
||||||
|
|
@ -128,7 +130,7 @@ def activate_with_bios():
|
||||||
raise GenericError('Activation Failed')
|
raise GenericError('Activation Failed')
|
||||||
|
|
||||||
|
|
||||||
def get_activation_string():
|
def get_activation_string() -> str:
|
||||||
"""Get activation status, returns str."""
|
"""Get activation status, returns str."""
|
||||||
cmd = ['cscript', '//nologo', SLMGR, '/xpr']
|
cmd = ['cscript', '//nologo', SLMGR, '/xpr']
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
|
|
@ -138,7 +140,7 @@ def get_activation_string():
|
||||||
return act_str
|
return act_str
|
||||||
|
|
||||||
|
|
||||||
def is_activated():
|
def is_activated() -> bool:
|
||||||
"""Check if Windows is activated via slmgr.vbs and return bool."""
|
"""Check if Windows is activated via slmgr.vbs and return bool."""
|
||||||
act_str = get_activation_string()
|
act_str = get_activation_string()
|
||||||
|
|
||||||
|
|
@ -147,46 +149,63 @@ def is_activated():
|
||||||
|
|
||||||
|
|
||||||
# Date / Time functions
|
# Date / Time functions
|
||||||
def get_timezone():
|
def get_timezone() -> str:
|
||||||
"""Get current timezone using tzutil, returns str."""
|
"""Get current timezone using tzutil, returns str."""
|
||||||
cmd = ['tzutil', '/g']
|
cmd = ['tzutil', '/g']
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
return proc.stdout
|
return proc.stdout
|
||||||
|
|
||||||
|
|
||||||
def set_timezone(zone):
|
def set_timezone(zone) -> None:
|
||||||
"""Set current timezone using tzutil."""
|
"""Set current timezone using tzutil."""
|
||||||
cmd = ['tzutil', '/s', zone]
|
cmd = ['tzutil', '/s', zone]
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
# Info Functions
|
# Info Functions
|
||||||
def check_4k_alignment(show_alert=False):
|
def check_4k_alignment(show_alert=False) -> list[str]:
|
||||||
"""Check if all partitions are 4K aligned, returns book."""
|
"""Check if all partitions are 4K aligned, returns list."""
|
||||||
cmd = ['WMIC', 'partition', 'get', 'StartingOffset']
|
script_path = find_kit_dir('Scripts').joinpath('check_partition_alignment.ps1')
|
||||||
|
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
|
||||||
|
json_data = get_json_from_command(cmd)
|
||||||
|
report = []
|
||||||
|
show_alert = False
|
||||||
|
|
||||||
# Check offsets
|
# Check offsets
|
||||||
proc = run_program(cmd)
|
for part in json_data:
|
||||||
for offset in proc.stdout.splitlines():
|
if part['StartingOffset'] % 4096 != 0:
|
||||||
offset = offset.strip()
|
report.append(
|
||||||
if not offset.isnumeric():
|
ansi.color_string(
|
||||||
continue
|
f'{part["Name"]}'
|
||||||
if int(offset) % 4096 != 0:
|
f' ({bytes_to_string(part["Size"], decimals=1)})'
|
||||||
# Not aligned
|
,
|
||||||
if show_alert:
|
'RED'
|
||||||
show_alert_box('One or more partitions are not 4K aligned')
|
)
|
||||||
raise GenericError('One or more partitions are not 4K aligned')
|
)
|
||||||
|
|
||||||
|
# Show alert
|
||||||
|
if show_alert:
|
||||||
|
show_alert_box('One or more partitions not 4K aligned')
|
||||||
|
|
||||||
|
# Done
|
||||||
|
if report:
|
||||||
|
report.insert(
|
||||||
|
0,
|
||||||
|
ansi.color_string('One or more partitions not 4K aligned', 'YELLOW'),
|
||||||
|
)
|
||||||
|
report.sort()
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
def export_bitlocker_info():
|
def export_bitlocker_info() -> None:
|
||||||
"""Get Bitlocker info and save to the current directory."""
|
"""Get Bitlocker info and save to the base directory of the kit."""
|
||||||
commands = [
|
commands = [
|
||||||
['manage-bde', '-status', SYSTEMDRIVE],
|
['manage-bde', '-status', SYSTEMDRIVE],
|
||||||
['manage-bde', '-protectors', '-get', SYSTEMDRIVE],
|
['manage-bde', '-protectors', '-get', SYSTEMDRIVE],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Get filename
|
# Get filename
|
||||||
file_name = input_text(prompt='Enter filename', allow_empty_response=False)
|
file_name = ui.input_text(prompt_msg='Enter filename')
|
||||||
file_path = pathlib.Path(f'../../Bitlocker_{file_name}.txt').resolve()
|
file_path = pathlib.Path(f'../../Bitlocker_{file_name}.txt').resolve()
|
||||||
|
|
||||||
# Save info
|
# Save info
|
||||||
|
|
@ -196,49 +215,56 @@ def export_bitlocker_info():
|
||||||
_f.write(f'{proc.stdout}\n\n')
|
_f.write(f'{proc.stdout}\n\n')
|
||||||
|
|
||||||
|
|
||||||
def get_installed_antivirus():
|
def get_installed_antivirus() -> dict[str, dict]:
|
||||||
"""Get list of installed antivirus programs, returns list."""
|
"""Get installed antivirus products and their status, returns dict."""
|
||||||
cmd = [
|
script_path = find_kit_dir('Scripts').joinpath('check_av.ps1')
|
||||||
'WMIC', r'/namespace:\\root\SecurityCenter2',
|
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
|
||||||
'path', 'AntivirusProduct',
|
json_data = get_json_from_command(cmd)
|
||||||
'get', 'displayName', '/value',
|
products = {}
|
||||||
]
|
|
||||||
products = []
|
|
||||||
report = []
|
|
||||||
|
|
||||||
# Get list of products
|
# Check state and build dict
|
||||||
proc = run_program(cmd)
|
for p in json_data:
|
||||||
for line in proc.stdout.splitlines():
|
name = p['displayName']
|
||||||
line = line.strip()
|
state = p['productState']
|
||||||
if '=' in line:
|
enabled = ((state>>8) & 0x11) in (0x10, 0x11) # middle two hex digits
|
||||||
products.append(line.split('=')[1])
|
outdated = (state & 0x11) != 0x00 # last two hex digits
|
||||||
|
products[name] = {
|
||||||
|
'Enabled': enabled,
|
||||||
|
'Outdated': outdated,
|
||||||
|
'State': state,
|
||||||
|
}
|
||||||
|
return products
|
||||||
|
|
||||||
|
|
||||||
|
def list_installed_antivirus() -> list[str]:
|
||||||
|
"""Get list of installed antivirus programs, returns list."""
|
||||||
|
products = get_installed_antivirus()
|
||||||
|
products_active = []
|
||||||
|
products_inactive = []
|
||||||
|
|
||||||
# Check product(s) status
|
# Check product(s) status
|
||||||
for product in sorted(products):
|
for name, details in products.items():
|
||||||
cmd = [
|
if details['Enabled']:
|
||||||
'WMIC', r'/namespace:\\root\SecurityCenter2',
|
if details['Outdated']:
|
||||||
'path', 'AntivirusProduct',
|
products_active.append(ansi.color_string(f'{name} [OUTDATED]', 'YELLOW'))
|
||||||
'where', f'displayName="{product}"',
|
else:
|
||||||
'get', 'productState', '/value',
|
products_active.append(name)
|
||||||
]
|
|
||||||
proc = run_program(cmd)
|
|
||||||
state = proc.stdout.split('=')[1]
|
|
||||||
state = hex(int(state))
|
|
||||||
if str(state)[3:5] not in ['10', '11']:
|
|
||||||
report.append(color_string(f'[Disabled] {product}', 'YELLOW'))
|
|
||||||
else:
|
else:
|
||||||
report.append(product)
|
# Disabled
|
||||||
|
products_inactive.append(ansi.color_string(f'[Disabled] {name}', 'YELLOW'))
|
||||||
|
|
||||||
# Final check
|
# Final check
|
||||||
if not report:
|
if not (products_active or products_inactive):
|
||||||
report.append(color_string('No products detected', 'RED'))
|
products_inactive.append(ansi.color_string('No products detected', 'RED'))
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return report
|
products_active.sort()
|
||||||
|
products_inactive.sort()
|
||||||
|
return products_active + products_inactive
|
||||||
|
|
||||||
|
|
||||||
def get_installed_ram(as_list=False, raise_exceptions=False):
|
def get_installed_ram(as_list=False, raise_exceptions=False) -> list | str:
|
||||||
"""Get installed RAM."""
|
"""Get installed RAM, returns list or str."""
|
||||||
mem = psutil.virtual_memory()
|
mem = psutil.virtual_memory()
|
||||||
mem_str = bytes_to_string(mem.total, decimals=1)
|
mem_str = bytes_to_string(mem.total, decimals=1)
|
||||||
|
|
||||||
|
|
@ -253,8 +279,8 @@ def get_installed_ram(as_list=False, raise_exceptions=False):
|
||||||
return [mem_str] if as_list else mem_str
|
return [mem_str] if as_list else mem_str
|
||||||
|
|
||||||
|
|
||||||
def get_os_activation(as_list=False, check=True):
|
def get_os_activation(as_list=False, check=True) -> list | str:
|
||||||
"""Get OS activation status, returns str.
|
"""Get OS activation status, returns list or str.
|
||||||
|
|
||||||
NOTE: If check=True then raise an exception if OS isn't activated.
|
NOTE: If check=True then raise an exception if OS isn't activated.
|
||||||
"""
|
"""
|
||||||
|
|
@ -270,7 +296,7 @@ def get_os_activation(as_list=False, check=True):
|
||||||
return [act_str] if as_list else act_str
|
return [act_str] if as_list else act_str
|
||||||
|
|
||||||
|
|
||||||
def get_os_name(as_list=False, check=True):
|
def get_os_name(as_list=False, check=True) -> str:
|
||||||
"""Build OS display name, returns str.
|
"""Build OS display name, returns str.
|
||||||
|
|
||||||
NOTE: If check=True then an exception is raised if the OS version is
|
NOTE: If check=True then an exception is raised if the OS version is
|
||||||
|
|
@ -296,7 +322,7 @@ def get_os_name(as_list=False, check=True):
|
||||||
return [display_name] if as_list else display_name
|
return [display_name] if as_list else display_name
|
||||||
|
|
||||||
|
|
||||||
def get_raw_disks():
|
def get_raw_disks() -> list[str]:
|
||||||
"""Get all disks without a partiton table, returns list."""
|
"""Get all disks without a partiton table, returns list."""
|
||||||
script_path = find_kit_dir('Scripts').joinpath('get_raw_disks.ps1')
|
script_path = find_kit_dir('Scripts').joinpath('get_raw_disks.ps1')
|
||||||
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
|
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
|
||||||
|
|
@ -321,7 +347,7 @@ def get_raw_disks():
|
||||||
return raw_disks
|
return raw_disks
|
||||||
|
|
||||||
|
|
||||||
def get_volume_usage(use_colors=False):
|
def get_volume_usage(use_colors=False) -> list[str]:
|
||||||
"""Get space usage info for all fixed volumes, returns list."""
|
"""Get space usage info for all fixed volumes, returns list."""
|
||||||
report = []
|
report = []
|
||||||
for disk in psutil.disk_partitions():
|
for disk in psutil.disk_partitions():
|
||||||
|
|
@ -338,14 +364,14 @@ def get_volume_usage(use_colors=False):
|
||||||
f' ({bytes_to_string(free, 2):>10} / {bytes_to_string(total, 2):>10})'
|
f' ({bytes_to_string(free, 2):>10} / {bytes_to_string(total, 2):>10})'
|
||||||
)
|
)
|
||||||
if use_colors:
|
if use_colors:
|
||||||
display_str = color_string(display_str, color)
|
display_str = ansi.color_string(display_str, color)
|
||||||
report.append(f'{disk.device} {display_str}')
|
report.append(f'{disk.device} {display_str}')
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
def show_alert_box(message, title=None):
|
def show_alert_box(message, title=None) -> None:
|
||||||
"""Show Windows alert box with message."""
|
"""Show Windows alert box with message."""
|
||||||
title = title if title else f'{KIT_NAME_FULL} Warning'
|
title = title if title else f'{KIT_NAME_FULL} Warning'
|
||||||
message_box = ctypes.windll.user32.MessageBoxW
|
message_box = ctypes.windll.user32.MessageBoxW
|
||||||
|
|
@ -353,7 +379,7 @@ def show_alert_box(message, title=None):
|
||||||
|
|
||||||
|
|
||||||
# Registry Functions
|
# Registry Functions
|
||||||
def reg_delete_key(hive, key, recurse=False):
|
def reg_delete_key(hive, key, recurse=False) -> None:
|
||||||
"""Delete a key from the registry.
|
"""Delete a key from the registry.
|
||||||
|
|
||||||
NOTE: If recurse is False then it will only work on empty keys.
|
NOTE: If recurse is False then it will only work on empty keys.
|
||||||
|
|
@ -375,7 +401,7 @@ def reg_delete_key(hive, key, recurse=False):
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# Ignore
|
# Ignore
|
||||||
pass
|
pass
|
||||||
except PermissionError:
|
except PermissionError as _e:
|
||||||
LOG.error(r'Failed to delete registry key: %s\%s', hive_name, key)
|
LOG.error(r'Failed to delete registry key: %s\%s', hive_name, key)
|
||||||
if recurse:
|
if recurse:
|
||||||
# Re-raise exception
|
# Re-raise exception
|
||||||
|
|
@ -383,10 +409,10 @@ def reg_delete_key(hive, key, recurse=False):
|
||||||
|
|
||||||
# recurse is not True so assuming we tried to remove a non-empty key
|
# recurse is not True so assuming we tried to remove a non-empty key
|
||||||
msg = fr'Refusing to remove non-empty key: {hive_name}\{key}'
|
msg = fr'Refusing to remove non-empty key: {hive_name}\{key}'
|
||||||
raise FileExistsError(msg)
|
raise FileExistsError(msg) from _e
|
||||||
|
|
||||||
|
|
||||||
def reg_delete_value(hive, key, value):
|
def reg_delete_value(hive, key, value) -> None:
|
||||||
"""Delete a value from the registry."""
|
"""Delete a value from the registry."""
|
||||||
access = winreg.KEY_ALL_ACCESS
|
access = winreg.KEY_ALL_ACCESS
|
||||||
hive = reg_get_hive(hive)
|
hive = reg_get_hive(hive)
|
||||||
|
|
@ -410,8 +436,9 @@ def reg_delete_value(hive, key, value):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def reg_get_hive(hive):
|
def reg_get_hive(hive) -> Any:
|
||||||
"""Get winreg HKEY constant from string, returns HKEY constant."""
|
"""Get winreg HKEY constant from string, returns HKEY constant."""
|
||||||
|
# TODO: Fix type hint
|
||||||
if isinstance(hive, int):
|
if isinstance(hive, int):
|
||||||
# Assuming we're already a winreg HKEY constant
|
# Assuming we're already a winreg HKEY constant
|
||||||
pass
|
pass
|
||||||
|
|
@ -422,8 +449,9 @@ def reg_get_hive(hive):
|
||||||
return hive
|
return hive
|
||||||
|
|
||||||
|
|
||||||
def reg_get_data_type(data_type):
|
def reg_get_data_type(data_type) -> Any:
|
||||||
"""Get registry data type from string, returns winreg constant."""
|
"""Get registry data type from string, returns winreg constant."""
|
||||||
|
# TODO: Fix type hint
|
||||||
if isinstance(data_type, int):
|
if isinstance(data_type, int):
|
||||||
# Assuming we're already a winreg value type constant
|
# Assuming we're already a winreg value type constant
|
||||||
pass
|
pass
|
||||||
|
|
@ -434,7 +462,7 @@ def reg_get_data_type(data_type):
|
||||||
return data_type
|
return data_type
|
||||||
|
|
||||||
|
|
||||||
def reg_key_exists(hive, key):
|
def reg_key_exists(hive, key) -> bool:
|
||||||
"""Test if the specified hive/key exists, returns bool."""
|
"""Test if the specified hive/key exists, returns bool."""
|
||||||
exists = False
|
exists = False
|
||||||
hive = reg_get_hive(hive)
|
hive = reg_get_hive(hive)
|
||||||
|
|
@ -452,7 +480,7 @@ def reg_key_exists(hive, key):
|
||||||
return exists
|
return exists
|
||||||
|
|
||||||
|
|
||||||
def reg_read_value(hive, key, value, force_32=False, force_64=False):
|
def reg_read_value(hive, key, value, force_32=False, force_64=False) -> Any:
|
||||||
"""Query value from hive/hey, returns multiple types.
|
"""Query value from hive/hey, returns multiple types.
|
||||||
|
|
||||||
NOTE: Set value='' to read the default value.
|
NOTE: Set value='' to read the default value.
|
||||||
|
|
@ -476,7 +504,7 @@ def reg_read_value(hive, key, value, force_32=False, force_64=False):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def reg_write_settings(settings):
|
def reg_write_settings(settings) -> None:
|
||||||
"""Set registry values in bulk from a custom data structure.
|
"""Set registry values in bulk from a custom data structure.
|
||||||
|
|
||||||
Data structure should be as follows:
|
Data structure should be as follows:
|
||||||
|
|
@ -516,7 +544,7 @@ def reg_write_settings(settings):
|
||||||
reg_set_value(hive, key, *value)
|
reg_set_value(hive, key, *value)
|
||||||
|
|
||||||
|
|
||||||
def reg_set_value(hive, key, name, data, data_type, option=None):
|
def reg_set_value(hive, key, name, data, data_type, option=None) -> None:
|
||||||
"""Set value for hive/key."""
|
"""Set value for hive/key."""
|
||||||
access = winreg.KEY_WRITE
|
access = winreg.KEY_WRITE
|
||||||
data_type = reg_get_data_type(data_type)
|
data_type = reg_get_data_type(data_type)
|
||||||
|
|
@ -548,25 +576,25 @@ def reg_set_value(hive, key, name, data, data_type, option=None):
|
||||||
|
|
||||||
|
|
||||||
# Safe Mode Functions
|
# Safe Mode Functions
|
||||||
def disable_safemode():
|
def disable_safemode() -> None:
|
||||||
"""Edit BCD to remove safeboot value."""
|
"""Edit BCD to remove safeboot value."""
|
||||||
cmd = ['bcdedit', '/deletevalue', '{default}', 'safeboot']
|
cmd = ['bcdedit', '/deletevalue', '{default}', 'safeboot']
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def disable_safemode_msi():
|
def disable_safemode_msi() -> None:
|
||||||
"""Disable MSI access under safemode."""
|
"""Disable MSI access under safemode."""
|
||||||
cmd = ['reg', 'delete', REG_MSISERVER, '/f']
|
cmd = ['reg', 'delete', REG_MSISERVER, '/f']
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def enable_safemode():
|
def enable_safemode() -> None:
|
||||||
"""Edit BCD to set safeboot as default."""
|
"""Edit BCD to set safeboot as default."""
|
||||||
cmd = ['bcdedit', '/set', '{default}', 'safeboot', 'network']
|
cmd = ['bcdedit', '/set', '{default}', 'safeboot', 'network']
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def enable_safemode_msi():
|
def enable_safemode_msi() -> None:
|
||||||
"""Enable MSI access under safemode."""
|
"""Enable MSI access under safemode."""
|
||||||
cmd = ['reg', 'add', REG_MSISERVER, '/f']
|
cmd = ['reg', 'add', REG_MSISERVER, '/f']
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
@ -579,7 +607,7 @@ def enable_safemode_msi():
|
||||||
|
|
||||||
|
|
||||||
# Secure Boot Functions
|
# Secure Boot Functions
|
||||||
def is_booted_uefi():
|
def is_booted_uefi() -> bool:
|
||||||
"""Check if booted UEFI or legacy, returns bool."""
|
"""Check if booted UEFI or legacy, returns bool."""
|
||||||
kernel = ctypes.windll.kernel32
|
kernel = ctypes.windll.kernel32
|
||||||
firmware_type = ctypes.c_uint()
|
firmware_type = ctypes.c_uint()
|
||||||
|
|
@ -587,7 +615,7 @@ def is_booted_uefi():
|
||||||
# Get value from kernel32 API (firmware_type is updated by the call)
|
# Get value from kernel32 API (firmware_type is updated by the call)
|
||||||
try:
|
try:
|
||||||
kernel.GetFirmwareType(ctypes.byref(firmware_type))
|
kernel.GetFirmwareType(ctypes.byref(firmware_type))
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception:
|
||||||
# Ignore and set firmware_type back to zero
|
# Ignore and set firmware_type back to zero
|
||||||
firmware_type = ctypes.c_uint(0)
|
firmware_type = ctypes.c_uint(0)
|
||||||
|
|
||||||
|
|
@ -595,7 +623,7 @@ def is_booted_uefi():
|
||||||
return firmware_type.value == 2
|
return firmware_type.value == 2
|
||||||
|
|
||||||
|
|
||||||
def is_secure_boot_enabled(raise_exceptions=False, show_alert=False):
|
def is_secure_boot_enabled(raise_exceptions=False, show_alert=False) -> bool:
|
||||||
"""Check if Secure Boot is enabled, returns bool.
|
"""Check if Secure Boot is enabled, returns bool.
|
||||||
|
|
||||||
If raise_exceptions is True then an exception is raised with details.
|
If raise_exceptions is True then an exception is raised with details.
|
||||||
|
|
@ -645,7 +673,7 @@ def is_secure_boot_enabled(raise_exceptions=False, show_alert=False):
|
||||||
|
|
||||||
|
|
||||||
# Service Functions
|
# Service Functions
|
||||||
def disable_service(service_name):
|
def disable_service(service_name) -> None:
|
||||||
"""Set service startup to disabled."""
|
"""Set service startup to disabled."""
|
||||||
cmd = ['sc', 'config', service_name, 'start=', 'disabled']
|
cmd = ['sc', 'config', service_name, 'start=', 'disabled']
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
@ -655,7 +683,7 @@ def disable_service(service_name):
|
||||||
raise GenericError(f'Failed to disable service {service_name}')
|
raise GenericError(f'Failed to disable service {service_name}')
|
||||||
|
|
||||||
|
|
||||||
def enable_service(service_name, start_type='auto'):
|
def enable_service(service_name, start_type='auto') -> None:
|
||||||
"""Enable service by setting start type."""
|
"""Enable service by setting start type."""
|
||||||
cmd = ['sc', 'config', service_name, 'start=', start_type]
|
cmd = ['sc', 'config', service_name, 'start=', start_type]
|
||||||
psutil_type = 'automatic'
|
psutil_type = 'automatic'
|
||||||
|
|
@ -670,7 +698,7 @@ def enable_service(service_name, start_type='auto'):
|
||||||
raise GenericError(f'Failed to enable service {service_name}')
|
raise GenericError(f'Failed to enable service {service_name}')
|
||||||
|
|
||||||
|
|
||||||
def get_service_status(service_name):
|
def get_service_status(service_name) -> str:
|
||||||
"""Get service status using psutil, returns str."""
|
"""Get service status using psutil, returns str."""
|
||||||
status = 'unknown'
|
status = 'unknown'
|
||||||
try:
|
try:
|
||||||
|
|
@ -682,7 +710,7 @@ def get_service_status(service_name):
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
def get_service_start_type(service_name):
|
def get_service_start_type(service_name) -> str:
|
||||||
"""Get service startup type using psutil, returns str."""
|
"""Get service startup type using psutil, returns str."""
|
||||||
start_type = 'unknown'
|
start_type = 'unknown'
|
||||||
try:
|
try:
|
||||||
|
|
@ -694,7 +722,7 @@ def get_service_start_type(service_name):
|
||||||
return start_type
|
return start_type
|
||||||
|
|
||||||
|
|
||||||
def start_service(service_name):
|
def start_service(service_name) -> None:
|
||||||
"""Stop service."""
|
"""Stop service."""
|
||||||
cmd = ['net', 'start', service_name]
|
cmd = ['net', 'start', service_name]
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
@ -704,7 +732,7 @@ def start_service(service_name):
|
||||||
raise GenericError(f'Failed to start service {service_name}')
|
raise GenericError(f'Failed to start service {service_name}')
|
||||||
|
|
||||||
|
|
||||||
def stop_service(service_name):
|
def stop_service(service_name) -> None:
|
||||||
"""Stop service."""
|
"""Stop service."""
|
||||||
cmd = ['net', 'stop', service_name]
|
cmd = ['net', 'stop', service_name]
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
@ -714,5 +742,62 @@ def stop_service(service_name):
|
||||||
raise GenericError(f'Failed to stop service {service_name}')
|
raise GenericError(f'Failed to stop service {service_name}')
|
||||||
|
|
||||||
|
|
||||||
|
# Winget Functions
|
||||||
|
def winget_check(raise_exceptions: bool = False) -> None:
|
||||||
|
"""Check if winget is present, install if not."""
|
||||||
|
cmd = [
|
||||||
|
'powershell',
|
||||||
|
'-ExecutionPolicy', 'bypass',
|
||||||
|
'-File', find_kit_dir('Scripts').joinpath('install_winget.ps1'),
|
||||||
|
]
|
||||||
|
proc = run_program(cmd, check=False)
|
||||||
|
|
||||||
|
# Raise exception if requested
|
||||||
|
if raise_exceptions:
|
||||||
|
if proc.returncode == 1:
|
||||||
|
raise GenericWarning('Already installed')
|
||||||
|
if proc.returncode == 2:
|
||||||
|
raise GenericError('Failed to install')
|
||||||
|
|
||||||
|
|
||||||
|
def winget_import(group_name: str = 'default') -> None:
|
||||||
|
"""Use winget to import a set of applications.
|
||||||
|
|
||||||
|
group_name should be the name of a JSON file exported from winget.
|
||||||
|
|
||||||
|
NOTE: The path is relative to .bin/Scripts/wk/cfg/winget/
|
||||||
|
"""
|
||||||
|
cmd = [
|
||||||
|
'winget',
|
||||||
|
'import', '--import-file',
|
||||||
|
str(find_kit_dir('Scripts').joinpath(f'wk/cfg/winget/{group_name}.json')),
|
||||||
|
]
|
||||||
|
tmp_file = fr'{os.environ.get("TMP")}\run_winget.cmd'
|
||||||
|
if CONEMU:
|
||||||
|
with open(tmp_file, 'w', encoding='utf-8') as _f:
|
||||||
|
_f.write('@echo off\n')
|
||||||
|
_f.write(" ".join(cmd))
|
||||||
|
cmd = ('cmd', '/c', tmp_file, '-new_console:n', '-new_console:s33V')
|
||||||
|
run_program(cmd, check=False, pipe=False)
|
||||||
|
sleep(1)
|
||||||
|
wait_for_procs('winget.exe')
|
||||||
|
|
||||||
|
|
||||||
|
def winget_upgrade() -> None:
|
||||||
|
"""Upgrade all supported programs with winget, returns subprocess.Popen."""
|
||||||
|
cmd = ['winget', 'upgrade', '--all']
|
||||||
|
|
||||||
|
# Adjust if running inside ConEmu
|
||||||
|
tmp_file = fr'{os.environ.get("TMP")}\run_winget.cmd'
|
||||||
|
if CONEMU:
|
||||||
|
with open(tmp_file, 'w', encoding='utf-8') as _f:
|
||||||
|
_f.write('@echo off\n')
|
||||||
|
_f.write(" ".join(cmd))
|
||||||
|
cmd = ('cmd', '/c', tmp_file, '-new_console:n', '-new_console:s33V')
|
||||||
|
run_program(cmd, check=False, pipe=False)
|
||||||
|
sleep(1)
|
||||||
|
wait_for_procs('winget.exe')
|
||||||
|
|
||||||
|
|
||||||
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.")
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@
|
||||||
import atexit
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from subprocess import CalledProcessError, DEVNULL
|
from subprocess import CalledProcessError, DEVNULL
|
||||||
|
from typing import Any
|
||||||
from xml.dom.minidom import parse as xml_parse
|
from xml.dom.minidom import parse as xml_parse
|
||||||
|
|
||||||
from wk.cfg.main import KIT_NAME_FULL, KIT_NAME_SHORT, WINDOWS_TIME_ZONE
|
from wk.cfg.main import KIT_NAME_FULL, KIT_NAME_SHORT, WINDOWS_TIME_ZONE
|
||||||
|
|
@ -58,21 +60,10 @@ from wk.os.win import (
|
||||||
from wk.std import (
|
from wk.std import (
|
||||||
GenericError,
|
GenericError,
|
||||||
GenericWarning,
|
GenericWarning,
|
||||||
Menu,
|
|
||||||
TryAndPrint,
|
|
||||||
abort,
|
|
||||||
ask,
|
|
||||||
clear_screen,
|
|
||||||
color_string,
|
|
||||||
pause,
|
|
||||||
print_info,
|
|
||||||
print_standard,
|
|
||||||
print_warning,
|
|
||||||
set_title,
|
|
||||||
show_data,
|
|
||||||
sleep,
|
sleep,
|
||||||
strip_colors,
|
|
||||||
)
|
)
|
||||||
|
from wk.ui import cli as ui
|
||||||
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -87,7 +78,7 @@ GPUPDATE_SUCCESS_STRINGS = (
|
||||||
'User Policy update has completed successfully.',
|
'User Policy update has completed successfully.',
|
||||||
)
|
)
|
||||||
IN_CONEMU = 'ConEmuPID' in os.environ
|
IN_CONEMU = 'ConEmuPID' in os.environ
|
||||||
MENU_PRESETS = Menu()
|
MENU_PRESETS = ui.Menu()
|
||||||
PROGRAMDATA = os.environ.get('{ALLUSERSPROFILE}', r'C:\ProgramData')
|
PROGRAMDATA = os.environ.get('{ALLUSERSPROFILE}', r'C:\ProgramData')
|
||||||
PROGRAMFILES_32 = os.environ.get(
|
PROGRAMFILES_32 = os.environ.get(
|
||||||
'PROGRAMFILES(X86)', os.environ.get(
|
'PROGRAMFILES(X86)', os.environ.get(
|
||||||
|
|
@ -105,7 +96,7 @@ WHITELIST = '\n'.join((
|
||||||
fr'{PROGRAMFILES_32}\TeamViewer\tv_x64.exe',
|
fr'{PROGRAMFILES_32}\TeamViewer\tv_x64.exe',
|
||||||
sys.executable,
|
sys.executable,
|
||||||
))
|
))
|
||||||
TRY_PRINT = TryAndPrint()
|
TRY_PRINT = ui.TryAndPrint()
|
||||||
TRY_PRINT.width = WIDTH
|
TRY_PRINT.width = WIDTH
|
||||||
TRY_PRINT.verbose = True
|
TRY_PRINT.verbose = True
|
||||||
for error in ('CalledProcessError', 'FileNotFoundError'):
|
for error in ('CalledProcessError', 'FileNotFoundError'):
|
||||||
|
|
@ -113,10 +104,10 @@ for error in ('CalledProcessError', 'FileNotFoundError'):
|
||||||
|
|
||||||
|
|
||||||
# Auto Repairs
|
# Auto Repairs
|
||||||
def build_menus(base_menus, title, presets):
|
def build_menus(base_menus, title, presets) -> dict[str, ui.Menu]:
|
||||||
"""Build menus, returns dict."""
|
"""Build menus, returns dict."""
|
||||||
menus = {}
|
menus = {}
|
||||||
menus['Main'] = Menu(title=f'{title}\n{color_string("Main Menu", "GREEN")}')
|
menus['Main'] = ui.Menu(title=f'{title}\n{ansi.color_string("Main Menu", "GREEN")}')
|
||||||
|
|
||||||
# Main Menu
|
# Main Menu
|
||||||
for entry in base_menus['Actions']:
|
for entry in base_menus['Actions']:
|
||||||
|
|
@ -125,7 +116,7 @@ def build_menus(base_menus, title, presets):
|
||||||
menus['Main'].add_option(group, {'Selected': True})
|
menus['Main'].add_option(group, {'Selected': True})
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
menus['Options'] = Menu(title=f'{title}\n{color_string("Options", "GREEN")}')
|
menus['Options'] = ui.Menu(title=f'{title}\n{ansi.color_string("Options", "GREEN")}')
|
||||||
for entry in base_menus['Options']:
|
for entry in base_menus['Options']:
|
||||||
menus['Options'].add_option(entry.name, entry.details)
|
menus['Options'].add_option(entry.name, entry.details)
|
||||||
menus['Options'].add_action('All')
|
menus['Options'].add_action('All')
|
||||||
|
|
@ -135,7 +126,7 @@ def build_menus(base_menus, title, presets):
|
||||||
|
|
||||||
# Run groups
|
# Run groups
|
||||||
for group, entries in base_menus['Groups'].items():
|
for group, entries in base_menus['Groups'].items():
|
||||||
menus[group] = Menu(title=f'{title}\n{color_string(group, "GREEN")}')
|
menus[group] = ui.Menu(title=f'{title}\n{ansi.color_string(group, "GREEN")}')
|
||||||
menus[group].disabled_str = 'Locked'
|
menus[group].disabled_str = 'Locked'
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
menus[group].add_option(entry.name, entry.details)
|
menus[group].add_option(entry.name, entry.details)
|
||||||
|
|
@ -167,7 +158,7 @@ def build_menus(base_menus, title, presets):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update presets Menu
|
# Update presets Menu
|
||||||
MENU_PRESETS.title = f'{title}\n{color_string("Load Preset", "GREEN")}'
|
MENU_PRESETS.title = f'{title}\n{ansi.color_string("Load Preset", "GREEN")}'
|
||||||
MENU_PRESETS.add_option('Default')
|
MENU_PRESETS.add_option('Default')
|
||||||
for name in presets:
|
for name in presets:
|
||||||
MENU_PRESETS.add_option(name)
|
MENU_PRESETS.add_option(name)
|
||||||
|
|
@ -180,7 +171,7 @@ def build_menus(base_menus, title, presets):
|
||||||
return menus
|
return menus
|
||||||
|
|
||||||
|
|
||||||
def update_scheduled_task():
|
def update_scheduled_task() -> None:
|
||||||
"""Create (or update) scheduled task to start repairs."""
|
"""Create (or update) scheduled task to start repairs."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'schtasks', '/create', '/f',
|
'schtasks', '/create', '/f',
|
||||||
|
|
@ -194,7 +185,7 @@ def update_scheduled_task():
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def end_session():
|
def end_session() -> None:
|
||||||
"""End Auto Repairs session."""
|
"""End Auto Repairs session."""
|
||||||
# Remove logon task
|
# Remove logon task
|
||||||
cmd = [
|
cmd = [
|
||||||
|
|
@ -233,7 +224,7 @@ def end_session():
|
||||||
LOG.error('Failed to remove Auto Repairs session settings')
|
LOG.error('Failed to remove Auto Repairs session settings')
|
||||||
|
|
||||||
|
|
||||||
def get_entry_settings(group, name):
|
def get_entry_settings(group, name) -> dict[str, Any]:
|
||||||
"""Get menu entry settings from the registry, returns dict."""
|
"""Get menu entry settings from the registry, returns dict."""
|
||||||
key_path = fr'{AUTO_REPAIR_KEY}\{group}\{name}'
|
key_path = fr'{AUTO_REPAIR_KEY}\{group}\{name}'
|
||||||
settings = {}
|
settings = {}
|
||||||
|
|
@ -252,7 +243,7 @@ def get_entry_settings(group, name):
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
def init(menus, presets):
|
def init(menus, presets) -> None:
|
||||||
"""Initialize Auto Repairs."""
|
"""Initialize Auto Repairs."""
|
||||||
session_started = is_session_started()
|
session_started = is_session_started()
|
||||||
|
|
||||||
|
|
@ -271,14 +262,14 @@ def init(menus, presets):
|
||||||
|
|
||||||
# Resume session
|
# Resume session
|
||||||
load_settings(menus)
|
load_settings(menus)
|
||||||
print_info('Resuming session, press CTRL+c to cancel')
|
ui.print_info('Resuming session, press CTRL+c to cancel')
|
||||||
for _x in range(AUTO_REPAIR_DELAY_IN_SECONDS, 0, -1):
|
for _x in range(AUTO_REPAIR_DELAY_IN_SECONDS, 0, -1):
|
||||||
print(f' {_x} second{"" if _x==1 else "s"} remaining... \r', end='')
|
print(f' {_x} second{"" if _x==1 else "s"} remaining... \r', end='')
|
||||||
sleep(1)
|
sleep(1)
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
|
|
||||||
def init_run(options):
|
def init_run(options) -> None:
|
||||||
"""Initialize Auto Repairs Run."""
|
"""Initialize Auto Repairs Run."""
|
||||||
update_scheduled_task()
|
update_scheduled_task()
|
||||||
if options['Kill Explorer']['Selected']:
|
if options['Kill Explorer']['Selected']:
|
||||||
|
|
@ -305,8 +296,9 @@ def init_run(options):
|
||||||
TRY_PRINT.run('Running RKill...', run_rkill, msg_good='DONE')
|
TRY_PRINT.run('Running RKill...', run_rkill, msg_good='DONE')
|
||||||
|
|
||||||
|
|
||||||
def init_session(options):
|
def init_session(options) -> None:
|
||||||
"""Initialize Auto Repairs session."""
|
"""Initialize Auto Repairs session."""
|
||||||
|
_ = options # Suppress linting error and reserve for furture use
|
||||||
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'SessionStarted', 1, 'DWORD')
|
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'SessionStarted', 1, 'DWORD')
|
||||||
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'LogName', get_root_logger_path().stem, 'SZ')
|
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'LogName', get_root_logger_path().stem, 'SZ')
|
||||||
|
|
||||||
|
|
@ -316,21 +308,20 @@ def init_session(options):
|
||||||
'The timezone is currently set to '
|
'The timezone is currently set to '
|
||||||
f'{zone}, switch it to {WINDOWS_TIME_ZONE}?'
|
f'{zone}, switch it to {WINDOWS_TIME_ZONE}?'
|
||||||
)
|
)
|
||||||
if zone != WINDOWS_TIME_ZONE and ask(msg):
|
if zone != WINDOWS_TIME_ZONE and ui.ask(msg):
|
||||||
set_timezone(WINDOWS_TIME_ZONE)
|
set_timezone(WINDOWS_TIME_ZONE)
|
||||||
|
|
||||||
# One-time tasks
|
# One-time tasks
|
||||||
TRY_PRINT.run(
|
if options['Run AVRemover (once)']['Selected']:
|
||||||
'Run AVRemover...', run_tool, 'AVRemover', 'AVRemover',
|
TRY_PRINT.run(
|
||||||
download=True, msg_good='DONE',
|
'Run AVRemover...', run_tool, 'AVRemover', 'AVRemover',
|
||||||
)
|
download=True, msg_good='DONE',
|
||||||
if options['Run TDSSKiller (once)']['Selected']:
|
)
|
||||||
TRY_PRINT.run('Running TDSSKiller...', run_tdsskiller, msg_good='DONE')
|
|
||||||
print('')
|
print('')
|
||||||
reboot(30)
|
reboot(30)
|
||||||
|
|
||||||
|
|
||||||
def is_autologon_enabled():
|
def is_autologon_enabled() -> bool:
|
||||||
"""Check if Autologon is enabled, returns bool."""
|
"""Check if Autologon is enabled, returns bool."""
|
||||||
auto_admin_logon = False
|
auto_admin_logon = False
|
||||||
try:
|
try:
|
||||||
|
|
@ -348,7 +339,7 @@ def is_autologon_enabled():
|
||||||
return auto_admin_logon
|
return auto_admin_logon
|
||||||
|
|
||||||
|
|
||||||
def is_session_started():
|
def is_session_started() -> bool:
|
||||||
"""Check if session was started, returns bool."""
|
"""Check if session was started, returns bool."""
|
||||||
session_started = False
|
session_started = False
|
||||||
try:
|
try:
|
||||||
|
|
@ -360,7 +351,7 @@ def is_session_started():
|
||||||
return session_started
|
return session_started
|
||||||
|
|
||||||
|
|
||||||
def load_preset(menus, presets, enable_menu_exit=True):
|
def load_preset(menus, presets, enable_menu_exit=True) -> None:
|
||||||
"""Load menu settings from preset and ask selection question(s)."""
|
"""Load menu settings from preset and ask selection question(s)."""
|
||||||
if not enable_menu_exit:
|
if not enable_menu_exit:
|
||||||
MENU_PRESETS.actions['Main Menu'].update({'Disabled':True, 'Hidden':True})
|
MENU_PRESETS.actions['Main Menu'].update({'Disabled':True, 'Hidden':True})
|
||||||
|
|
@ -386,26 +377,26 @@ def load_preset(menus, presets, enable_menu_exit=True):
|
||||||
MENU_PRESETS.actions['Main Menu'].update({'Disabled':False, 'Hidden':False})
|
MENU_PRESETS.actions['Main Menu'].update({'Disabled':False, 'Hidden':False})
|
||||||
|
|
||||||
|
|
||||||
def load_settings(menus):
|
def load_settings(menus) -> None:
|
||||||
"""Load session settings from the registry."""
|
"""Load session settings from the registry."""
|
||||||
for group, menu in menus.items():
|
for group, menu in menus.items():
|
||||||
if group == 'Main':
|
if group == 'Main':
|
||||||
continue
|
continue
|
||||||
for name in menu.options:
|
for name in menu.options:
|
||||||
menu.options[name].update(get_entry_settings(group, strip_colors(name)))
|
menu.options[name].update(get_entry_settings(group, ansi.strip_colors(name)))
|
||||||
|
|
||||||
|
|
||||||
def run_auto_repairs(base_menus, presets):
|
def run_auto_repairs(base_menus, presets) -> None:
|
||||||
"""Run Auto Repairs."""
|
"""Run Auto Repairs."""
|
||||||
set_log_path()
|
set_log_path()
|
||||||
title = f'{KIT_NAME_FULL}: Auto Repairs'
|
title = f'{KIT_NAME_FULL}: Auto Repairs'
|
||||||
clear_screen()
|
ui.clear_screen()
|
||||||
set_title(title)
|
ui.set_title(title)
|
||||||
print_info(title)
|
ui.print_info(title)
|
||||||
print('')
|
print('')
|
||||||
|
|
||||||
# Generate menus
|
# Generate menus
|
||||||
print_standard('Initializing...')
|
ui.print_standard('Initializing...')
|
||||||
menus = build_menus(base_menus, title, presets)
|
menus = build_menus(base_menus, title, presets)
|
||||||
|
|
||||||
# Init
|
# Init
|
||||||
|
|
@ -423,21 +414,21 @@ def run_auto_repairs(base_menus, presets):
|
||||||
try:
|
try:
|
||||||
show_main_menu(base_menus, menus, presets, title)
|
show_main_menu(base_menus, menus, presets, title)
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
if ask('End session?'):
|
if ui.ask('End session?'):
|
||||||
end_session()
|
end_session()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Start or resume repairs
|
# Start or resume repairs
|
||||||
clear_screen()
|
ui.clear_screen()
|
||||||
print_standard(title)
|
ui.print_standard(title)
|
||||||
print('')
|
print('')
|
||||||
save_selection_settings(menus)
|
save_selection_settings(menus)
|
||||||
print_info('Initializing...')
|
ui.print_info('Initializing...')
|
||||||
init_run(menus['Options'].options)
|
init_run(menus['Options'].options)
|
||||||
save_selection_settings(menus)
|
save_selection_settings(menus)
|
||||||
if not session_started:
|
if not session_started:
|
||||||
init_session(menus['Options'].options)
|
init_session(menus['Options'].options)
|
||||||
print_info('Running repairs')
|
ui.print_info('Running repairs')
|
||||||
|
|
||||||
# Run repairs
|
# Run repairs
|
||||||
for group, menu in menus.items():
|
for group, menu in menus.items():
|
||||||
|
|
@ -446,19 +437,19 @@ def run_auto_repairs(base_menus, presets):
|
||||||
try:
|
try:
|
||||||
run_group(group, menu)
|
run_group(group, menu)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
abort()
|
ui.abort()
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
end_session()
|
end_session()
|
||||||
print_info('Done')
|
ui.print_info('Done')
|
||||||
pause('Press Enter to exit...')
|
ui.pause('Press Enter to exit...')
|
||||||
|
|
||||||
|
|
||||||
def run_group(group, menu):
|
def run_group(group, menu) -> None:
|
||||||
"""Run entries in group if appropriate."""
|
"""Run entries in group if appropriate."""
|
||||||
print_info(f' {group}')
|
ui.print_info(f' {group}')
|
||||||
for name, details in menu.options.items():
|
for name, details in menu.options.items():
|
||||||
name_str = strip_colors(name)
|
name_str = ansi.strip_colors(name)
|
||||||
skipped = details.get('Skipped', False)
|
skipped = details.get('Skipped', False)
|
||||||
done = details.get('Done', False)
|
done = details.get('Done', False)
|
||||||
disabled = details.get('Disabled', False)
|
disabled = details.get('Disabled', False)
|
||||||
|
|
@ -472,7 +463,7 @@ def run_group(group, menu):
|
||||||
|
|
||||||
# Previously skipped
|
# Previously skipped
|
||||||
if skipped:
|
if skipped:
|
||||||
show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
ui.show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Previously ran
|
# Previously ran
|
||||||
|
|
@ -482,7 +473,7 @@ def run_group(group, menu):
|
||||||
color = 'YELLOW'
|
color = 'YELLOW'
|
||||||
elif details.get('Failed', False):
|
elif details.get('Failed', False):
|
||||||
color = 'RED'
|
color = 'RED'
|
||||||
show_data(
|
ui.show_data(
|
||||||
f'{name_str}...',
|
f'{name_str}...',
|
||||||
details.get('Message', 'Unknown'), color, width=WIDTH,
|
details.get('Message', 'Unknown'), color, width=WIDTH,
|
||||||
)
|
)
|
||||||
|
|
@ -490,7 +481,7 @@ def run_group(group, menu):
|
||||||
|
|
||||||
# Not selected
|
# Not selected
|
||||||
if not selected:
|
if not selected:
|
||||||
show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
ui.show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
||||||
save_settings(group, name, skipped=True)
|
save_settings(group, name, skipped=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -498,7 +489,7 @@ def run_group(group, menu):
|
||||||
details['Function'](group, name)
|
details['Function'](group, name)
|
||||||
|
|
||||||
|
|
||||||
def save_selection_settings(menus):
|
def save_selection_settings(menus) -> None:
|
||||||
"""Save selections in the registry."""
|
"""Save selections in the registry."""
|
||||||
for group, menu in menus.items():
|
for group, menu in menus.items():
|
||||||
if group == 'Main':
|
if group == 'Main':
|
||||||
|
|
@ -511,9 +502,9 @@ def save_selection_settings(menus):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def save_settings(group, name, result=None, **kwargs):
|
def save_settings(group, name, result=None, **kwargs) -> None:
|
||||||
"""Save entry settings in the registry."""
|
"""Save entry settings in the registry."""
|
||||||
key_path = fr'{AUTO_REPAIR_KEY}\{group}\{strip_colors(name)}'
|
key_path = fr'{AUTO_REPAIR_KEY}\{group}\{ansi.strip_colors(name)}'
|
||||||
|
|
||||||
# Get values from TryAndPrint result
|
# Get values from TryAndPrint result
|
||||||
if result:
|
if result:
|
||||||
|
|
@ -527,7 +518,7 @@ def save_settings(group, name, result=None, **kwargs):
|
||||||
|
|
||||||
# Write values to registry
|
# Write values to registry
|
||||||
for value_name, data in kwargs.items():
|
for value_name, data in kwargs.items():
|
||||||
value_name = strip_colors(value_name)
|
value_name = ansi.strip_colors(value_name)
|
||||||
if isinstance(data, bool):
|
if isinstance(data, bool):
|
||||||
data = 1 if data else 0
|
data = 1 if data else 0
|
||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
|
|
@ -539,7 +530,7 @@ def save_settings(group, name, result=None, **kwargs):
|
||||||
reg_set_value('HKCU', key_path, value_name, data, data_type)
|
reg_set_value('HKCU', key_path, value_name, data, data_type)
|
||||||
|
|
||||||
|
|
||||||
def set_log_path():
|
def set_log_path() -> None:
|
||||||
"""Set log name using defaults or the saved registry value."""
|
"""Set log name using defaults or the saved registry value."""
|
||||||
try:
|
try:
|
||||||
log_path = reg_read_value('HKCU', AUTO_REPAIR_KEY, 'LogName')
|
log_path = reg_read_value('HKCU', AUTO_REPAIR_KEY, 'LogName')
|
||||||
|
|
@ -555,7 +546,7 @@ def set_log_path():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def show_main_menu(base_menus, menus, presets, title):
|
def show_main_menu(base_menus, menus, presets, title) -> None:
|
||||||
"""Show main menu and handle actions."""
|
"""Show main menu and handle actions."""
|
||||||
while True:
|
while True:
|
||||||
update_main_menu(menus)
|
update_main_menu(menus)
|
||||||
|
|
@ -570,7 +561,7 @@ def show_main_menu(base_menus, menus, presets, title):
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
def show_sub_menu(menu):
|
def show_sub_menu(menu) -> None:
|
||||||
"""Show sub-menu and handle sub-menu actions."""
|
"""Show sub-menu and handle sub-menu actions."""
|
||||||
while True:
|
while True:
|
||||||
selection = menu.advanced_select()
|
selection = menu.advanced_select()
|
||||||
|
|
@ -601,7 +592,7 @@ def show_sub_menu(menu):
|
||||||
menu.options[name][key] = value
|
menu.options[name][key] = value
|
||||||
|
|
||||||
|
|
||||||
def update_main_menu(menus):
|
def update_main_menu(menus) -> None:
|
||||||
"""Update main menu based on current selections."""
|
"""Update main menu based on current selections."""
|
||||||
index = 1
|
index = 1
|
||||||
skip = 'Reboot'
|
skip = 'Reboot'
|
||||||
|
|
@ -620,7 +611,7 @@ def update_main_menu(menus):
|
||||||
|
|
||||||
|
|
||||||
# Auto Repairs: Wrapper Functions
|
# Auto Repairs: Wrapper Functions
|
||||||
def auto_adwcleaner(group, name):
|
def auto_adwcleaner(group, name) -> None:
|
||||||
"""Run AdwCleaner scan.
|
"""Run AdwCleaner scan.
|
||||||
|
|
||||||
save_settings() is called first since AdwCleaner may kill this script.
|
save_settings() is called first since AdwCleaner may kill this script.
|
||||||
|
|
@ -632,25 +623,25 @@ def auto_adwcleaner(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_backup_browser_profiles(group, name):
|
def auto_backup_browser_profiles(group, name) -> None:
|
||||||
"""Backup browser profiles."""
|
"""Backup browser profiles."""
|
||||||
backup_all_browser_profiles(use_try_print=True)
|
backup_all_browser_profiles(use_try_print=True)
|
||||||
save_settings(group, name, done=True, failed=False, message='DONE')
|
save_settings(group, name, done=True, failed=False, message='DONE')
|
||||||
|
|
||||||
|
|
||||||
def auto_backup_power_plans(group, name):
|
def auto_backup_power_plans(group, name) -> None:
|
||||||
"""Backup power plans."""
|
"""Backup power plans."""
|
||||||
result = TRY_PRINT.run('Backup Power Plans...', export_power_plans)
|
result = TRY_PRINT.run('Backup Power Plans...', export_power_plans)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_backup_registry(group, name):
|
def auto_backup_registry(group, name) -> None:
|
||||||
"""Backup registry."""
|
"""Backup registry."""
|
||||||
result = TRY_PRINT.run('Backup Registry...', backup_registry)
|
result = TRY_PRINT.run('Backup Registry...', backup_registry)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_bleachbit(group, name):
|
def auto_bleachbit(group, name) -> None:
|
||||||
"""Run BleachBit to clean files."""
|
"""Run BleachBit to clean files."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'BleachBit...', run_bleachbit, BLEACH_BIT_CLEANERS, msg_good='DONE',
|
'BleachBit...', run_bleachbit, BLEACH_BIT_CLEANERS, msg_good='DONE',
|
||||||
|
|
@ -658,7 +649,15 @@ def auto_bleachbit(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_chkdsk(group, name):
|
def auto_bcuninstaller(group, name) -> None:
|
||||||
|
"""Run BCUninstaller."""
|
||||||
|
result = TRY_PRINT.run(
|
||||||
|
'BCUninstaller...', run_bcuninstaller, msg_good='DONE',
|
||||||
|
)
|
||||||
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
|
def auto_chkdsk(group, name) -> None:
|
||||||
"""Run CHKDSK repairs."""
|
"""Run CHKDSK repairs."""
|
||||||
needs_reboot = False
|
needs_reboot = False
|
||||||
result = TRY_PRINT.run(f'CHKDSK ({SYSTEMDRIVE})...', run_chkdsk_online)
|
result = TRY_PRINT.run(f'CHKDSK ({SYSTEMDRIVE})...', run_chkdsk_online)
|
||||||
|
|
@ -682,7 +681,7 @@ def auto_chkdsk(group, name):
|
||||||
reboot()
|
reboot()
|
||||||
|
|
||||||
|
|
||||||
def auto_disable_pending_renames(group, name):
|
def auto_disable_pending_renames(group, name) -> None:
|
||||||
"""Disable pending renames."""
|
"""Disable pending renames."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Disabling pending renames...', disable_pending_renames,
|
'Disabling pending renames...', disable_pending_renames,
|
||||||
|
|
@ -690,7 +689,7 @@ def auto_disable_pending_renames(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_dism(group, name):
|
def auto_dism(group, name) -> None:
|
||||||
"""Run DISM repairs."""
|
"""Run DISM repairs."""
|
||||||
needs_reboot = False
|
needs_reboot = False
|
||||||
result = TRY_PRINT.run('DISM (RestoreHealth)...', run_dism)
|
result = TRY_PRINT.run('DISM (RestoreHealth)...', run_dism)
|
||||||
|
|
@ -715,7 +714,7 @@ def auto_dism(group, name):
|
||||||
reboot()
|
reboot()
|
||||||
|
|
||||||
|
|
||||||
def auto_enable_regback(group, name):
|
def auto_enable_regback(group, name) -> None:
|
||||||
"""Enable RegBack."""
|
"""Enable RegBack."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Enable RegBack...', reg_set_value, 'HKLM',
|
'Enable RegBack...', reg_set_value, 'HKLM',
|
||||||
|
|
@ -725,19 +724,19 @@ def auto_enable_regback(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_hitmanpro(group, name):
|
def auto_hitmanpro(group, name) -> None:
|
||||||
"""Run HitmanPro scan."""
|
"""Run HitmanPro scan."""
|
||||||
result = TRY_PRINT.run('HitmanPro...', run_hitmanpro, msg_good='DONE')
|
result = TRY_PRINT.run('HitmanPro...', run_hitmanpro, msg_good='DONE')
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_kvrt(group, name):
|
def auto_kvrt(group, name) -> None:
|
||||||
"""Run KVRT scan."""
|
"""Run KVRT scan."""
|
||||||
result = TRY_PRINT.run('KVRT...', run_kvrt, msg_good='DONE')
|
result = TRY_PRINT.run('KVRT...', run_kvrt, msg_good='DONE')
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_microsoft_defender(group, name):
|
def auto_microsoft_defender(group, name) -> None:
|
||||||
"""Run Microsoft Defender scan."""
|
"""Run Microsoft Defender scan."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Microsoft Defender...', run_microsoft_defender, msg_good='DONE',
|
'Microsoft Defender...', run_microsoft_defender, msg_good='DONE',
|
||||||
|
|
@ -745,14 +744,14 @@ def auto_microsoft_defender(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_reboot(group, name):
|
def auto_reboot(group, name) -> None:
|
||||||
"""Reboot the system."""
|
"""Reboot the system."""
|
||||||
save_settings(group, name, done=True, failed=False, message='DONE')
|
save_settings(group, name, done=True, failed=False, message='DONE')
|
||||||
print('')
|
print('')
|
||||||
reboot(30)
|
reboot(30)
|
||||||
|
|
||||||
|
|
||||||
def auto_remove_power_plan(group, name):
|
def auto_remove_power_plan(group, name) -> None:
|
||||||
"""Remove custom power plan and set to Balanced."""
|
"""Remove custom power plan and set to Balanced."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Remove Custom Power Plan...', remove_custom_power_plan,
|
'Remove Custom Power Plan...', remove_custom_power_plan,
|
||||||
|
|
@ -760,7 +759,7 @@ def auto_remove_power_plan(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_repair_registry(group, name):
|
def auto_repair_registry(group, name) -> None:
|
||||||
"""Delete registry keys with embedded null characters."""
|
"""Delete registry keys with embedded null characters."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Running Registry repairs...', delete_registry_null_keys,
|
'Running Registry repairs...', delete_registry_null_keys,
|
||||||
|
|
@ -768,19 +767,19 @@ def auto_repair_registry(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_reset_power_plans(group, name):
|
def auto_reset_power_plans(group, name) -> None:
|
||||||
"""Reset power plans."""
|
"""Reset power plans."""
|
||||||
result = TRY_PRINT.run('Reset Power Plans...', reset_power_plans)
|
result = TRY_PRINT.run('Reset Power Plans...', reset_power_plans)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_reset_proxy(group, name):
|
def auto_reset_proxy(group, name) -> None:
|
||||||
"""Reset proxy settings."""
|
"""Reset proxy settings."""
|
||||||
result = TRY_PRINT.run('Clearing proxy settings...', reset_proxy)
|
result = TRY_PRINT.run('Clearing proxy settings...', reset_proxy)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_reset_windows_policies(group, name):
|
def auto_reset_windows_policies(group, name) -> None:
|
||||||
"""Reset Windows policies to defaults."""
|
"""Reset Windows policies to defaults."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Resetting Windows policies...', reset_windows_policies,
|
'Resetting Windows policies...', reset_windows_policies,
|
||||||
|
|
@ -788,13 +787,13 @@ def auto_reset_windows_policies(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_restore_uac_defaults(group, name):
|
def auto_restore_uac_defaults(group, name) -> None:
|
||||||
"""Restore UAC default settings."""
|
"""Restore UAC default settings."""
|
||||||
result = TRY_PRINT.run('Restoring UAC defaults...', restore_uac_defaults)
|
result = TRY_PRINT.run('Restoring UAC defaults...', restore_uac_defaults)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_set_custom_power_plan(group, name):
|
def auto_set_custom_power_plan(group, name) -> None:
|
||||||
"""Set custom power plan."""
|
"""Set custom power plan."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Set Custom Power Plan...', create_custom_power_plan,
|
'Set Custom Power Plan...', create_custom_power_plan,
|
||||||
|
|
@ -804,13 +803,13 @@ def auto_set_custom_power_plan(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_sfc(group, name):
|
def auto_sfc(group, name) -> None:
|
||||||
"""Run SFC repairs."""
|
"""Run SFC repairs."""
|
||||||
result = TRY_PRINT.run('SFC Scan...', run_sfc_scan)
|
result = TRY_PRINT.run('SFC Scan...', run_sfc_scan)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_system_restore_create(group, name):
|
def auto_system_restore_create(group, name) -> None:
|
||||||
"""Create System Restore point."""
|
"""Create System Restore point."""
|
||||||
result = TRY_PRINT.run(
|
result = TRY_PRINT.run(
|
||||||
'Create System Restore...', create_system_restore_point,
|
'Create System Restore...', create_system_restore_point,
|
||||||
|
|
@ -818,7 +817,7 @@ def auto_system_restore_create(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_system_restore_enable(group, name):
|
def auto_system_restore_enable(group, name) -> None:
|
||||||
"""Enable System Restore."""
|
"""Enable System Restore."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'powershell', '-Command', 'Enable-ComputerRestore',
|
'powershell', '-Command', 'Enable-ComputerRestore',
|
||||||
|
|
@ -828,21 +827,13 @@ def auto_system_restore_enable(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_system_restore_set_size(group, name):
|
def auto_system_restore_set_size(group, name) -> None:
|
||||||
"""Set System Restore size."""
|
"""Set System Restore size."""
|
||||||
result = TRY_PRINT.run('Set System Restore Size...', set_system_restore_size)
|
result = TRY_PRINT.run('Set System Restore Size...', set_system_restore_size)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_uninstallview(group, name):
|
def auto_windows_updates_disable(group, name) -> None:
|
||||||
"""Run UninstallView."""
|
|
||||||
result = TRY_PRINT.run(
|
|
||||||
'UninstallView...', run_uninstallview, msg_good='DONE',
|
|
||||||
)
|
|
||||||
save_settings(group, name, result=result)
|
|
||||||
|
|
||||||
|
|
||||||
def auto_windows_updates_disable(group, name):
|
|
||||||
"""Disable Windows Updates."""
|
"""Disable Windows Updates."""
|
||||||
result = TRY_PRINT.run('Disable Windows Updates...', disable_windows_updates)
|
result = TRY_PRINT.run('Disable Windows Updates...', disable_windows_updates)
|
||||||
if result['Failed']:
|
if result['Failed']:
|
||||||
|
|
@ -851,13 +842,13 @@ def auto_windows_updates_disable(group, name):
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_windows_updates_enable(group, name):
|
def auto_windows_updates_enable(group, name) -> None:
|
||||||
"""Enable Windows Updates."""
|
"""Enable Windows Updates."""
|
||||||
result = TRY_PRINT.run('Enable Windows Updates...', enable_windows_updates)
|
result = TRY_PRINT.run('Enable Windows Updates...', enable_windows_updates)
|
||||||
save_settings(group, name, result=result)
|
save_settings(group, name, result=result)
|
||||||
|
|
||||||
|
|
||||||
def auto_windows_updates_reset(group, name):
|
def auto_windows_updates_reset(group, name) -> None:
|
||||||
"""Reset Windows Updates."""
|
"""Reset Windows Updates."""
|
||||||
result = TRY_PRINT.run('Reset Windows Updates...', reset_windows_updates)
|
result = TRY_PRINT.run('Reset Windows Updates...', reset_windows_updates)
|
||||||
if result['Failed']:
|
if result['Failed']:
|
||||||
|
|
@ -867,12 +858,12 @@ def auto_windows_updates_reset(group, name):
|
||||||
|
|
||||||
|
|
||||||
# Misc Functions
|
# Misc Functions
|
||||||
def set_backup_path(name, date=False):
|
def set_backup_path(name, date=False) -> pathlib.Path:
|
||||||
"""Set backup path, returns pathlib.Path."""
|
"""Set backup path, returns pathlib.Path."""
|
||||||
return set_local_storage_path('Backups', name, date)
|
return set_local_storage_path('Backups', name, date)
|
||||||
|
|
||||||
|
|
||||||
def set_local_storage_path(folder, name, date=False):
|
def set_local_storage_path(folder, name, date=False) -> pathlib.Path:
|
||||||
"""Get path for local storage, returns pathlib.Path."""
|
"""Get path for local storage, returns pathlib.Path."""
|
||||||
local_path = get_path_obj(f'{SYSTEMDRIVE}/{KIT_NAME_SHORT}/{folder}/{name}')
|
local_path = get_path_obj(f'{SYSTEMDRIVE}/{KIT_NAME_SHORT}/{folder}/{name}')
|
||||||
if date:
|
if date:
|
||||||
|
|
@ -880,22 +871,22 @@ def set_local_storage_path(folder, name, date=False):
|
||||||
return local_path
|
return local_path
|
||||||
|
|
||||||
|
|
||||||
def set_quarantine_path(name, date=False):
|
def set_quarantine_path(name, date=False) -> pathlib.Path:
|
||||||
"""Set quarantine path, returns pathlib.Path."""
|
"""Set quarantine path, returns pathlib.Path."""
|
||||||
return set_local_storage_path('Quarantine', name, date)
|
return set_local_storage_path('Quarantine', name, date)
|
||||||
|
|
||||||
|
|
||||||
# Tool Functions
|
# Tool Functions
|
||||||
def backup_all_browser_profiles(use_try_print=False):
|
def backup_all_browser_profiles(use_try_print=False) -> None:
|
||||||
"""Backup browser profiles for all users."""
|
"""Backup browser profiles for all users."""
|
||||||
users = get_path_obj(f'{SYSTEMDRIVE}/Users')
|
users = get_path_obj(f'{SYSTEMDRIVE}/Users')
|
||||||
for userprofile in users.iterdir():
|
for userprofile in users.iterdir():
|
||||||
if use_try_print:
|
if use_try_print:
|
||||||
print_info(f'{" "*6}{userprofile.name}')
|
ui.print_info(f'{" "*6}{userprofile.name}')
|
||||||
backup_browser_profiles(userprofile, use_try_print)
|
backup_browser_profiles(userprofile, use_try_print)
|
||||||
|
|
||||||
|
|
||||||
def backup_browser_chromium(backup_path, browser, search_path, use_try_print):
|
def backup_browser_chromium(backup_path, browser, search_path, use_try_print) -> None:
|
||||||
"""Backup Chromium-based browser profile."""
|
"""Backup Chromium-based browser profile."""
|
||||||
for item in search_path.iterdir():
|
for item in search_path.iterdir():
|
||||||
match = re.match(r'^(Default|Profile).*', item.name, re.IGNORECASE)
|
match = re.match(r'^(Default|Profile).*', item.name, re.IGNORECASE)
|
||||||
|
|
@ -905,7 +896,7 @@ def backup_browser_chromium(backup_path, browser, search_path, use_try_print):
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
# Assuming backup was already done
|
# Assuming backup was already done
|
||||||
if use_try_print:
|
if use_try_print:
|
||||||
show_data(
|
ui.show_data(
|
||||||
f'{" "*8}{browser} ({item.name})...', 'Backup already exists.',
|
f'{" "*8}{browser} ({item.name})...', 'Backup already exists.',
|
||||||
color='YELLOW', width=WIDTH,
|
color='YELLOW', width=WIDTH,
|
||||||
)
|
)
|
||||||
|
|
@ -925,7 +916,7 @@ def backup_browser_chromium(backup_path, browser, search_path, use_try_print):
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def backup_browser_firefox(backup_path, search_path, use_try_print):
|
def backup_browser_firefox(backup_path, search_path, use_try_print) -> None:
|
||||||
"""Backup Firefox browser profile."""
|
"""Backup Firefox browser profile."""
|
||||||
output_path = backup_path.joinpath('Firefox.7z')
|
output_path = backup_path.joinpath('Firefox.7z')
|
||||||
|
|
||||||
|
|
@ -933,7 +924,7 @@ def backup_browser_firefox(backup_path, search_path, use_try_print):
|
||||||
if output_path.exists():
|
if output_path.exists():
|
||||||
# Assuming backup was already done
|
# Assuming backup was already done
|
||||||
if use_try_print:
|
if use_try_print:
|
||||||
show_data(
|
ui.show_data(
|
||||||
f'{" "*8}Firefox (All)...', 'Backup already exists.',
|
f'{" "*8}Firefox (All)...', 'Backup already exists.',
|
||||||
color='YELLOW', width=WIDTH,
|
color='YELLOW', width=WIDTH,
|
||||||
)
|
)
|
||||||
|
|
@ -950,7 +941,7 @@ def backup_browser_firefox(backup_path, search_path, use_try_print):
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def backup_browser_profiles(userprofile, use_try_print=False):
|
def backup_browser_profiles(userprofile, use_try_print=False) -> None:
|
||||||
"""Backup browser profiles for userprofile."""
|
"""Backup browser profiles for userprofile."""
|
||||||
backup_path = set_backup_path('Browsers', date=True)
|
backup_path = set_backup_path('Browsers', date=True)
|
||||||
backup_path = backup_path.joinpath(userprofile.name)
|
backup_path = backup_path.joinpath(userprofile.name)
|
||||||
|
|
@ -979,7 +970,7 @@ def backup_browser_profiles(userprofile, use_try_print=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def backup_registry():
|
def backup_registry() -> None:
|
||||||
"""Backup Registry."""
|
"""Backup Registry."""
|
||||||
backup_path = set_backup_path('Registry', date=True)
|
backup_path = set_backup_path('Registry', date=True)
|
||||||
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
backup_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -992,12 +983,12 @@ def backup_registry():
|
||||||
run_tool('ERUNT', 'ERUNT', backup_path, 'sysreg', 'curuser', 'otherusers')
|
run_tool('ERUNT', 'ERUNT', backup_path, 'sysreg', 'curuser', 'otherusers')
|
||||||
|
|
||||||
|
|
||||||
def delete_registry_null_keys():
|
def delete_registry_null_keys() -> None:
|
||||||
"""Delete registry keys with embedded null characters."""
|
"""Delete registry keys with embedded null characters."""
|
||||||
run_tool('RegDelNull', 'RegDelNull', '-s', '-y', download=True)
|
run_tool('RegDelNull', 'RegDelNull', '-s', '-y', download=True)
|
||||||
|
|
||||||
|
|
||||||
def log_kvrt_results(log_path, report_path):
|
def log_kvrt_results(log_path, report_path) -> None:
|
||||||
"""Parse KVRT report and log results in plain text."""
|
"""Parse KVRT report and log results in plain text."""
|
||||||
log_text = ''
|
log_text = ''
|
||||||
report_file = None
|
report_file = None
|
||||||
|
|
@ -1038,7 +1029,7 @@ def log_kvrt_results(log_path, report_path):
|
||||||
log_path.write_text(log_text, encoding='utf-8')
|
log_path.write_text(log_text, encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
def run_adwcleaner():
|
def run_adwcleaner() -> None:
|
||||||
"""Run AdwCleaner."""
|
"""Run AdwCleaner."""
|
||||||
settings_path = get_tool_path('AdwCleaner', 'AdwCleaner', check=False)
|
settings_path = get_tool_path('AdwCleaner', 'AdwCleaner', check=False)
|
||||||
settings_path = settings_path.with_name('settings')
|
settings_path = settings_path.with_name('settings')
|
||||||
|
|
@ -1048,7 +1039,7 @@ def run_adwcleaner():
|
||||||
run_tool('AdwCleaner', 'AdwCleaner', download=True)
|
run_tool('AdwCleaner', 'AdwCleaner', download=True)
|
||||||
|
|
||||||
|
|
||||||
def run_bleachbit(cleaners, preview=True):
|
def run_bleachbit(cleaners, preview=True) -> None:
|
||||||
"""Run BleachBit to either clean or preview files."""
|
"""Run BleachBit to either clean or preview files."""
|
||||||
cmd_args = (
|
cmd_args = (
|
||||||
'--preview' if preview else '--clean',
|
'--preview' if preview else '--clean',
|
||||||
|
|
@ -1059,11 +1050,20 @@ def run_bleachbit(cleaners, preview=True):
|
||||||
proc = run_tool('BleachBit', 'bleachbit_console', *cmd_args)
|
proc = run_tool('BleachBit', 'bleachbit_console', *cmd_args)
|
||||||
|
|
||||||
# Save logs
|
# Save logs
|
||||||
log_path.write_text(proc.stdout, encoding='utf-8')
|
log_path.write_text(
|
||||||
log_path.with_suffix('.err').write_text(proc.stderr, encoding='utf-8')
|
proc.stdout, encoding='utf-8', # type: ignore[reportGeneralTypeIssues]
|
||||||
|
)
|
||||||
|
log_path.with_suffix('.err').write_text(
|
||||||
|
proc.stderr, encoding='utf-8', # type: ignore[reportGeneralTypeIssues]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_hitmanpro():
|
def run_bcuninstaller() -> None:
|
||||||
|
"""Run BCUninstaller."""
|
||||||
|
run_tool('BCUninstaller', 'BCUninstaller')
|
||||||
|
|
||||||
|
|
||||||
|
def run_hitmanpro() -> None:
|
||||||
"""Run HitmanPro scan."""
|
"""Run HitmanPro scan."""
|
||||||
log_path = format_log_path(log_name='HitmanPro', timestamp=True, tool=True)
|
log_path = format_log_path(log_name='HitmanPro', timestamp=True, tool=True)
|
||||||
log_path = log_path.with_suffix('.xml')
|
log_path = log_path.with_suffix('.xml')
|
||||||
|
|
@ -1072,7 +1072,7 @@ def run_hitmanpro():
|
||||||
run_tool('HitmanPro', 'HitmanPro', *cmd_args, download=True)
|
run_tool('HitmanPro', 'HitmanPro', *cmd_args, download=True)
|
||||||
|
|
||||||
|
|
||||||
def run_kvrt():
|
def run_kvrt() -> None:
|
||||||
"""Run KVRT scan."""
|
"""Run KVRT scan."""
|
||||||
log_path = format_log_path(log_name='KVRT', timestamp=True, tool=True)
|
log_path = format_log_path(log_name='KVRT', timestamp=True, tool=True)
|
||||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -1113,11 +1113,11 @@ def run_kvrt():
|
||||||
log_kvrt_results(log_path, report_path)
|
log_kvrt_results(log_path, report_path)
|
||||||
|
|
||||||
|
|
||||||
def run_microsoft_defender(full=True):
|
def run_microsoft_defender(full=True) -> None:
|
||||||
"""Run Microsoft Defender scan."""
|
"""Run Microsoft Defender scan."""
|
||||||
reg_key = r'Software\Microsoft\Windows Defender'
|
reg_key = r'Software\Microsoft\Windows Defender'
|
||||||
|
|
||||||
def _get_defender_path():
|
def _get_defender_path() -> str:
|
||||||
install_path = reg_read_value('HKLM', reg_key, 'InstallLocation')
|
install_path = reg_read_value('HKLM', reg_key, 'InstallLocation')
|
||||||
return fr'{install_path}\MpCmdRun.exe'
|
return fr'{install_path}\MpCmdRun.exe'
|
||||||
|
|
||||||
|
|
@ -1158,7 +1158,7 @@ def run_microsoft_defender(full=True):
|
||||||
raise GenericError('Failed to run scan or clean items.')
|
raise GenericError('Failed to run scan or clean items.')
|
||||||
|
|
||||||
|
|
||||||
def run_rkill():
|
def run_rkill() -> None:
|
||||||
"""Run RKill scan."""
|
"""Run RKill scan."""
|
||||||
log_path = format_log_path(log_name='RKill', timestamp=True, tool=True)
|
log_path = format_log_path(log_name='RKill', timestamp=True, tool=True)
|
||||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -1172,31 +1172,8 @@ def run_rkill():
|
||||||
run_tool('RKill', 'RKill', *cmd_args, download=True)
|
run_tool('RKill', 'RKill', *cmd_args, download=True)
|
||||||
|
|
||||||
|
|
||||||
def run_tdsskiller():
|
|
||||||
"""Run TDSSKiller scan."""
|
|
||||||
log_path = format_log_path(log_name='TDSSKiller', timestamp=True, tool=True)
|
|
||||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
quarantine_path = set_quarantine_path('TDSSKiller')
|
|
||||||
quarantine_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
cmd_args = (
|
|
||||||
'-accepteula',
|
|
||||||
'-accepteulaksn',
|
|
||||||
'-l', log_path,
|
|
||||||
'-qpath', quarantine_path,
|
|
||||||
'-qsus',
|
|
||||||
'-dcexact',
|
|
||||||
'-silent',
|
|
||||||
)
|
|
||||||
run_tool('TDSSKiller', 'TDSSKiller', *cmd_args, download=True)
|
|
||||||
|
|
||||||
|
|
||||||
def run_uninstallview():
|
|
||||||
"""Run UninstallView."""
|
|
||||||
run_tool('UninstallView', 'UninstallView')
|
|
||||||
|
|
||||||
|
|
||||||
# OS Built-in Functions
|
# OS Built-in Functions
|
||||||
def create_custom_power_plan(enable_sleep=True, keep_display_on=False):
|
def create_custom_power_plan(enable_sleep=True, keep_display_on=False) -> None:
|
||||||
"""Create new power plan and set as active."""
|
"""Create new power plan and set as active."""
|
||||||
custom_guid = POWER_PLANS['Custom']
|
custom_guid = POWER_PLANS['Custom']
|
||||||
sleep_timeouts = POWER_PLAN_SLEEP_TIMEOUTS['High Performance']
|
sleep_timeouts = POWER_PLAN_SLEEP_TIMEOUTS['High Performance']
|
||||||
|
|
@ -1245,7 +1222,7 @@ def create_custom_power_plan(enable_sleep=True, keep_display_on=False):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def create_system_restore_point():
|
def create_system_restore_point() -> None:
|
||||||
"""Create System Restore point."""
|
"""Create System Restore point."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'powershell', '-Command', 'Checkpoint-Computer',
|
'powershell', '-Command', 'Checkpoint-Computer',
|
||||||
|
|
@ -1260,7 +1237,7 @@ def create_system_restore_point():
|
||||||
raise GenericWarning('Skipped, a restore point was created too recently')
|
raise GenericWarning('Skipped, a restore point was created too recently')
|
||||||
|
|
||||||
|
|
||||||
def disable_pending_renames():
|
def disable_pending_renames() -> None:
|
||||||
"""Disable pending renames."""
|
"""Disable pending renames."""
|
||||||
reg_set_value(
|
reg_set_value(
|
||||||
'HKLM', r'SYSTEM\CurrentControlSet\Control\Session Manager',
|
'HKLM', r'SYSTEM\CurrentControlSet\Control\Session Manager',
|
||||||
|
|
@ -1268,18 +1245,18 @@ def disable_pending_renames():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def disable_windows_updates():
|
def disable_windows_updates() -> None:
|
||||||
"""Disable and stop Windows Updates."""
|
"""Disable and stop Windows Updates."""
|
||||||
disable_service('wuauserv')
|
disable_service('wuauserv')
|
||||||
stop_service('wuauserv')
|
stop_service('wuauserv')
|
||||||
|
|
||||||
|
|
||||||
def enable_windows_updates():
|
def enable_windows_updates() -> None:
|
||||||
"""Enable Windows Updates."""
|
"""Enable Windows Updates."""
|
||||||
enable_service('wuauserv', 'demand')
|
enable_service('wuauserv', 'demand')
|
||||||
|
|
||||||
|
|
||||||
def export_power_plans():
|
def export_power_plans() -> None:
|
||||||
"""Export existing power plans."""
|
"""Export existing power plans."""
|
||||||
backup_path = set_backup_path('Power Plans', date=True)
|
backup_path = set_backup_path('Power Plans', date=True)
|
||||||
|
|
||||||
|
|
@ -1310,23 +1287,23 @@ def export_power_plans():
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def kill_explorer():
|
def kill_explorer() -> None:
|
||||||
"""Kill all Explorer processes."""
|
"""Kill all Explorer processes."""
|
||||||
cmd = ['taskkill', '/im', 'explorer.exe', '/f']
|
cmd = ['taskkill', '/im', 'explorer.exe', '/f']
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def reboot(timeout=10):
|
def reboot(timeout=10) -> None:
|
||||||
"""Reboot the system."""
|
"""Reboot the system."""
|
||||||
atexit.unregister(start_explorer)
|
atexit.unregister(start_explorer)
|
||||||
print_warning(f'Rebooting the system in {timeout} seconds...')
|
ui.print_warning(f'Rebooting the system in {timeout} seconds...')
|
||||||
sleep(timeout)
|
sleep(timeout)
|
||||||
cmd = ['shutdown', '-r', '-t', '0']
|
cmd = ['shutdown', '-r', '-t', '0']
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
def remove_custom_power_plan(high_performance=False):
|
def remove_custom_power_plan(high_performance=False) -> None:
|
||||||
"""Remove custom power plan and set to a built-in plan.
|
"""Remove custom power plan and set to a built-in plan.
|
||||||
|
|
||||||
If high_performance is True then set to High Performance and set
|
If high_performance is True then set to High Performance and set
|
||||||
|
|
@ -1353,13 +1330,13 @@ def remove_custom_power_plan(high_performance=False):
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def reset_power_plans():
|
def reset_power_plans() -> None:
|
||||||
"""Reset power plans to their default settings."""
|
"""Reset power plans to their default settings."""
|
||||||
cmd = ['powercfg', '-RestoreDefaultSchemes']
|
cmd = ['powercfg', '-RestoreDefaultSchemes']
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def reset_proxy():
|
def reset_proxy() -> None:
|
||||||
"""Reset WinHTTP proxy settings."""
|
"""Reset WinHTTP proxy settings."""
|
||||||
cmd = ['netsh', 'winhttp', 'reset', 'proxy']
|
cmd = ['netsh', 'winhttp', 'reset', 'proxy']
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
|
|
@ -1369,7 +1346,7 @@ def reset_proxy():
|
||||||
raise GenericError('Failed to reset proxy settings.')
|
raise GenericError('Failed to reset proxy settings.')
|
||||||
|
|
||||||
|
|
||||||
def reset_windows_policies():
|
def reset_windows_policies() -> None:
|
||||||
"""Reset Windows policies to defaults."""
|
"""Reset Windows policies to defaults."""
|
||||||
cmd = ['gpupdate', '/force']
|
cmd = ['gpupdate', '/force']
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
|
|
@ -1379,7 +1356,7 @@ def reset_windows_policies():
|
||||||
raise GenericError('Failed to reset one or more policies.')
|
raise GenericError('Failed to reset one or more policies.')
|
||||||
|
|
||||||
|
|
||||||
def reset_windows_updates():
|
def reset_windows_updates() -> None:
|
||||||
"""Reset Windows Updates."""
|
"""Reset Windows Updates."""
|
||||||
system_root = os.environ.get('SYSTEMROOT', 'C:/Windows')
|
system_root = os.environ.get('SYSTEMROOT', 'C:/Windows')
|
||||||
src_path = f'{system_root}/SoftwareDistribution'
|
src_path = f'{system_root}/SoftwareDistribution'
|
||||||
|
|
@ -1392,7 +1369,7 @@ def reset_windows_updates():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def restore_uac_defaults():
|
def restore_uac_defaults() -> None:
|
||||||
"""Restore UAC default settings."""
|
"""Restore UAC default settings."""
|
||||||
settings = REG_UAC_DEFAULTS_WIN10
|
settings = REG_UAC_DEFAULTS_WIN10
|
||||||
if OS_VERSION in (7, 8, 8.1):
|
if OS_VERSION in (7, 8, 8.1):
|
||||||
|
|
@ -1401,7 +1378,7 @@ def restore_uac_defaults():
|
||||||
reg_write_settings(settings)
|
reg_write_settings(settings)
|
||||||
|
|
||||||
|
|
||||||
def run_chkdsk_offline():
|
def run_chkdsk_offline() -> None:
|
||||||
"""Set filesystem 'dirty bit' to force a CHKDSK during startup."""
|
"""Set filesystem 'dirty bit' to force a CHKDSK during startup."""
|
||||||
cmd = ['fsutil', 'dirty', 'set', SYSTEMDRIVE]
|
cmd = ['fsutil', 'dirty', 'set', SYSTEMDRIVE]
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
|
|
@ -1411,7 +1388,7 @@ def run_chkdsk_offline():
|
||||||
raise GenericError('Failed to set dirty bit.')
|
raise GenericError('Failed to set dirty bit.')
|
||||||
|
|
||||||
|
|
||||||
def run_chkdsk_online():
|
def run_chkdsk_online() -> None:
|
||||||
"""Run CHKDSK.
|
"""Run CHKDSK.
|
||||||
|
|
||||||
NOTE: If run on Windows 8+ online repairs are attempted.
|
NOTE: If run on Windows 8+ online repairs are attempted.
|
||||||
|
|
@ -1451,7 +1428,7 @@ def run_chkdsk_online():
|
||||||
raise GenericError('Issue(s) detected')
|
raise GenericError('Issue(s) detected')
|
||||||
|
|
||||||
|
|
||||||
def run_dism(repair=True):
|
def run_dism(repair=True) -> None:
|
||||||
"""Run DISM to either scan or repair component store health."""
|
"""Run DISM to either scan or repair component store health."""
|
||||||
conemu_args = ['-new_console:nb', '-new_console:s33V'] if IN_CONEMU else []
|
conemu_args = ['-new_console:nb', '-new_console:s33V'] if IN_CONEMU else []
|
||||||
|
|
||||||
|
|
@ -1490,7 +1467,7 @@ def run_dism(repair=True):
|
||||||
raise GenericError('Issue(s) detected')
|
raise GenericError('Issue(s) detected')
|
||||||
|
|
||||||
|
|
||||||
def run_sfc_scan():
|
def run_sfc_scan() -> None:
|
||||||
"""Run SFC and save results."""
|
"""Run SFC and save results."""
|
||||||
cmd = ['sfc', '/scannow']
|
cmd = ['sfc', '/scannow']
|
||||||
log_path = format_log_path(log_name='SFC', timestamp=True, tool=True)
|
log_path = format_log_path(log_name='SFC', timestamp=True, tool=True)
|
||||||
|
|
@ -1517,7 +1494,7 @@ def run_sfc_scan():
|
||||||
raise OSError
|
raise OSError
|
||||||
|
|
||||||
|
|
||||||
def set_system_restore_size(size=8):
|
def set_system_restore_size(size=8) -> None:
|
||||||
"""Set System Restore size."""
|
"""Set System Restore size."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'vssadmin', 'Resize', 'ShadowStorage',
|
'vssadmin', 'Resize', 'ShadowStorage',
|
||||||
|
|
@ -1526,7 +1503,7 @@ def set_system_restore_size(size=8):
|
||||||
run_program(cmd, pipe=False, stderr=DEVNULL, stdout=DEVNULL)
|
run_program(cmd, pipe=False, stderr=DEVNULL, stdout=DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
def start_explorer():
|
def start_explorer() -> None:
|
||||||
"""Start Explorer."""
|
"""Start Explorer."""
|
||||||
popen_program(['explorer.exe'])
|
popen_program(['explorer.exe'])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from wk.cfg.main import KIT_NAME_FULL
|
from wk.cfg.main import KIT_NAME_FULL
|
||||||
from wk.cfg.setup import (
|
from wk.cfg.setup import (
|
||||||
BROWSER_PATHS,
|
BROWSER_PATHS,
|
||||||
|
|
@ -17,13 +19,13 @@ from wk.cfg.setup import (
|
||||||
REG_WINDOWS_EXPLORER,
|
REG_WINDOWS_EXPLORER,
|
||||||
REG_OPEN_SHELL_SETTINGS,
|
REG_OPEN_SHELL_SETTINGS,
|
||||||
REG_OPEN_SHELL_LOW_POWER_IDLE,
|
REG_OPEN_SHELL_LOW_POWER_IDLE,
|
||||||
|
REG_WINDOWS_BSOD_MINIDUMPS,
|
||||||
UBLOCK_ORIGIN_URLS,
|
UBLOCK_ORIGIN_URLS,
|
||||||
)
|
)
|
||||||
from wk.exe import kill_procs, run_program, popen_program
|
from wk.exe import kill_procs, run_program, popen_program
|
||||||
from wk.io import case_insensitive_path, get_path_obj
|
from wk.io import case_insensitive_path, get_path_obj
|
||||||
from wk.kit.tools import (
|
from wk.kit.tools import (
|
||||||
ARCH,
|
ARCH,
|
||||||
download_tool,
|
|
||||||
extract_archive,
|
extract_archive,
|
||||||
extract_tool,
|
extract_tool,
|
||||||
find_kit_dir,
|
find_kit_dir,
|
||||||
|
|
@ -35,16 +37,21 @@ from wk.os.win import (
|
||||||
OS_VERSION,
|
OS_VERSION,
|
||||||
activate_with_bios,
|
activate_with_bios,
|
||||||
check_4k_alignment,
|
check_4k_alignment,
|
||||||
get_installed_antivirus,
|
|
||||||
get_installed_ram,
|
get_installed_ram,
|
||||||
get_os_activation,
|
get_os_activation,
|
||||||
get_os_name,
|
get_os_name,
|
||||||
get_raw_disks,
|
get_raw_disks,
|
||||||
|
get_service_status,
|
||||||
get_volume_usage,
|
get_volume_usage,
|
||||||
is_activated,
|
is_activated,
|
||||||
is_secure_boot_enabled,
|
is_secure_boot_enabled,
|
||||||
|
list_installed_antivirus,
|
||||||
reg_set_value,
|
reg_set_value,
|
||||||
reg_write_settings,
|
reg_write_settings,
|
||||||
|
stop_service,
|
||||||
|
winget_check,
|
||||||
|
winget_import,
|
||||||
|
winget_upgrade,
|
||||||
)
|
)
|
||||||
from wk.repairs.win import (
|
from wk.repairs.win import (
|
||||||
WIDTH,
|
WIDTH,
|
||||||
|
|
@ -60,22 +67,10 @@ from wk.repairs.win import (
|
||||||
from wk.std import (
|
from wk.std import (
|
||||||
GenericError,
|
GenericError,
|
||||||
GenericWarning,
|
GenericWarning,
|
||||||
Menu,
|
|
||||||
TryAndPrint,
|
|
||||||
abort,
|
|
||||||
ask,
|
|
||||||
clear_screen,
|
|
||||||
color_string,
|
|
||||||
pause,
|
|
||||||
print_error,
|
|
||||||
print_info,
|
|
||||||
print_standard,
|
|
||||||
print_warning,
|
|
||||||
set_title,
|
|
||||||
show_data,
|
|
||||||
sleep,
|
sleep,
|
||||||
strip_colors,
|
|
||||||
)
|
)
|
||||||
|
from wk.ui import cli as ui
|
||||||
|
from wk.ui import ansi
|
||||||
|
|
||||||
|
|
||||||
# STATIC VARIABLES
|
# STATIC VARIABLES
|
||||||
|
|
@ -91,7 +86,7 @@ KNOWN_ENCODINGS = (
|
||||||
'utf-32-le',
|
'utf-32-le',
|
||||||
)
|
)
|
||||||
IN_CONEMU = 'ConEmuPID' in os.environ
|
IN_CONEMU = 'ConEmuPID' in os.environ
|
||||||
MENU_PRESETS = Menu()
|
MENU_PRESETS = ui.Menu()
|
||||||
PROGRAMFILES_32 = os.environ.get(
|
PROGRAMFILES_32 = os.environ.get(
|
||||||
'PROGRAMFILES(X86)', os.environ.get(
|
'PROGRAMFILES(X86)', os.environ.get(
|
||||||
'PROGRAMFILES', r'C:\Program Files (x86)',
|
'PROGRAMFILES', r'C:\Program Files (x86)',
|
||||||
|
|
@ -103,7 +98,7 @@ PROGRAMFILES_64 = os.environ.get(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
SYSTEMDRIVE = os.environ.get('SYSTEMDRIVE', 'C:')
|
SYSTEMDRIVE = os.environ.get('SYSTEMDRIVE', 'C:')
|
||||||
TRY_PRINT = TryAndPrint()
|
TRY_PRINT = ui.TryAndPrint()
|
||||||
TRY_PRINT.width = WIDTH
|
TRY_PRINT.width = WIDTH
|
||||||
TRY_PRINT.verbose = True
|
TRY_PRINT.verbose = True
|
||||||
for error in ('CalledProcessError', 'FileNotFoundError'):
|
for error in ('CalledProcessError', 'FileNotFoundError'):
|
||||||
|
|
@ -111,10 +106,10 @@ for error in ('CalledProcessError', 'FileNotFoundError'):
|
||||||
|
|
||||||
|
|
||||||
# Auto Setup
|
# Auto Setup
|
||||||
def build_menus(base_menus, title, presets):
|
def build_menus(base_menus, title, presets) -> dict[str, ui.Menu]:
|
||||||
"""Build menus, returns dict."""
|
"""Build menus, returns dict."""
|
||||||
menus = {}
|
menus = {}
|
||||||
menus['Main'] = Menu(title=f'{title}\n{color_string("Main Menu", "GREEN")}')
|
menus['Main'] = ui.Menu(title=f'{title}\n{ansi.color_string("Main Menu", "GREEN")}')
|
||||||
|
|
||||||
# Main Menu
|
# Main Menu
|
||||||
for entry in base_menus['Actions']:
|
for entry in base_menus['Actions']:
|
||||||
|
|
@ -124,7 +119,7 @@ def build_menus(base_menus, title, presets):
|
||||||
|
|
||||||
# Run groups
|
# Run groups
|
||||||
for group, entries in base_menus['Groups'].items():
|
for group, entries in base_menus['Groups'].items():
|
||||||
menus[group] = Menu(title=f'{title}\n{color_string(group, "GREEN")}')
|
menus[group] = ui.Menu(title=f'{title}\n{ansi.color_string(group, "GREEN")}')
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
menus[group].add_option(entry.name, entry.details)
|
menus[group].add_option(entry.name, entry.details)
|
||||||
menus[group].add_action('All')
|
menus[group].add_action('All')
|
||||||
|
|
@ -153,7 +148,7 @@ def build_menus(base_menus, title, presets):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update presets Menu
|
# Update presets Menu
|
||||||
MENU_PRESETS.title = f'{title}\n{color_string("Load Preset", "GREEN")}'
|
MENU_PRESETS.title = f'{title}\n{ansi.color_string("Load Preset", "GREEN")}'
|
||||||
MENU_PRESETS.add_option('Default')
|
MENU_PRESETS.add_option('Default')
|
||||||
for name in presets:
|
for name in presets:
|
||||||
MENU_PRESETS.add_option(name)
|
MENU_PRESETS.add_option(name)
|
||||||
|
|
@ -166,33 +161,33 @@ def build_menus(base_menus, title, presets):
|
||||||
return menus
|
return menus
|
||||||
|
|
||||||
|
|
||||||
def check_os_and_set_menu_title(title):
|
def check_os_and_set_menu_title(title) -> str:
|
||||||
"""Check OS version and update title for menus, returns str."""
|
"""Check OS version and update title for menus, returns str."""
|
||||||
color = None
|
color = None
|
||||||
os_name = get_os_name(check=False)
|
os_name = get_os_name(check=False)
|
||||||
print_standard(f'Operating System: {os_name}')
|
ui.print_standard(f'Operating System: {os_name}')
|
||||||
|
|
||||||
# Check support status and set color
|
# Check support status and set color
|
||||||
try:
|
try:
|
||||||
get_os_name()
|
get_os_name()
|
||||||
except GenericWarning:
|
except GenericWarning:
|
||||||
# Outdated version
|
# Outdated version
|
||||||
print_warning('OS version is outdated, updating is recommended.')
|
ui.print_warning('OS version is outdated, updating is recommended.')
|
||||||
if not ask('Continue anyway?'):
|
if not ui.ask('Continue anyway?'):
|
||||||
abort()
|
ui.abort()
|
||||||
color = 'YELLOW'
|
color = 'YELLOW'
|
||||||
except GenericError:
|
except GenericError:
|
||||||
# Unsupported version
|
# Unsupported version
|
||||||
print_error('OS version is unsupported, updating is recommended.')
|
ui.print_error('OS version is unsupported, updating is recommended.')
|
||||||
if not ask('Continue anyway? (NOT RECOMMENDED)'):
|
if not ui.ask('Continue anyway? (NOT RECOMMENDED)'):
|
||||||
abort()
|
ui.abort()
|
||||||
color = 'RED'
|
color = 'RED'
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return f'{title} ({color_string(os_name, color)})'
|
return f'{title} ({ansi.color_string(os_name, color)})'
|
||||||
|
|
||||||
|
|
||||||
def load_preset(menus, presets, title, enable_menu_exit=True):
|
def load_preset(menus, presets, title, enable_menu_exit=True) -> None:
|
||||||
"""Load menu settings from preset and ask selection question(s)."""
|
"""Load menu settings from preset and ask selection question(s)."""
|
||||||
if not enable_menu_exit:
|
if not enable_menu_exit:
|
||||||
MENU_PRESETS.actions['Main Menu'].update({'Disabled':True, 'Hidden':True})
|
MENU_PRESETS.actions['Main Menu'].update({'Disabled':True, 'Hidden':True})
|
||||||
|
|
@ -215,10 +210,10 @@ def load_preset(menus, presets, title, enable_menu_exit=True):
|
||||||
menu.options[name]['Selected'] = value
|
menu.options[name]['Selected'] = value
|
||||||
|
|
||||||
# Ask selection question(s)
|
# Ask selection question(s)
|
||||||
clear_screen()
|
ui.clear_screen()
|
||||||
print_standard(f'{title}')
|
ui.print_standard(f'{title}')
|
||||||
print('')
|
print('')
|
||||||
if selection[0] == 'Default' and ask('Install LibreOffice?'):
|
if selection[0] == 'Default' and ui.ask('Install LibreOffice?'):
|
||||||
menus['Install Software'].options['LibreOffice']['Selected'] = True
|
menus['Install Software'].options['LibreOffice']['Selected'] = True
|
||||||
|
|
||||||
# Re-enable Main Menu action if disabled
|
# Re-enable Main Menu action if disabled
|
||||||
|
|
@ -231,15 +226,15 @@ def load_preset(menus, presets, title, enable_menu_exit=True):
|
||||||
menus[group_name].options[entry_name]['Selected'] = False
|
menus[group_name].options[entry_name]['Selected'] = False
|
||||||
|
|
||||||
|
|
||||||
def run_auto_setup(base_menus, presets):
|
def run_auto_setup(base_menus, presets) -> None:
|
||||||
"""Run Auto Setup."""
|
"""Run Auto Setup."""
|
||||||
update_log_path(dest_name='Auto Setup', timestamp=True)
|
update_log_path(dest_name='Auto Setup', timestamp=True)
|
||||||
title = f'{KIT_NAME_FULL}: Auto Setup'
|
title = f'{KIT_NAME_FULL}: Auto Setup'
|
||||||
clear_screen()
|
ui.clear_screen()
|
||||||
set_title(title)
|
ui.set_title(title)
|
||||||
print_info(title)
|
ui.print_info(title)
|
||||||
print('')
|
print('')
|
||||||
print_standard('Initializing...')
|
ui.print_standard('Initializing...')
|
||||||
|
|
||||||
# Check OS and update title for menus
|
# Check OS and update title for menus
|
||||||
title = check_os_and_set_menu_title(title)
|
title = check_os_and_set_menu_title(title)
|
||||||
|
|
@ -254,10 +249,10 @@ def run_auto_setup(base_menus, presets):
|
||||||
show_main_menu(base_menus, menus, presets, title)
|
show_main_menu(base_menus, menus, presets, title)
|
||||||
|
|
||||||
# Start setup
|
# Start setup
|
||||||
clear_screen()
|
ui.clear_screen()
|
||||||
print_standard(title)
|
ui.print_standard(title)
|
||||||
print('')
|
print('')
|
||||||
print_info('Running setup')
|
ui.print_info('Running setup')
|
||||||
|
|
||||||
# Run setup
|
# Run setup
|
||||||
for group, menu in menus.items():
|
for group, menu in menus.items():
|
||||||
|
|
@ -266,29 +261,29 @@ def run_auto_setup(base_menus, presets):
|
||||||
try:
|
try:
|
||||||
run_group(group, menu)
|
run_group(group, menu)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
abort()
|
ui.abort()
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
print_info('Done')
|
ui.print_info('Done')
|
||||||
pause('Press Enter to exit...')
|
ui.pause('Press Enter to exit...')
|
||||||
|
|
||||||
|
|
||||||
def run_group(group, menu):
|
def run_group(group, menu) -> None:
|
||||||
"""Run entries in group if appropriate."""
|
"""Run entries in group if appropriate."""
|
||||||
print_info(f' {group}')
|
ui.print_info(f' {group}')
|
||||||
for name, details in menu.options.items():
|
for name, details in menu.options.items():
|
||||||
name_str = strip_colors(name)
|
name_str = ansi.strip_colors(name)
|
||||||
|
|
||||||
# Not selected
|
# Not selected
|
||||||
if not details.get('Selected', False):
|
if not details.get('Selected', False):
|
||||||
show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
ui.show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Selected
|
# Selected
|
||||||
details['Function']()
|
details['Function']()
|
||||||
|
|
||||||
|
|
||||||
def show_main_menu(base_menus, menus, presets, title):
|
def show_main_menu(base_menus, menus, presets, title) -> None:
|
||||||
"""Show main menu and handle actions."""
|
"""Show main menu and handle actions."""
|
||||||
while True:
|
while True:
|
||||||
update_main_menu(menus)
|
update_main_menu(menus)
|
||||||
|
|
@ -303,7 +298,7 @@ def show_main_menu(base_menus, menus, presets, title):
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
|
|
||||||
def show_sub_menu(menu):
|
def show_sub_menu(menu) -> None:
|
||||||
"""Show sub-menu and handle sub-menu actions."""
|
"""Show sub-menu and handle sub-menu actions."""
|
||||||
while True:
|
while True:
|
||||||
selection = menu.advanced_select()
|
selection = menu.advanced_select()
|
||||||
|
|
@ -319,7 +314,7 @@ def show_sub_menu(menu):
|
||||||
menu.options[name]['Selected'] = value
|
menu.options[name]['Selected'] = value
|
||||||
|
|
||||||
|
|
||||||
def update_main_menu(menus):
|
def update_main_menu(menus) -> None:
|
||||||
"""Update main menu based on current selections."""
|
"""Update main menu based on current selections."""
|
||||||
index = 1
|
index = 1
|
||||||
skip = 'Reboot'
|
skip = 'Reboot'
|
||||||
|
|
@ -338,37 +333,37 @@ def update_main_menu(menus):
|
||||||
|
|
||||||
|
|
||||||
# Auto Repairs: Wrapper Functions
|
# Auto Repairs: Wrapper Functions
|
||||||
def auto_backup_registry():
|
def auto_backup_registry() -> None:
|
||||||
"""Backup registry."""
|
"""Backup registry."""
|
||||||
TRY_PRINT.run('Backup Registry...', backup_registry)
|
TRY_PRINT.run('Backup Registry...', backup_registry)
|
||||||
|
|
||||||
|
|
||||||
def auto_backup_browser_profiles():
|
def auto_backup_browser_profiles() -> None:
|
||||||
"""Backup browser profiles."""
|
"""Backup browser profiles."""
|
||||||
backup_all_browser_profiles(use_try_print=True)
|
backup_all_browser_profiles(use_try_print=True)
|
||||||
|
|
||||||
|
|
||||||
def auto_backup_power_plans():
|
def auto_backup_power_plans() -> None:
|
||||||
"""Backup power plans."""
|
"""Backup power plans."""
|
||||||
TRY_PRINT.run('Backup Power Plans...', export_power_plans)
|
TRY_PRINT.run('Backup Power Plans...', export_power_plans)
|
||||||
|
|
||||||
|
|
||||||
def auto_reset_power_plans():
|
def auto_reset_power_plans() -> None:
|
||||||
"""Reset power plans."""
|
"""Reset power plans."""
|
||||||
TRY_PRINT.run('Reset Power Plans...', reset_power_plans)
|
TRY_PRINT.run('Reset Power Plans...', reset_power_plans)
|
||||||
|
|
||||||
|
|
||||||
def auto_set_custom_power_plan():
|
def auto_set_custom_power_plan() -> None:
|
||||||
"""Set custom power plan."""
|
"""Set custom power plan."""
|
||||||
TRY_PRINT.run('Set Custom Power Plan...', create_custom_power_plan)
|
TRY_PRINT.run('Set Custom Power Plan...', create_custom_power_plan)
|
||||||
|
|
||||||
|
|
||||||
def auto_enable_bsod_minidumps():
|
def auto_enable_bsod_minidumps() -> None:
|
||||||
"""Enable saving minidumps during BSoDs."""
|
"""Enable saving minidumps during BSoDs."""
|
||||||
TRY_PRINT.run('Enable BSoD mini dumps...', enable_bsod_minidumps)
|
TRY_PRINT.run('Enable BSoD mini dumps...', enable_bsod_minidumps)
|
||||||
|
|
||||||
|
|
||||||
def auto_enable_regback():
|
def auto_enable_regback() -> None:
|
||||||
"""Enable RegBack."""
|
"""Enable RegBack."""
|
||||||
TRY_PRINT.run(
|
TRY_PRINT.run(
|
||||||
'Enable RegBack...', reg_set_value, 'HKLM',
|
'Enable RegBack...', reg_set_value, 'HKLM',
|
||||||
|
|
@ -377,7 +372,7 @@ def auto_enable_regback():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def auto_system_restore_enable():
|
def auto_system_restore_enable() -> None:
|
||||||
"""Enable System Restore."""
|
"""Enable System Restore."""
|
||||||
cmd = [
|
cmd = [
|
||||||
'powershell', '-Command', 'Enable-ComputerRestore',
|
'powershell', '-Command', 'Enable-ComputerRestore',
|
||||||
|
|
@ -386,28 +381,28 @@ def auto_system_restore_enable():
|
||||||
TRY_PRINT.run('Enable System Restore...', run_program, cmd=cmd)
|
TRY_PRINT.run('Enable System Restore...', run_program, cmd=cmd)
|
||||||
|
|
||||||
|
|
||||||
def auto_system_restore_set_size():
|
def auto_system_restore_set_size() -> None:
|
||||||
"""Set System Restore size."""
|
"""Set System Restore size."""
|
||||||
TRY_PRINT.run('Set System Restore Size...', set_system_restore_size)
|
TRY_PRINT.run('Set System Restore Size...', set_system_restore_size)
|
||||||
|
|
||||||
|
|
||||||
def auto_system_restore_create():
|
def auto_system_restore_create() -> None:
|
||||||
"""Create System Restore point."""
|
"""Create System Restore point."""
|
||||||
TRY_PRINT.run('Create System Restore...', create_system_restore_point)
|
TRY_PRINT.run('Create System Restore...', create_system_restore_point)
|
||||||
|
|
||||||
|
|
||||||
def auto_windows_updates_enable():
|
def auto_windows_updates_enable() -> None:
|
||||||
"""Enable Windows Updates."""
|
"""Enable Windows Updates."""
|
||||||
TRY_PRINT.run('Enable Windows Updates...', enable_windows_updates)
|
TRY_PRINT.run('Enable Windows Updates...', enable_windows_updates)
|
||||||
|
|
||||||
|
|
||||||
# Auto Setup: Wrapper Functions
|
# Auto Setup: Wrapper Functions
|
||||||
def auto_activate_windows():
|
def auto_activate_windows() -> None:
|
||||||
"""Attempt to activate Windows using BIOS key."""
|
"""Attempt to activate Windows using BIOS key."""
|
||||||
TRY_PRINT.run('Windows Activation...', activate_with_bios)
|
TRY_PRINT.run('Windows Activation...', activate_with_bios)
|
||||||
|
|
||||||
|
|
||||||
def auto_config_browsers():
|
def auto_config_browsers() -> None:
|
||||||
"""Configure Browsers."""
|
"""Configure Browsers."""
|
||||||
prompt = ' Press Enter to continue...'
|
prompt = ' Press Enter to continue...'
|
||||||
TRY_PRINT.run('Chrome Notifications...', disable_chrome_notifications)
|
TRY_PRINT.run('Chrome Notifications...', disable_chrome_notifications)
|
||||||
|
|
@ -418,33 +413,38 @@ def auto_config_browsers():
|
||||||
'Set default browser...', set_default_browser, msg_good='STARTED',
|
'Set default browser...', set_default_browser, msg_good='STARTED',
|
||||||
)
|
)
|
||||||
print(prompt, end='', flush=True)
|
print(prompt, end='', flush=True)
|
||||||
pause('')
|
ui.pause(' ')
|
||||||
|
|
||||||
# Move cursor to beginning of the previous line and clear prompt
|
# Move cursor to beginning of the previous line and clear prompt
|
||||||
print(f'\033[F\r{" "*len(prompt)}\r', end='', flush=True)
|
print(f'\033[F\r{" "*len(prompt)}\r', end='', flush=True)
|
||||||
|
|
||||||
|
|
||||||
def auto_config_explorer():
|
def auto_config_explorer() -> None:
|
||||||
"""Configure Windows Explorer and restart the process."""
|
"""Configure Windows Explorer and restart the process."""
|
||||||
TRY_PRINT.run('Windows Explorer...', config_explorer)
|
TRY_PRINT.run('Windows Explorer...', config_explorer)
|
||||||
|
|
||||||
|
|
||||||
def auto_config_open_shell():
|
def auto_config_open_shell() -> None:
|
||||||
"""Configure Open Shell."""
|
"""Configure Open Shell."""
|
||||||
TRY_PRINT.run('Open Shell...', config_open_shell)
|
TRY_PRINT.run('Open Shell...', config_open_shell)
|
||||||
|
|
||||||
|
|
||||||
def auto_export_aida64_report():
|
def auto_disable_password_expiration() -> None:
|
||||||
|
"""Disable password expiration for all users."""
|
||||||
|
TRY_PRINT.run('Disable password expiration...', disable_password_expiration)
|
||||||
|
|
||||||
|
|
||||||
|
def auto_export_aida64_report() -> None:
|
||||||
"""Export AIDA64 reports."""
|
"""Export AIDA64 reports."""
|
||||||
TRY_PRINT.run('AIDA64 Report...', export_aida64_report)
|
TRY_PRINT.run('AIDA64 Report...', export_aida64_report)
|
||||||
|
|
||||||
|
|
||||||
def auto_install_firefox():
|
def auto_install_firefox() -> None:
|
||||||
"""Install Firefox."""
|
"""Install Firefox."""
|
||||||
TRY_PRINT.run('Firefox...', install_firefox)
|
TRY_PRINT.run('Firefox...', install_firefox)
|
||||||
|
|
||||||
|
|
||||||
def auto_install_libreoffice():
|
def auto_install_libreoffice() -> None:
|
||||||
"""Install LibreOffice.
|
"""Install LibreOffice.
|
||||||
|
|
||||||
NOTE: It is assumed that auto_install_vcredists() will be run
|
NOTE: It is assumed that auto_install_vcredists() will be run
|
||||||
|
|
@ -453,105 +453,120 @@ def auto_install_libreoffice():
|
||||||
TRY_PRINT.run('LibreOffice...', install_libreoffice, vcredist=False)
|
TRY_PRINT.run('LibreOffice...', install_libreoffice, vcredist=False)
|
||||||
|
|
||||||
|
|
||||||
def auto_install_open_shell():
|
def auto_install_open_shell() -> None:
|
||||||
"""Install Open Shell."""
|
"""Install Open Shell."""
|
||||||
TRY_PRINT.run('Open Shell...', install_open_shell)
|
TRY_PRINT.run('Open Shell...', install_open_shell)
|
||||||
|
|
||||||
|
|
||||||
def auto_install_software_bundle():
|
def auto_install_software_bundle() -> None:
|
||||||
"""Install standard software bundle."""
|
"""Install standard software bundle."""
|
||||||
TRY_PRINT.run('Software Bundle...', install_software_bundle)
|
TRY_PRINT.run('Software Bundle...', winget_import, group_name='default')
|
||||||
|
|
||||||
|
|
||||||
def auto_install_vcredists():
|
def auto_install_software_upgrades() -> None:
|
||||||
|
"""Upgrade all supported installed software."""
|
||||||
|
TRY_PRINT.run('Software Upgrades...', winget_upgrade)
|
||||||
|
|
||||||
|
|
||||||
|
def auto_install_vcredists() -> None:
|
||||||
"""Install latest supported Visual C++ runtimes."""
|
"""Install latest supported Visual C++ runtimes."""
|
||||||
TRY_PRINT.run('Visual C++ Runtimes...', install_vcredists)
|
TRY_PRINT.run('Visual C++ Runtimes...', winget_import, group_name='vcredists')
|
||||||
|
|
||||||
|
|
||||||
def auto_open_device_manager():
|
def auto_install_winget() -> None:
|
||||||
|
"""Install winget if needed."""
|
||||||
|
TRY_PRINT.run('Winget...', winget_check, raise_exceptions=True)
|
||||||
|
|
||||||
|
|
||||||
|
def auto_open_device_manager() -> None:
|
||||||
"""Open Device Manager."""
|
"""Open Device Manager."""
|
||||||
TRY_PRINT.run('Device Manager...', open_device_manager)
|
TRY_PRINT.run('Device Manager...', open_device_manager)
|
||||||
|
|
||||||
|
|
||||||
def auto_open_hwinfo_sensors():
|
def auto_open_hwinfo_sensors() -> None:
|
||||||
"""Open HWiNFO Sensors."""
|
"""Open HWiNFO Sensors."""
|
||||||
TRY_PRINT.run('HWiNFO Sensors...', open_hwinfo_sensors)
|
TRY_PRINT.run('HWiNFO Sensors...', open_hwinfo_sensors)
|
||||||
|
|
||||||
|
|
||||||
def auto_open_snappy_driver_installer_origin():
|
def auto_open_microsoft_store_updates() -> None:
|
||||||
|
"""Opem Microsoft Store Updates."""
|
||||||
|
TRY_PRINT.run('Microsoft Store Updates...', open_microsoft_store_updates)
|
||||||
|
|
||||||
|
|
||||||
|
def auto_open_snappy_driver_installer_origin() -> None:
|
||||||
"""Open Snappy Driver Installer Origin."""
|
"""Open Snappy Driver Installer Origin."""
|
||||||
TRY_PRINT.run('Snappy Driver Installer...', open_snappy_driver_installer_origin)
|
TRY_PRINT.run('Snappy Driver Installer...', open_snappy_driver_installer_origin)
|
||||||
|
|
||||||
|
|
||||||
def auto_open_windows_activation():
|
def auto_open_windows_activation() -> None:
|
||||||
"""Open Windows Activation."""
|
"""Open Windows Activation."""
|
||||||
if not is_activated():
|
if not is_activated():
|
||||||
TRY_PRINT.run('Windows Activation...', open_windows_activation)
|
TRY_PRINT.run('Windows Activation...', open_windows_activation)
|
||||||
|
|
||||||
|
|
||||||
def auto_open_windows_updates():
|
def auto_open_windows_updates() -> None:
|
||||||
"""Open Windows Updates."""
|
"""Open Windows Updates."""
|
||||||
TRY_PRINT.run('Windows Updates...', open_windows_updates)
|
TRY_PRINT.run('Windows Updates...', open_windows_updates)
|
||||||
|
|
||||||
|
|
||||||
def auto_open_xmplay():
|
def auto_open_xmplay() -> None:
|
||||||
"""Open XMPlay."""
|
"""Open XMPlay."""
|
||||||
TRY_PRINT.run('XMPlay...', open_xmplay)
|
TRY_PRINT.run('XMPlay...', open_xmplay)
|
||||||
|
|
||||||
|
|
||||||
def auto_show_4k_alignment_check():
|
def auto_show_4k_alignment_check() -> None:
|
||||||
"""Display 4K alignment check."""
|
"""Display 4K alignment check."""
|
||||||
TRY_PRINT.run('4K alignment Check...', check_4k_alignment, show_alert=True)
|
TRY_PRINT.run('4K alignment Check...', check_4k_alignment, show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
def auto_show_installed_antivirus():
|
def auto_show_installed_antivirus() -> None:
|
||||||
"""Display installed antivirus."""
|
"""Display installed antivirus."""
|
||||||
TRY_PRINT.run('Virus Protection...', get_installed_antivirus)
|
TRY_PRINT.run('Virus Protection...', list_installed_antivirus)
|
||||||
|
|
||||||
|
|
||||||
def auto_show_installed_ram():
|
def auto_show_installed_ram() -> None:
|
||||||
"""Display installed RAM."""
|
"""Display installed RAM."""
|
||||||
TRY_PRINT.run('Installed RAM...', get_installed_ram,
|
TRY_PRINT.run('Installed RAM...', get_installed_ram,
|
||||||
as_list=True, raise_exceptions=True,
|
as_list=True, raise_exceptions=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def auto_show_os_activation():
|
def auto_show_os_activation() -> None:
|
||||||
"""Display OS activation status."""
|
"""Display OS activation status."""
|
||||||
TRY_PRINT.run('Activation...', get_os_activation, as_list=True)
|
TRY_PRINT.run('Activation...', get_os_activation, as_list=True)
|
||||||
|
|
||||||
|
|
||||||
def auto_show_os_name():
|
def auto_show_os_name() -> None:
|
||||||
"""Display OS Name."""
|
"""Display OS Name."""
|
||||||
TRY_PRINT.run('Operating System...', get_os_name, as_list=True)
|
TRY_PRINT.run('Operating System...', get_os_name, as_list=True)
|
||||||
|
|
||||||
|
|
||||||
def auto_show_secure_boot_status():
|
def auto_show_secure_boot_status() -> None:
|
||||||
"""Display Secure Boot status."""
|
"""Display Secure Boot status."""
|
||||||
TRY_PRINT.run(
|
TRY_PRINT.run(
|
||||||
'Secure Boot...', check_secure_boot_status, msg_good='Enabled',
|
'Secure Boot...', check_secure_boot_status, msg_good='Enabled',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def auto_show_storage_status():
|
def auto_show_storage_status() -> None:
|
||||||
"""Display storage status."""
|
"""Display storage status."""
|
||||||
TRY_PRINT.run('Storage Status...', get_storage_status)
|
TRY_PRINT.run('Storage Status...', get_storage_status)
|
||||||
|
|
||||||
|
|
||||||
def auto_windows_temp_fix():
|
def auto_windows_temp_fix() -> None:
|
||||||
"""Restore default ACLs for Windows\\Temp."""
|
"""Restore default ACLs for Windows\\Temp."""
|
||||||
TRY_PRINT.run(r'Windows\Temp fix...', fix_windows_temp)
|
TRY_PRINT.run(r'Windows\Temp fix...', fix_windows_temp)
|
||||||
|
|
||||||
|
|
||||||
# Configure Functions
|
# Configure Functions
|
||||||
def config_explorer():
|
def config_explorer() -> None:
|
||||||
"""Configure Windows Explorer and restart the process."""
|
"""Configure Windows Explorer and restart the process."""
|
||||||
reg_write_settings(REG_WINDOWS_EXPLORER)
|
reg_write_settings(REG_WINDOWS_EXPLORER)
|
||||||
kill_procs('explorer.exe', force=True)
|
kill_procs('explorer.exe', force=True)
|
||||||
popen_program(['explorer.exe'])
|
popen_program(['explorer.exe'])
|
||||||
|
|
||||||
|
|
||||||
def config_open_shell():
|
def config_open_shell() -> None:
|
||||||
"""Configure Open Shell."""
|
"""Configure Open Shell."""
|
||||||
has_low_power_idle = False
|
has_low_power_idle = False
|
||||||
|
|
||||||
|
|
@ -571,7 +586,7 @@ def config_open_shell():
|
||||||
reg_write_settings(REG_OPEN_SHELL_LOW_POWER_IDLE)
|
reg_write_settings(REG_OPEN_SHELL_LOW_POWER_IDLE)
|
||||||
|
|
||||||
|
|
||||||
def disable_chrome_notifications():
|
def disable_chrome_notifications() -> None:
|
||||||
"""Disable notifications in Google Chrome."""
|
"""Disable notifications in Google Chrome."""
|
||||||
defaults_key = 'default_content_setting_values'
|
defaults_key = 'default_content_setting_values'
|
||||||
profiles = []
|
profiles = []
|
||||||
|
|
@ -613,13 +628,19 @@ def disable_chrome_notifications():
|
||||||
pref_file.write_text(json.dumps(pref_data, separators=(',', ':')))
|
pref_file.write_text(json.dumps(pref_data, separators=(',', ':')))
|
||||||
|
|
||||||
|
|
||||||
def enable_bsod_minidumps():
|
def disable_password_expiration() -> None:
|
||||||
"""Enable saving minidumps during BSoDs."""
|
"""Disable password expiration for all users."""
|
||||||
cmd = ['wmic', 'RECOVEROS', 'set', 'DebugInfoType', '=', '3']
|
script_path = find_kit_dir('Scripts').joinpath('disable_password_expiration.ps1')
|
||||||
|
cmd = ['PowerShell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def enable_ublock_origin():
|
def enable_bsod_minidumps() -> None:
|
||||||
|
"""Enable saving minidumps during BSoDs."""
|
||||||
|
reg_write_settings(REG_WINDOWS_BSOD_MINIDUMPS)
|
||||||
|
|
||||||
|
|
||||||
|
def enable_ublock_origin() -> None:
|
||||||
"""Enable uBlock Origin in supported browsers."""
|
"""Enable uBlock Origin in supported browsers."""
|
||||||
base_paths = [
|
base_paths = [
|
||||||
PROGRAMFILES_64, PROGRAMFILES_32, os.environ.get('LOCALAPPDATA'),
|
PROGRAMFILES_64, PROGRAMFILES_32, os.environ.get('LOCALAPPDATA'),
|
||||||
|
|
@ -646,10 +667,10 @@ def enable_ublock_origin():
|
||||||
|
|
||||||
# Open detected browsers
|
# Open detected browsers
|
||||||
for cmd in cmds:
|
for cmd in cmds:
|
||||||
popen_program(cmd)
|
popen_program(cmd, pipe=True)
|
||||||
|
|
||||||
|
|
||||||
def fix_windows_temp():
|
def fix_windows_temp() -> None:
|
||||||
"""Restore default permissions for Windows\\Temp."""
|
"""Restore default permissions for Windows\\Temp."""
|
||||||
permissions = (
|
permissions = (
|
||||||
'Users:(CI)(X,WD,AD)',
|
'Users:(CI)(X,WD,AD)',
|
||||||
|
|
@ -661,7 +682,7 @@ def fix_windows_temp():
|
||||||
|
|
||||||
|
|
||||||
# Install Functions
|
# Install Functions
|
||||||
def install_firefox():
|
def install_firefox() -> None:
|
||||||
"""Install Firefox.
|
"""Install Firefox.
|
||||||
|
|
||||||
As far as I can tell if you use the EXE installers then it will use
|
As far as I can tell if you use the EXE installers then it will use
|
||||||
|
|
@ -766,12 +787,12 @@ def install_libreoffice(
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def install_open_shell():
|
def install_open_shell() -> None:
|
||||||
"""Install Open Shell (just the Start Menu)."""
|
"""Install Open Shell (just the Start Menu)."""
|
||||||
skin_zip = get_tool_path('OpenShell', 'Fluent-Metro', suffix='zip')
|
skin_zip = get_tool_path('OpenShell', 'Fluent-Metro', suffix='zip')
|
||||||
|
|
||||||
# Bail early
|
# Bail early
|
||||||
if OS_VERSION != 10:
|
if OS_VERSION < 10:
|
||||||
raise GenericWarning('Unsupported OS')
|
raise GenericWarning('Unsupported OS')
|
||||||
|
|
||||||
# Install OpenShell
|
# Install OpenShell
|
||||||
|
|
@ -796,49 +817,7 @@ def install_open_shell():
|
||||||
run_program(cmd)
|
run_program(cmd)
|
||||||
|
|
||||||
|
|
||||||
def install_software_bundle():
|
def uninstall_firefox() -> None:
|
||||||
"""Install standard software bundle."""
|
|
||||||
download_tool('Ninite', 'Software Bundle')
|
|
||||||
installer = get_tool_path('Ninite', 'Software Bundle')
|
|
||||||
msg = 'Waiting for installations to finish...'
|
|
||||||
warning = 'NOTE: Press CTRL+c to manually resume if it gets stuck...'
|
|
||||||
|
|
||||||
# Start installations and wait for them to finish
|
|
||||||
print_standard(msg)
|
|
||||||
print_warning(warning, end='', flush=True)
|
|
||||||
proc = popen_program([installer])
|
|
||||||
try:
|
|
||||||
proc.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
# Assuming user-forced continue
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Clear info lines
|
|
||||||
print(
|
|
||||||
'\r\033[0K' # Cursor to start of current line and clear to end of line
|
|
||||||
'\033[F\033[54C' # Cursor to start of prev line and then move 54 right
|
|
||||||
'\033[0K', # Clear from cursor to end of line
|
|
||||||
end='', flush=True)
|
|
||||||
|
|
||||||
|
|
||||||
def install_vcredists():
|
|
||||||
"""Install latest supported Visual C++ runtimes."""
|
|
||||||
for year in (2012, 2013, 2022):
|
|
||||||
cmd_args = ['/install', '/passive', '/norestart']
|
|
||||||
if year == 2012:
|
|
||||||
cmd_args.pop(0)
|
|
||||||
name = f'VCRedist_{year}_x32'
|
|
||||||
download_tool('VCRedist', name)
|
|
||||||
installer = get_tool_path('VCRedist', name)
|
|
||||||
run_program([installer, *cmd_args])
|
|
||||||
if ARCH == '64':
|
|
||||||
name = f'{name[:-2]}64'
|
|
||||||
download_tool('VCRedist', name)
|
|
||||||
installer = get_tool_path('VCRedist', name)
|
|
||||||
run_program([installer, *cmd_args])
|
|
||||||
|
|
||||||
|
|
||||||
def uninstall_firefox():
|
|
||||||
"""Uninstall all copies of Firefox."""
|
"""Uninstall all copies of Firefox."""
|
||||||
json_file = format_log_path(log_name='Installed Programs', timestamp=True)
|
json_file = format_log_path(log_name='Installed Programs', timestamp=True)
|
||||||
json_file = json_file.with_name(f'{json_file.stem}.json')
|
json_file = json_file.with_name(f'{json_file.stem}.json')
|
||||||
|
|
@ -859,13 +838,14 @@ def uninstall_firefox():
|
||||||
|
|
||||||
|
|
||||||
# Misc Functions
|
# Misc Functions
|
||||||
def check_secure_boot_status():
|
def check_secure_boot_status() -> None:
|
||||||
"""Check Secure Boot status."""
|
"""Check Secure Boot status."""
|
||||||
is_secure_boot_enabled(raise_exceptions=True, show_alert=True)
|
is_secure_boot_enabled(raise_exceptions=True, show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
def get_firefox_default_profile(profiles_ini):
|
def get_firefox_default_profile(profiles_ini) -> Any:
|
||||||
"""Get Firefox default profile, returns(pathlib.Path, encoding) or None."""
|
"""Get Firefox default profile, returns(pathlib.Path, encoding) or None."""
|
||||||
|
# TODO: Refactor to remove dependancy on Any
|
||||||
default_profile = None
|
default_profile = None
|
||||||
encoding = None
|
encoding = None
|
||||||
parser = None
|
parser = None
|
||||||
|
|
@ -902,24 +882,24 @@ def get_firefox_default_profile(profiles_ini):
|
||||||
return (default_profile, encoding)
|
return (default_profile, encoding)
|
||||||
|
|
||||||
|
|
||||||
def get_storage_status():
|
def get_storage_status() -> list[str]:
|
||||||
"""Get storage status for fixed disks, returns list."""
|
"""Get storage status for fixed disks, returns list."""
|
||||||
report = get_volume_usage(use_colors=True)
|
report = get_volume_usage(use_colors=True)
|
||||||
for disk in get_raw_disks():
|
for disk in get_raw_disks():
|
||||||
report.append(color_string(f'Uninitialized Disk: {disk}', 'RED'))
|
report.append(ansi.color_string(f'Uninitialized Disk: {disk}', 'RED'))
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
def set_default_browser():
|
def set_default_browser() -> None:
|
||||||
"""Open Windows Settings to the default apps section."""
|
"""Open Windows Settings to the default apps section."""
|
||||||
cmd = ['start', '', 'ms-settings:defaultapps']
|
cmd = ['start', '', 'ms-settings:defaultapps']
|
||||||
popen_program(cmd, shell=True)
|
popen_program(cmd, shell=True)
|
||||||
|
|
||||||
|
|
||||||
# Tool Functions
|
# Tool Functions
|
||||||
def export_aida64_report():
|
def export_aida64_report() -> None:
|
||||||
"""Export AIDA64 report."""
|
"""Export AIDA64 report."""
|
||||||
report_path = format_log_path(
|
report_path = format_log_path(
|
||||||
log_name='AIDA64 System Report',
|
log_name='AIDA64 System Report',
|
||||||
|
|
@ -940,12 +920,12 @@ def export_aida64_report():
|
||||||
raise GenericError('Error(s) encountered exporting report.')
|
raise GenericError('Error(s) encountered exporting report.')
|
||||||
|
|
||||||
|
|
||||||
def open_device_manager():
|
def open_device_manager() -> None:
|
||||||
"""Open Device Manager."""
|
"""Open Device Manager."""
|
||||||
popen_program(['mmc', 'devmgmt.msc'])
|
popen_program(['mmc', 'devmgmt.msc'])
|
||||||
|
|
||||||
|
|
||||||
def open_hwinfo_sensors():
|
def open_hwinfo_sensors() -> None:
|
||||||
"""Open HWiNFO sensors."""
|
"""Open HWiNFO sensors."""
|
||||||
hwinfo_path = get_tool_path('HWiNFO', 'HWiNFO')
|
hwinfo_path = get_tool_path('HWiNFO', 'HWiNFO')
|
||||||
base_config = hwinfo_path.with_name('general.ini')
|
base_config = hwinfo_path.with_name('general.ini')
|
||||||
|
|
@ -961,22 +941,33 @@ def open_hwinfo_sensors():
|
||||||
run_tool('HWiNFO', 'HWiNFO', popen=True)
|
run_tool('HWiNFO', 'HWiNFO', popen=True)
|
||||||
|
|
||||||
|
|
||||||
def open_snappy_driver_installer_origin():
|
def open_microsoft_store_updates() -> None:
|
||||||
|
"""Open Microsoft Store to the updates page."""
|
||||||
|
popen_program(['explorer', 'ms-windows-store:updates'])
|
||||||
|
|
||||||
|
|
||||||
|
def open_snappy_driver_installer_origin() -> None:
|
||||||
"""Open Snappy Driver Installer Origin."""
|
"""Open Snappy Driver Installer Origin."""
|
||||||
|
if OS_VERSION == 11:
|
||||||
|
appid_services = ['appid', 'appidsvc', 'applockerfltr']
|
||||||
|
for svc in appid_services:
|
||||||
|
stop_service(svc)
|
||||||
|
if any([get_service_status(s) != 'stopped' for s in appid_services]):
|
||||||
|
raise GenericWarning('Failed to stop AppID services')
|
||||||
run_tool('SDIO', 'SDIO', cwd=True, pipe=True, popen=True)
|
run_tool('SDIO', 'SDIO', cwd=True, pipe=True, popen=True)
|
||||||
|
|
||||||
|
|
||||||
def open_windows_activation():
|
def open_windows_activation() -> None:
|
||||||
"""Open Windows Activation."""
|
"""Open Windows Activation."""
|
||||||
popen_program(['slui'])
|
popen_program(['slui'])
|
||||||
|
|
||||||
|
|
||||||
def open_windows_updates():
|
def open_windows_updates() -> None:
|
||||||
"""Open Windows Updates."""
|
"""Open Windows Updates."""
|
||||||
popen_program(['control', '/name', 'Microsoft.WindowsUpdate'])
|
popen_program(['control', '/name', 'Microsoft.WindowsUpdate'])
|
||||||
|
|
||||||
|
|
||||||
def open_xmplay():
|
def open_xmplay() -> None:
|
||||||
"""Open XMPlay."""
|
"""Open XMPlay."""
|
||||||
sleep(2)
|
sleep(2)
|
||||||
run_tool('XMPlay', 'XMPlay', 'music.7z', cwd=True, popen=True)
|
run_tool('XMPlay', 'XMPlay', 'music.7z', cwd=True, popen=True)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
6
scripts/wk/ui/__init__.py
Normal file
6
scripts/wk/ui/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""WizardKit: ui module init"""
|
||||||
|
|
||||||
|
from . import ansi
|
||||||
|
from . import cli
|
||||||
|
from . import tmux
|
||||||
|
from . import tui
|
||||||
70
scripts/wk/ui/ansi.py
Normal file
70
scripts/wk/ui/ansi.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""WizardKit: ANSI control/escape functions"""
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
COLORS = {
|
||||||
|
'CLEAR': '\033[0m',
|
||||||
|
'RED': '\033[31m',
|
||||||
|
'RED_BLINK': '\033[31;5m',
|
||||||
|
'ORANGE': '\033[31;1m',
|
||||||
|
'ORANGE_RED': '\033[1;31;41m',
|
||||||
|
'GREEN': '\033[32m',
|
||||||
|
'YELLOW': '\033[33m',
|
||||||
|
'YELLOW_BLINK': '\033[33;5m',
|
||||||
|
'BLUE': '\033[34m',
|
||||||
|
'PURPLE': '\033[35m',
|
||||||
|
'CYAN': '\033[36m',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def clear_screen() -> None:
|
||||||
|
"""Clear screen using ANSI escape."""
|
||||||
|
print('\033c', end='', flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def color_string(
|
||||||
|
strings: Iterable[str] | str,
|
||||||
|
colors: Iterable[str | None] | str,
|
||||||
|
sep=' ',
|
||||||
|
) -> str:
|
||||||
|
"""Build colored string using ANSI escapes, returns str."""
|
||||||
|
data = {'strings': strings, 'colors': colors}
|
||||||
|
msg = []
|
||||||
|
|
||||||
|
# Convert input to tuples of strings
|
||||||
|
for k, v in data.items():
|
||||||
|
if isinstance(v, str):
|
||||||
|
# Avoid splitting string into a list of characters
|
||||||
|
data[k] = (v,)
|
||||||
|
try:
|
||||||
|
iter(v)
|
||||||
|
except TypeError:
|
||||||
|
# Assuming single element passed, convert to string
|
||||||
|
data[k] = (str(v),)
|
||||||
|
|
||||||
|
# Build new string with color escapes added
|
||||||
|
for string, color in itertools.zip_longest(data['strings'], data['colors']):
|
||||||
|
color_code = COLORS.get(str(color), COLORS['CLEAR'])
|
||||||
|
msg.append(f'{color_code}{string}{COLORS["CLEAR"]}')
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return sep.join(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_colors(string: str) -> str:
|
||||||
|
"""Strip known ANSI color escapes from string, returns str."""
|
||||||
|
LOG.debug('string: %s', string)
|
||||||
|
for color in COLORS.values():
|
||||||
|
string = string.replace(color, '')
|
||||||
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
924
scripts/wk/ui/cli.py
Normal file
924
scripts/wk/ui/cli.py
Normal file
|
|
@ -0,0 +1,924 @@
|
||||||
|
"""WizardKit: CLI functions"""
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from typing import Any, Callable, Iterable
|
||||||
|
|
||||||
|
from prompt_toolkit import prompt
|
||||||
|
from prompt_toolkit.document import Document
|
||||||
|
from prompt_toolkit.validation import Validator, ValidationError
|
||||||
|
|
||||||
|
try:
|
||||||
|
from functools import cache
|
||||||
|
except ImportError:
|
||||||
|
# Assuming Python is < 3.9
|
||||||
|
from functools import lru_cache as cache
|
||||||
|
|
||||||
|
from wk.cfg.main import (
|
||||||
|
ENABLED_UPLOAD_DATA,
|
||||||
|
INDENT,
|
||||||
|
SUPPORT_MESSAGE,
|
||||||
|
WIDTH,
|
||||||
|
)
|
||||||
|
from wk.std import (sleep, GenericWarning)
|
||||||
|
from wk.ui.ansi import clear_screen, color_string, strip_colors
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
PLATFORM = platform.system()
|
||||||
|
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
class InputChoiceValidator(Validator):
|
||||||
|
"""Validate that input is one of the provided choices."""
|
||||||
|
def __init__(self, choices: Iterable[str], allow_empty: bool = False):
|
||||||
|
self.allow_empty: bool = allow_empty
|
||||||
|
self.choices: list[str] = [str(c).upper() for c in choices]
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def validate(self, document: Document) -> None:
|
||||||
|
text = document.text
|
||||||
|
if not (text or self.allow_empty):
|
||||||
|
raise ValidationError(
|
||||||
|
message='This input is required!',
|
||||||
|
cursor_position=len(text),
|
||||||
|
)
|
||||||
|
if text and text.upper() not in self.choices:
|
||||||
|
raise ValidationError(
|
||||||
|
message='Invalid selection',
|
||||||
|
cursor_position=len(text),
|
||||||
|
)
|
||||||
|
|
||||||
|
class InputNotEmptyValidator(Validator):
|
||||||
|
"""Validate that input is not empty."""
|
||||||
|
def validate(self, document: Document) -> None:
|
||||||
|
text = document.text
|
||||||
|
if not text:
|
||||||
|
raise ValidationError(
|
||||||
|
message='This input is required!',
|
||||||
|
cursor_position=len(text),
|
||||||
|
)
|
||||||
|
|
||||||
|
class InputTicketIDValidator(Validator):
|
||||||
|
"""Validate that input resembles a ticket ID."""
|
||||||
|
def __init__(self, allow_empty: bool = False):
|
||||||
|
self.allow_empty: bool = allow_empty
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def validate(self, document: Document) -> None:
|
||||||
|
text = document.text
|
||||||
|
if not (text or self.allow_empty):
|
||||||
|
raise ValidationError(
|
||||||
|
message='This input is required!',
|
||||||
|
cursor_position=len(text),
|
||||||
|
)
|
||||||
|
if text and not re.match(r'^\d', text):
|
||||||
|
raise ValidationError(
|
||||||
|
message='Ticket ID should start with a number!',
|
||||||
|
cursor_position=len(text),
|
||||||
|
)
|
||||||
|
|
||||||
|
class InputYesNoValidator(Validator):
|
||||||
|
"""Validate that input is a yes or no."""
|
||||||
|
def __init__(self, allow_empty: bool = False):
|
||||||
|
self.allow_empty: bool = allow_empty
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def validate(self, document: Document) -> None:
|
||||||
|
text = document.text
|
||||||
|
if not (text or self.allow_empty):
|
||||||
|
raise ValidationError(
|
||||||
|
message='This input is required!',
|
||||||
|
cursor_position=len(text),
|
||||||
|
)
|
||||||
|
if text and not re.match(r'^(y(es|up|)|n(o|ope|))$', text, re.IGNORECASE):
|
||||||
|
raise ValidationError(
|
||||||
|
message='Please answer "yes" or "no"',
|
||||||
|
cursor_position=len(text),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Menu():
|
||||||
|
"""Object for tracking menu specific data and methods.
|
||||||
|
|
||||||
|
ASSUMPTIONS:
|
||||||
|
1. All entry names are unique.
|
||||||
|
2. All action entry names start with different letters.
|
||||||
|
"""
|
||||||
|
def __init__(self, title: str = '[Untitled Menu]'):
|
||||||
|
self.actions: dict[str, dict[Any, Any]] = {}
|
||||||
|
self.options: dict[str, dict[Any, Any]] = {}
|
||||||
|
self.sets: dict[str, dict[Any, Any]] = {}
|
||||||
|
self.toggles: dict[str, dict[Any, Any]] = {}
|
||||||
|
self.disabled_str: str = 'Disabled'
|
||||||
|
self.separator: str = '─'
|
||||||
|
self.title: str = title
|
||||||
|
|
||||||
|
def _generate_menu_text(self) -> str:
|
||||||
|
"""Generate menu text, returns str."""
|
||||||
|
separator_string = self._get_separator_string()
|
||||||
|
menu_lines = [self.title, separator_string] if self.title else []
|
||||||
|
|
||||||
|
# Sets & toggles
|
||||||
|
for section in (self.sets, self.toggles):
|
||||||
|
for details in section.values():
|
||||||
|
if details.get('Hidden', False):
|
||||||
|
continue
|
||||||
|
if details.get('Separator', False):
|
||||||
|
menu_lines.append(separator_string)
|
||||||
|
menu_lines.append(details['Display Name'])
|
||||||
|
if self.sets or self.toggles:
|
||||||
|
menu_lines.append(separator_string)
|
||||||
|
|
||||||
|
# Options
|
||||||
|
for details in self.options.values():
|
||||||
|
if details.get('Hidden', False):
|
||||||
|
continue
|
||||||
|
if details.get('Separator', False):
|
||||||
|
menu_lines.append(separator_string)
|
||||||
|
menu_lines.append(details['Display Name'])
|
||||||
|
if self.options:
|
||||||
|
menu_lines.append(separator_string)
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
for details in self.actions.values():
|
||||||
|
if details.get('Hidden', False):
|
||||||
|
continue
|
||||||
|
if details.get('Separator', False):
|
||||||
|
menu_lines.append(separator_string)
|
||||||
|
menu_lines.append(details['Display Name'])
|
||||||
|
|
||||||
|
# Show menu
|
||||||
|
menu_lines.append('')
|
||||||
|
menu_lines = [str(line) for line in menu_lines]
|
||||||
|
return '\n'.join(menu_lines)
|
||||||
|
|
||||||
|
def _get_display_name(
|
||||||
|
self, name, details,
|
||||||
|
index=None, no_checkboxes=True, setting_item=False) -> str:
|
||||||
|
"""Format display name based on details and args, returns str."""
|
||||||
|
disabled = details.get('Disabled', False)
|
||||||
|
if setting_item and not details['Selected']:
|
||||||
|
# Display item in YELLOW
|
||||||
|
disabled = True
|
||||||
|
checkmark = '*'
|
||||||
|
if 'CONEMUPID' in os.environ or 'DISPLAY' in os.environ or PLATFORM == 'Darwin':
|
||||||
|
checkmark = '✓'
|
||||||
|
display_name = f'{index if index else name[:1].upper()}: '
|
||||||
|
if not (index and index >= 10):
|
||||||
|
display_name = f' {display_name}'
|
||||||
|
if setting_item and 'Value' in details:
|
||||||
|
name = f'{name} = {details["Value"]}'
|
||||||
|
|
||||||
|
# Add enabled status if necessary
|
||||||
|
if not no_checkboxes:
|
||||||
|
display_name += f'[{checkmark if details["Selected"] else " "}] '
|
||||||
|
|
||||||
|
# Add name
|
||||||
|
if disabled:
|
||||||
|
display_name += color_string(f'{name} ({self.disabled_str})', 'YELLOW')
|
||||||
|
else:
|
||||||
|
display_name += name
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return display_name
|
||||||
|
|
||||||
|
def _get_separator_string(self) -> str:
|
||||||
|
"""Format separator length based on name lengths, returns str."""
|
||||||
|
separator_length = 0
|
||||||
|
|
||||||
|
# Check title line(s)
|
||||||
|
if self.title:
|
||||||
|
for line in self.title.split('\n'):
|
||||||
|
separator_length = max(separator_length, len(strip_colors(line)))
|
||||||
|
|
||||||
|
# Loop over all item names
|
||||||
|
for section in (self.actions, self.options, self.sets, self.toggles):
|
||||||
|
for details in section.values():
|
||||||
|
if details.get('Hidden', False):
|
||||||
|
# Skip hidden lines
|
||||||
|
continue
|
||||||
|
line = strip_colors(details['Display Name'])
|
||||||
|
separator_length = max(separator_length, len(line))
|
||||||
|
separator_length += 1
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return self.separator * separator_length
|
||||||
|
|
||||||
|
def _get_valid_answers(self) -> list[str]:
|
||||||
|
"""Get valid answers based on menu items, returns list."""
|
||||||
|
valid_answers = []
|
||||||
|
|
||||||
|
# Numbered items
|
||||||
|
index = 0
|
||||||
|
for section in (self.sets, self.toggles, self.options):
|
||||||
|
for details in section.values():
|
||||||
|
if details.get('Hidden', False):
|
||||||
|
# Don't increment index or add to valid_answers
|
||||||
|
continue
|
||||||
|
index += 1
|
||||||
|
if not details.get('Disabled', False):
|
||||||
|
valid_answers.append(str(index))
|
||||||
|
|
||||||
|
# Action items
|
||||||
|
for name, details in self.actions.items():
|
||||||
|
if not details.get('Disabled', False):
|
||||||
|
valid_answers.append(name[:1].upper())
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return valid_answers
|
||||||
|
|
||||||
|
def _resolve_selection(self, selection: str) -> tuple[str, dict[Any, Any]]:
|
||||||
|
"""Get menu item based on user selection, returns tuple."""
|
||||||
|
offset = 1
|
||||||
|
resolved_selection = tuple()
|
||||||
|
if selection.isnumeric():
|
||||||
|
# Enumerate over numbered entries
|
||||||
|
entries = [
|
||||||
|
*self.sets.items(),
|
||||||
|
*self.toggles.items(),
|
||||||
|
*self.options.items(),
|
||||||
|
]
|
||||||
|
for _i, details in enumerate(entries):
|
||||||
|
if details[1].get('Hidden', False):
|
||||||
|
offset -= 1
|
||||||
|
elif str(_i+offset) == selection:
|
||||||
|
# TODO: Fix this typo!
|
||||||
|
# It was discovered after being in production for SEVERAL YEARS!
|
||||||
|
# Extra testing is needed to verify any calls to this function still
|
||||||
|
# depend on this functionality
|
||||||
|
resolved_selection = (details)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Just check actions
|
||||||
|
for action, details in self.actions.items():
|
||||||
|
if action.lower().startswith(selection.lower()):
|
||||||
|
resolved_selection = (action, details)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return resolved_selection
|
||||||
|
|
||||||
|
def _update(self, single_selection: bool = True, settings_mode: bool = False) -> None:
|
||||||
|
"""Update menu items in preparation for printing to screen."""
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
# Fix selection status for sets
|
||||||
|
for set_details in self.sets.values():
|
||||||
|
set_selected = True
|
||||||
|
set_targets = set_details['Targets']
|
||||||
|
for option, option_details in self.options.items():
|
||||||
|
if option in set_targets and not option_details['Selected']:
|
||||||
|
set_selected = False
|
||||||
|
elif option not in set_targets and option_details['Selected']:
|
||||||
|
set_selected = False
|
||||||
|
set_details['Selected'] = set_selected
|
||||||
|
|
||||||
|
# Numbered sections
|
||||||
|
for section in (self.sets, self.toggles, self.options):
|
||||||
|
for name, details in section.items():
|
||||||
|
if details.get('Hidden', False):
|
||||||
|
# Skip hidden lines and don't increment index
|
||||||
|
continue
|
||||||
|
index += 1
|
||||||
|
details['Display Name'] = self._get_display_name(
|
||||||
|
name,
|
||||||
|
details,
|
||||||
|
index=index,
|
||||||
|
no_checkboxes=single_selection,
|
||||||
|
setting_item=settings_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
for name, details in self.actions.items():
|
||||||
|
details['Display Name'] = self._get_display_name(
|
||||||
|
name,
|
||||||
|
details,
|
||||||
|
no_checkboxes=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _update_entry_selection_status(
|
||||||
|
self, entry: str, toggle: bool = True, status: bool = False) -> None:
|
||||||
|
"""Update entry selection status either directly or by toggling."""
|
||||||
|
if entry in self.sets:
|
||||||
|
# Update targets not the set itself
|
||||||
|
new_status = not self.sets[entry]['Selected'] if toggle else status
|
||||||
|
targets = self.sets[entry]['Targets']
|
||||||
|
self._update_set_selection_status(targets, new_status)
|
||||||
|
for section in (self.toggles, self.options, self.actions):
|
||||||
|
if entry in section:
|
||||||
|
if toggle:
|
||||||
|
section[entry]['Selected'] = not section[entry]['Selected']
|
||||||
|
else:
|
||||||
|
section[entry]['Selected'] = status
|
||||||
|
|
||||||
|
def _update_set_selection_status(self, targets: Iterable[str], status: bool) -> None:
|
||||||
|
"""Select or deselect options based on targets and status."""
|
||||||
|
for option, details in self.options.items():
|
||||||
|
# If (new) status is True and this option is a target then select
|
||||||
|
# Otherwise deselect
|
||||||
|
details['Selected'] = status and option in targets
|
||||||
|
|
||||||
|
def _user_select(self, prompt_msg: str) -> str:
|
||||||
|
"""Show menu and select an entry, returns str."""
|
||||||
|
menu_text = self._generate_menu_text()
|
||||||
|
valid_answers = self._get_valid_answers()
|
||||||
|
|
||||||
|
# Menu loop
|
||||||
|
while True:
|
||||||
|
clear_screen()
|
||||||
|
print(menu_text)
|
||||||
|
sleep(0.01)
|
||||||
|
answer = input_text(prompt_msg).strip()
|
||||||
|
if answer.upper() in valid_answers:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def add_action(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
|
"""Add action to menu."""
|
||||||
|
details = details if details else {}
|
||||||
|
details['Selected'] = details.get('Selected', False)
|
||||||
|
self.actions[name] = details
|
||||||
|
|
||||||
|
def add_option(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
|
"""Add option to menu."""
|
||||||
|
details = details if details else {}
|
||||||
|
details['Selected'] = details.get('Selected', False)
|
||||||
|
self.options[name] = details
|
||||||
|
|
||||||
|
def add_set(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
|
"""Add set to menu."""
|
||||||
|
details = details if details else {}
|
||||||
|
details['Selected'] = details.get('Selected', False)
|
||||||
|
|
||||||
|
# Safety check
|
||||||
|
if 'Targets' not in details:
|
||||||
|
raise KeyError('Menu set has no targets')
|
||||||
|
|
||||||
|
# Add set
|
||||||
|
self.sets[name] = details
|
||||||
|
|
||||||
|
def add_toggle(self, name: str, details: dict[Any, Any] | None = None) -> None:
|
||||||
|
"""Add toggle to menu."""
|
||||||
|
details = details if details else {}
|
||||||
|
details['Selected'] = details.get('Selected', False)
|
||||||
|
self.toggles[name] = details
|
||||||
|
|
||||||
|
def advanced_select(
|
||||||
|
self,
|
||||||
|
prompt_msg: str = 'Please make a selection: ',
|
||||||
|
) -> tuple[str, dict[Any, Any]]:
|
||||||
|
"""Display menu and make multiple selections, returns tuple.
|
||||||
|
|
||||||
|
NOTE: Menu is displayed until an action entry is selected.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
self._update(single_selection=False)
|
||||||
|
user_selection = self._user_select(prompt_msg)
|
||||||
|
selected_entry = self._resolve_selection(user_selection)
|
||||||
|
if user_selection.isnumeric():
|
||||||
|
# Update selection(s)
|
||||||
|
self._update_entry_selection_status(selected_entry[0])
|
||||||
|
else:
|
||||||
|
# Action selected
|
||||||
|
break
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return selected_entry
|
||||||
|
|
||||||
|
def settings_select(
|
||||||
|
self,
|
||||||
|
prompt_msg: str = 'Please make a selection: ',
|
||||||
|
) -> tuple[str, dict[Any, Any]]:
|
||||||
|
"""Display menu and make multiple selections, returns tuple.
|
||||||
|
|
||||||
|
NOTE: Menu is displayed until an action entry is selected.
|
||||||
|
"""
|
||||||
|
choice_kwargs = {
|
||||||
|
'prompt_msg': 'Toggle or change value?',
|
||||||
|
'choices': ['T', 'C'],
|
||||||
|
}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self._update(single_selection=True, settings_mode=True)
|
||||||
|
user_selection = self._user_select(prompt_msg)
|
||||||
|
selected_entry = self._resolve_selection(user_selection)
|
||||||
|
if user_selection.isnumeric():
|
||||||
|
if 'Value' in selected_entry[-1] and choice(**choice_kwargs) == 'C':
|
||||||
|
# Change
|
||||||
|
selected_entry[-1]['Value'] = input_text('Enter new value: ')
|
||||||
|
else:
|
||||||
|
# Toggle
|
||||||
|
self._update_entry_selection_status(selected_entry[0])
|
||||||
|
else:
|
||||||
|
# Action selected
|
||||||
|
break
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return selected_entry
|
||||||
|
|
||||||
|
def simple_select(
|
||||||
|
self,
|
||||||
|
prompt_msg: str = 'Please make a selection: ',
|
||||||
|
update: bool = True,
|
||||||
|
) -> tuple[str, dict[Any, Any]]:
|
||||||
|
"""Display menu and make a single selection, returns tuple."""
|
||||||
|
if update:
|
||||||
|
self._update()
|
||||||
|
user_selection = self._user_select(prompt_msg)
|
||||||
|
return self._resolve_selection(user_selection)
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
"""Update menu with default settings."""
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
class TryAndPrint():
|
||||||
|
"""Object used to standardize running functions and returning the result.
|
||||||
|
|
||||||
|
The errors and warning attributes are used to allow fine-tuned results
|
||||||
|
based on exception names.
|
||||||
|
"""
|
||||||
|
def __init__(self, msg_bad: str = 'FAILED', msg_good: str = 'SUCCESS'):
|
||||||
|
self.catch_all : bool = True
|
||||||
|
self.indent: int = INDENT
|
||||||
|
self.list_errors: list[str] = ['GenericError']
|
||||||
|
self.list_warnings: list[str] = ['GenericWarning']
|
||||||
|
self.msg_bad: str = msg_bad
|
||||||
|
self.msg_good: str = msg_good
|
||||||
|
self.verbose : bool = False
|
||||||
|
self.width: int = WIDTH
|
||||||
|
|
||||||
|
def _format_exception_message(self, _exception: Exception) -> str:
|
||||||
|
"""Format using the exception's args or name, returns str."""
|
||||||
|
LOG.debug(
|
||||||
|
'Formatting exception: %s, %s',
|
||||||
|
_exception.__class__.__name__,
|
||||||
|
_exception,
|
||||||
|
)
|
||||||
|
message = ''
|
||||||
|
|
||||||
|
# Format message string from _exception
|
||||||
|
try:
|
||||||
|
if isinstance(_exception, subprocess.CalledProcessError):
|
||||||
|
message = _exception.stderr
|
||||||
|
if not isinstance(message, str):
|
||||||
|
message = message.decode('utf-8')
|
||||||
|
message = message.strip()
|
||||||
|
elif isinstance(_exception, ZeroDivisionError):
|
||||||
|
# Skip and just use exception name below
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
message = str(_exception)
|
||||||
|
except Exception:
|
||||||
|
# Just use the exception name instead
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Prepend exception name
|
||||||
|
if _exception.__class__.__name__ not in ('GenericError', 'GenericWarning'):
|
||||||
|
try:
|
||||||
|
message = f'{_exception.__class__.__name__}: {message}'
|
||||||
|
except Exception:
|
||||||
|
message = f'UNKNOWN ERROR: {message}'
|
||||||
|
|
||||||
|
# Fix multi-line messages
|
||||||
|
if '\n' in message:
|
||||||
|
try:
|
||||||
|
lines = [
|
||||||
|
f'{" "*(self.indent+self.width)}{line.strip()}'
|
||||||
|
for line in message.splitlines() if line.strip()
|
||||||
|
]
|
||||||
|
lines[0] = lines[0].strip()
|
||||||
|
message = '\n'.join(lines)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return message
|
||||||
|
|
||||||
|
def _format_function_output(
|
||||||
|
self,
|
||||||
|
output: list | subprocess.CompletedProcess,
|
||||||
|
msg_good: str,
|
||||||
|
) -> str:
|
||||||
|
"""Format function output for use in try_and_print(), returns str."""
|
||||||
|
LOG.debug('Formatting output: %s', output)
|
||||||
|
|
||||||
|
if not output:
|
||||||
|
raise GenericWarning('No output')
|
||||||
|
|
||||||
|
# Ensure we're working with a list
|
||||||
|
if isinstance(output, subprocess.CompletedProcess):
|
||||||
|
stdout = output.stdout
|
||||||
|
if not isinstance(stdout, str):
|
||||||
|
stdout = stdout.decode('utf8')
|
||||||
|
output = stdout.strip().splitlines()
|
||||||
|
if not output:
|
||||||
|
# Going to treat these as successes (for now)
|
||||||
|
LOG.warning('Program output was empty, assuming good result.')
|
||||||
|
return color_string(msg_good, 'GREEN')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
output = list(output)
|
||||||
|
except TypeError:
|
||||||
|
output = [output]
|
||||||
|
|
||||||
|
# Safety check
|
||||||
|
if not output:
|
||||||
|
# Going to ignore empty function output for now
|
||||||
|
LOG.error('Output is empty')
|
||||||
|
return 'UNKNOWN'
|
||||||
|
|
||||||
|
# Build result_msg
|
||||||
|
result_msg = f'{output.pop(0)}'
|
||||||
|
if output:
|
||||||
|
output = [f'{" "*(self.indent+self.width)}{line}' for line in output]
|
||||||
|
result_msg += '\n' + '\n'.join(output)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return result_msg
|
||||||
|
|
||||||
|
def _log_result(self, message: str, result_msg: str) -> None:
|
||||||
|
"""Log result text without color formatting."""
|
||||||
|
log_text = f'{" "*self.indent}{message:<{self.width}}{result_msg}'
|
||||||
|
for line in log_text.splitlines():
|
||||||
|
line = strip_colors(line)
|
||||||
|
LOG.info(line)
|
||||||
|
|
||||||
|
def add_error(self, exception_name: str) -> None:
|
||||||
|
"""Add exception name to error list."""
|
||||||
|
if exception_name not in self.list_errors:
|
||||||
|
self.list_errors.append(exception_name)
|
||||||
|
|
||||||
|
def add_warning(self, exception_name: str) -> None:
|
||||||
|
"""Add exception name to warning list."""
|
||||||
|
if exception_name not in self.list_warnings:
|
||||||
|
self.list_warnings.append(exception_name)
|
||||||
|
|
||||||
|
def run(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
function: Callable,
|
||||||
|
*args: Iterable[Any],
|
||||||
|
catch_all: bool | None = None,
|
||||||
|
msg_good: str | None = None,
|
||||||
|
verbose: bool | None = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Run a function and print the results, returns results as dict.
|
||||||
|
|
||||||
|
If catch_all is True then (nearly) all exceptions will be caught.
|
||||||
|
Otherwise if an exception occurs that wasn't specified it will be
|
||||||
|
re-raised.
|
||||||
|
|
||||||
|
If the function returns data it will be used instead of msg_good,
|
||||||
|
msg_bad, or exception text.
|
||||||
|
The output should be a list or a subprocess.CompletedProcess object.
|
||||||
|
|
||||||
|
If msg_good is passed it will override self.msg_good.
|
||||||
|
|
||||||
|
If verbose is True then exception names or messages will be used for
|
||||||
|
the result message. Otherwise it will simply be set to result_bad.
|
||||||
|
|
||||||
|
If catch_all and/or verbose are passed it will override
|
||||||
|
self.catch_all and/or self.verbose for this call.
|
||||||
|
|
||||||
|
args and kwargs are passed to the function.
|
||||||
|
"""
|
||||||
|
LOG.debug('function: %s.%s', function.__module__, function.__name__)
|
||||||
|
LOG.debug('args: %s', args)
|
||||||
|
LOG.debug('kwargs: %s', kwargs)
|
||||||
|
LOG.debug(
|
||||||
|
'catch_all: %s, msg_good: %s, verbose: %s',
|
||||||
|
catch_all,
|
||||||
|
msg_good,
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
f_exception = None
|
||||||
|
catch_all = catch_all if catch_all is not None else self.catch_all
|
||||||
|
msg_good = msg_good if msg_good is not None else self.msg_good
|
||||||
|
output = None
|
||||||
|
result_msg = 'UNKNOWN'
|
||||||
|
verbose = verbose if verbose is not None else self.verbose
|
||||||
|
|
||||||
|
# Build exception tuples
|
||||||
|
e_exceptions: tuple = tuple(get_exception(e) for e in self.list_errors)
|
||||||
|
w_exceptions: tuple = tuple(get_exception(e) for e in self.list_warnings)
|
||||||
|
|
||||||
|
# Run function and catch exceptions
|
||||||
|
print(f'{" "*self.indent}{message:<{self.width}}', end='', flush=True)
|
||||||
|
LOG.debug('Running function: %s.%s', function.__module__, function.__name__)
|
||||||
|
try:
|
||||||
|
output = function(*args, **kwargs)
|
||||||
|
except w_exceptions as _exception:
|
||||||
|
# Warnings
|
||||||
|
result_msg = self._format_exception_message(_exception)
|
||||||
|
print_warning(result_msg, log=False)
|
||||||
|
f_exception = _exception
|
||||||
|
except e_exceptions as _exception:
|
||||||
|
# Exceptions
|
||||||
|
result_msg = self._format_exception_message(_exception)
|
||||||
|
print_error(result_msg, log=False)
|
||||||
|
f_exception = _exception
|
||||||
|
except Exception as _exception:
|
||||||
|
# Unexpected exceptions
|
||||||
|
if verbose:
|
||||||
|
result_msg = self._format_exception_message(_exception)
|
||||||
|
else:
|
||||||
|
result_msg = self.msg_bad
|
||||||
|
print_error(result_msg, log=False)
|
||||||
|
f_exception = _exception
|
||||||
|
if not catch_all:
|
||||||
|
# Re-raise error as necessary
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# Success
|
||||||
|
if output:
|
||||||
|
result_msg = self._format_function_output(output, msg_good)
|
||||||
|
print(result_msg)
|
||||||
|
else:
|
||||||
|
result_msg = msg_good
|
||||||
|
print_success(result_msg, log=False)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
self._log_result(message, result_msg)
|
||||||
|
return {
|
||||||
|
'Exception': f_exception,
|
||||||
|
'Failed': bool(f_exception),
|
||||||
|
'Message': result_msg,
|
||||||
|
'Output': output,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def abort(
|
||||||
|
prompt_msg: str = 'Aborted.',
|
||||||
|
show_prompt_msg: bool = True,
|
||||||
|
return_code: int = 1,
|
||||||
|
) -> None:
|
||||||
|
"""Abort script."""
|
||||||
|
print_warning(prompt_msg)
|
||||||
|
if show_prompt_msg:
|
||||||
|
sleep(0.5)
|
||||||
|
pause(prompt_msg='Press Enter to exit... ')
|
||||||
|
sys.exit(return_code)
|
||||||
|
|
||||||
|
|
||||||
|
def ask(prompt_msg: str) -> bool:
|
||||||
|
"""Prompt the user with a Y/N question, returns bool."""
|
||||||
|
validator = InputYesNoValidator()
|
||||||
|
|
||||||
|
# Show prompt
|
||||||
|
response = input_text(f'{prompt_msg} [Y/N]: ', validator=validator)
|
||||||
|
if response.upper().startswith('Y'):
|
||||||
|
LOG.info('%s Yes', prompt_msg)
|
||||||
|
return True
|
||||||
|
if response.upper().startswith('N'):
|
||||||
|
LOG.info('%s No', prompt_msg)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# This shouldn't ever be reached
|
||||||
|
raise ValueError(f'Invalid answer given: {response}')
|
||||||
|
|
||||||
|
|
||||||
|
def beep(repeat: int = 1) -> None:
|
||||||
|
"""Play system bell with optional repeat."""
|
||||||
|
while repeat >= 1:
|
||||||
|
# Print bell char without a newline
|
||||||
|
print('\a', end='', flush=True)
|
||||||
|
sleep(0.5)
|
||||||
|
repeat -= 1
|
||||||
|
|
||||||
|
|
||||||
|
def choice(prompt_msg: str, choices: Iterable[str]) -> str:
|
||||||
|
"""Choose an option from a provided list, returns str.
|
||||||
|
|
||||||
|
Choices provided will be converted to uppercase and returned as such.
|
||||||
|
Similar to the commands choice (Windows) and select (Linux).
|
||||||
|
"""
|
||||||
|
LOG.debug('prompt_msg: %s, choices: %s', prompt_msg, choices)
|
||||||
|
choices = [str(c).upper()[:1] for c in choices]
|
||||||
|
prompt_msg = f'{prompt_msg} [{"/".join(choices)}]'
|
||||||
|
|
||||||
|
# Show prompt
|
||||||
|
response = input_text(prompt_msg, validator=InputChoiceValidator(choices))
|
||||||
|
|
||||||
|
# Done
|
||||||
|
LOG.info('%s %s', prompt_msg, response)
|
||||||
|
return response.upper()
|
||||||
|
|
||||||
|
|
||||||
|
def fix_prompt(message: str) -> str:
|
||||||
|
"""Fix prompt, returns str."""
|
||||||
|
if not message:
|
||||||
|
message = 'Input text: '
|
||||||
|
message = str(message)
|
||||||
|
if message[-1:] != ' ':
|
||||||
|
message += ' '
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def get_exception(name: str) -> Exception:
|
||||||
|
"""Get exception by name, returns exception object.
|
||||||
|
|
||||||
|
[Doctest]
|
||||||
|
>>> t = TryAndPrint()
|
||||||
|
>>> t._get_exception('AttributeError')
|
||||||
|
<class 'AttributeError'>
|
||||||
|
>>> t._get_exception('CalledProcessError')
|
||||||
|
<class 'subprocess.CalledProcessError'>
|
||||||
|
>>> t._get_exception('GenericError')
|
||||||
|
<class 'wk.std.GenericError'>
|
||||||
|
"""
|
||||||
|
LOG.debug('Getting exception: %s', name)
|
||||||
|
obj = getattr(sys.modules[__name__], name, None)
|
||||||
|
if obj:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# Try builtin classes
|
||||||
|
obj = getattr(sys.modules['builtins'], name, None)
|
||||||
|
if obj:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# Try all modules
|
||||||
|
for _mod in sys.modules.values():
|
||||||
|
obj = getattr(_mod, name, None)
|
||||||
|
if obj:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check if not found
|
||||||
|
if not obj:
|
||||||
|
raise AttributeError(f'Failed to find exception: {name}')
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def get_ticket_id() -> str:
|
||||||
|
"""Get ticket ID, returns str."""
|
||||||
|
prompt_msg = 'Please enter ticket ID:'
|
||||||
|
validator = InputTicketIDValidator()
|
||||||
|
|
||||||
|
# Show prompt
|
||||||
|
ticket_id = input_text(prompt_msg, validator=validator)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return ticket_id
|
||||||
|
|
||||||
|
|
||||||
|
def input_text(
|
||||||
|
prompt_msg: str = 'Enter text: ',
|
||||||
|
allow_empty: bool = False,
|
||||||
|
validator: Validator | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Get input from user, returns str."""
|
||||||
|
prompt_msg = fix_prompt(prompt_msg)
|
||||||
|
|
||||||
|
# Accept empty responses?
|
||||||
|
if not (allow_empty or validator):
|
||||||
|
validator = InputNotEmptyValidator()
|
||||||
|
|
||||||
|
# Show prompt
|
||||||
|
result = None
|
||||||
|
while result is None:
|
||||||
|
try:
|
||||||
|
result = prompt(prompt_msg, validator=validator)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# Ignore CTRL+c
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def major_exception() -> None:
|
||||||
|
"""Display traceback, optionally upload detailes, and exit."""
|
||||||
|
LOG.critical('Major exception encountered', exc_info=True)
|
||||||
|
print_error('Major exception', log=False)
|
||||||
|
print_warning(SUPPORT_MESSAGE)
|
||||||
|
if ENABLED_UPLOAD_DATA:
|
||||||
|
print_warning('Also, please run upload-logs to help debugging!')
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
|
# Done
|
||||||
|
pause('Press Enter to exit... ')
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def pause(prompt_msg: str = 'Press Enter to continue... ') -> None:
|
||||||
|
"""Simple pause implementation."""
|
||||||
|
input_text(prompt_msg, allow_empty=True)
|
||||||
|
|
||||||
|
|
||||||
|
def print_colored(
|
||||||
|
strings: Iterable[str] | str,
|
||||||
|
colors: Iterable[str | None] | str,
|
||||||
|
log: bool = False,
|
||||||
|
sep: str = ' ',
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
"""Prints strings in the colors specified."""
|
||||||
|
LOG.debug(
|
||||||
|
'strings: %s, colors: %s, sep: %s, kwargs: %s',
|
||||||
|
strings, colors, sep, kwargs,
|
||||||
|
)
|
||||||
|
msg = color_string(strings, colors, sep=sep)
|
||||||
|
print_options = {
|
||||||
|
'end': kwargs.get('end', '\n'),
|
||||||
|
'file': kwargs.get('file', sys.stdout),
|
||||||
|
'flush': kwargs.get('flush', False),
|
||||||
|
}
|
||||||
|
|
||||||
|
print(msg, **print_options)
|
||||||
|
if log:
|
||||||
|
LOG.info(strip_colors(msg))
|
||||||
|
|
||||||
|
|
||||||
|
def print_error(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
|
"""Prints message in RED and log as ERROR."""
|
||||||
|
if 'file' not in kwargs:
|
||||||
|
# Only set if not specified
|
||||||
|
kwargs['file'] = sys.stderr
|
||||||
|
print_colored(msg, 'RED', **kwargs)
|
||||||
|
if log:
|
||||||
|
LOG.error(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def print_info(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
|
"""Prints message in BLUE and log as INFO."""
|
||||||
|
print_colored(msg, 'BLUE', **kwargs)
|
||||||
|
if log:
|
||||||
|
LOG.info(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def print_report(report: list[str], indent=None, log: bool = True) -> None:
|
||||||
|
"""Print report to screen and optionally to log."""
|
||||||
|
for line in report:
|
||||||
|
if indent:
|
||||||
|
line = f'{" "*indent}{line}'
|
||||||
|
print(line)
|
||||||
|
if log:
|
||||||
|
LOG.info(strip_colors(line))
|
||||||
|
|
||||||
|
|
||||||
|
def print_standard(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
|
"""Prints message and log as INFO."""
|
||||||
|
print(msg, **kwargs)
|
||||||
|
if log:
|
||||||
|
LOG.info(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def print_success(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
|
"""Prints message in GREEN and log as INFO."""
|
||||||
|
print_colored(msg, 'GREEN', **kwargs)
|
||||||
|
if log:
|
||||||
|
LOG.info(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def print_warning(msg: str, log: bool = True, **kwargs) -> None:
|
||||||
|
"""Prints message in YELLOW and log as WARNING."""
|
||||||
|
if 'file' not in kwargs:
|
||||||
|
# Only set if not specified
|
||||||
|
kwargs['file'] = sys.stderr
|
||||||
|
print_colored(msg, 'YELLOW', **kwargs)
|
||||||
|
if log:
|
||||||
|
LOG.warning(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def set_title(title: str) -> None:
|
||||||
|
"""Set window title."""
|
||||||
|
LOG.debug('title: %s', title)
|
||||||
|
if os.name == 'nt':
|
||||||
|
os.system(f'title {title}')
|
||||||
|
else:
|
||||||
|
print_error('Setting the title is only supported under Windows.')
|
||||||
|
|
||||||
|
|
||||||
|
def show_data(
|
||||||
|
message: str,
|
||||||
|
data: Any,
|
||||||
|
color: str | None = None,
|
||||||
|
indent: int | None = None,
|
||||||
|
width: int | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Display info using default or provided indent and width."""
|
||||||
|
indent = INDENT if indent is None else indent
|
||||||
|
width = WIDTH if width is None else width
|
||||||
|
print_colored(
|
||||||
|
(f'{" "*indent}{message:<{width}}', data),
|
||||||
|
(None, color if color else None),
|
||||||
|
log=True,
|
||||||
|
sep='',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from wk.exe import run_program
|
from wk.exe import run_program
|
||||||
from wk.std import PLATFORM
|
from wk.std import PLATFORM
|
||||||
|
|
||||||
|
|
@ -13,7 +15,7 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Functions
|
# Functions
|
||||||
def capture_pane(pane_id=None):
|
def capture_pane(pane_id: str | None = None) -> str:
|
||||||
"""Capture text from current or target pane, returns str."""
|
"""Capture text from current or target pane, returns str."""
|
||||||
cmd = ['tmux', 'capture-pane', '-p']
|
cmd = ['tmux', 'capture-pane', '-p']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
|
|
@ -24,49 +26,122 @@ def capture_pane(pane_id=None):
|
||||||
return proc.stdout.strip()
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def clear_pane(pane_id=None):
|
def clear_pane(pane_id: str | None = None) -> None:
|
||||||
"""Clear pane buffer for current or target pane."""
|
"""Clear pane buffer for current or target pane."""
|
||||||
cmd = ['tmux', 'send-keys', '-R']
|
commands = [
|
||||||
|
['tmux', 'send-keys', '-R'],
|
||||||
|
['tmux', 'clear-history'],
|
||||||
|
]
|
||||||
if pane_id:
|
if pane_id:
|
||||||
cmd.extend(['-t', pane_id])
|
commands = [[*cmd, '-t', pane_id] for cmd in commands]
|
||||||
|
|
||||||
# Clear pane
|
# Clear pane
|
||||||
run_program(cmd, check=False)
|
for cmd in commands:
|
||||||
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def fix_layout(panes, layout, forced=False):
|
def fix_layout(
|
||||||
"""Fix pane sizes based on layout."""
|
layout: dict[str, dict[str, Any]],
|
||||||
if not (forced or layout_needs_fixed(panes, layout)):
|
clear_on_resize: bool = False,
|
||||||
|
forced: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Fix pane sizes based on layout.
|
||||||
|
|
||||||
|
NOTE: The magic +/- 1 values are for the split rows/columns.
|
||||||
|
"""
|
||||||
|
resize_kwargs = []
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not (forced or layout_needs_fixed(layout)):
|
||||||
# Layout should be fine
|
# Layout should be fine
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update panes
|
# Clear current pane if needed
|
||||||
for name, data in layout.items():
|
if clear_on_resize:
|
||||||
# Skip missing panes
|
clear_pane()
|
||||||
if name not in panes:
|
|
||||||
|
# Remove closed panes
|
||||||
|
for data in layout.values():
|
||||||
|
data['Panes'] = [pane for pane in data['Panes'] if poll_pane(pane)]
|
||||||
|
|
||||||
|
# Calculate constraints
|
||||||
|
avail_horizontal, avail_vertical = get_window_size()
|
||||||
|
avail_vertical -= layout['Current'].get('height', 0)
|
||||||
|
for group in ('Title', 'Info'):
|
||||||
|
if not layout[group]['Panes']:
|
||||||
continue
|
continue
|
||||||
|
avail_vertical -= layout[group].get('height', 0) + 1
|
||||||
|
num_workers = len(layout['Workers']['Panes'])
|
||||||
|
avail_vertical -= num_workers * (layout['Workers'].get('height', 0) + 1)
|
||||||
|
avail_horizontal -= layout['Progress']['width'] + 1
|
||||||
|
|
||||||
# Resize pane(s)
|
# Fix heights
|
||||||
pane_list = panes[name]
|
for group, data in layout.items():
|
||||||
if isinstance(pane_list, str):
|
if not data['Panes'] or group in ('Started', 'Progress'):
|
||||||
pane_list = [pane_list]
|
continue
|
||||||
for pane_id in pane_list:
|
resize_kwargs.append(
|
||||||
if name == 'Current':
|
{'pane_id': data['Panes'][0], 'height': data.get('height', avail_vertical)}
|
||||||
pane_id = None
|
)
|
||||||
try:
|
if group == 'Workers' and len(data['Panes']) > 1:
|
||||||
resize_pane(pane_id, **data)
|
for pane_id in data['Panes'][1:]:
|
||||||
except RuntimeError:
|
resize_kwargs.append(
|
||||||
# Assuming pane was closed just before resizing
|
{'pane_id': pane_id, 'height': data.get('height', avail_vertical)}
|
||||||
pass
|
)
|
||||||
|
|
||||||
|
# Fix widths
|
||||||
|
resize_kwargs.append(
|
||||||
|
{'pane_id': layout['Progress']['Panes'][0], 'width': layout['Progress']['width']}
|
||||||
|
)
|
||||||
|
resize_kwargs.append(
|
||||||
|
{'pane_id': layout['Started']['Panes'][0], 'height': layout['Started']['height']}
|
||||||
|
)
|
||||||
|
for group, data in layout.items():
|
||||||
|
num_panes = len(data['Panes'])
|
||||||
|
if num_panes < 2 or group not in ('Title', 'Info'):
|
||||||
|
continue
|
||||||
|
pane_width, remainder = divmod(avail_horizontal - (num_panes-1), num_panes)
|
||||||
|
for pane_id in data['Panes']:
|
||||||
|
new_width = pane_width
|
||||||
|
if remainder > 0:
|
||||||
|
new_width += 1
|
||||||
|
remainder -= 1
|
||||||
|
resize_kwargs.append({'pane_id': pane_id, 'width': new_width})
|
||||||
|
|
||||||
|
# Resize panes
|
||||||
|
for kwargs in resize_kwargs:
|
||||||
|
try:
|
||||||
|
resize_pane(**kwargs)
|
||||||
|
except RuntimeError:
|
||||||
|
# Assuming pane was closed just before resizing
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_pane_size(pane_id=None):
|
def get_pane_size(pane_id: str | None = None) -> tuple[int, int]:
|
||||||
"""Get current or target pane size, returns tuple."""
|
"""Get current or target pane size, returns tuple."""
|
||||||
cmd = ['tmux', 'display', '-p']
|
cmd = ['tmux', 'display-message', '-p']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
cmd.extend(['-t', pane_id])
|
cmd.extend(['-t', pane_id])
|
||||||
cmd.append('#{pane_width} #{pane_height}')
|
cmd.append('#{pane_width} #{pane_height}')
|
||||||
|
|
||||||
|
# Get resolution
|
||||||
|
proc = run_program(cmd, check=False)
|
||||||
|
try:
|
||||||
|
width, height = proc.stdout.strip().split()
|
||||||
|
except ValueError:
|
||||||
|
# Assuming this is a race condition as it usually happens inside the
|
||||||
|
# background fix layout loop
|
||||||
|
return 0, 0
|
||||||
|
width = int(width)
|
||||||
|
height = int(height)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return (width, height)
|
||||||
|
|
||||||
|
|
||||||
|
def get_window_size() -> tuple[int, int]:
|
||||||
|
"""Get current window size, returns tuple."""
|
||||||
|
cmd = ['tmux', 'display-message', '-p', '#{window_width} #{window_height}']
|
||||||
|
|
||||||
# Get resolution
|
# Get resolution
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
width, height = proc.stdout.strip().split()
|
width, height = proc.stdout.strip().split()
|
||||||
|
|
@ -77,7 +152,7 @@ def get_pane_size(pane_id=None):
|
||||||
return (width, height)
|
return (width, height)
|
||||||
|
|
||||||
|
|
||||||
def kill_all_panes(pane_id=None):
|
def kill_all_panes(pane_id: str | None = None) -> None:
|
||||||
"""Kill all panes except for the current or target pane."""
|
"""Kill all panes except for the current or target pane."""
|
||||||
cmd = ['tmux', 'kill-pane', '-a']
|
cmd = ['tmux', 'kill-pane', '-a']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
|
|
@ -87,7 +162,7 @@ def kill_all_panes(pane_id=None):
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def kill_pane(*pane_ids):
|
def kill_pane(*pane_ids: str) -> None:
|
||||||
"""Kill pane(s) by id."""
|
"""Kill pane(s) by id."""
|
||||||
cmd = ['tmux', 'kill-pane', '-t']
|
cmd = ['tmux', 'kill-pane', '-t']
|
||||||
|
|
||||||
|
|
@ -96,42 +171,28 @@ def kill_pane(*pane_ids):
|
||||||
run_program(cmd+[pane_id], check=False)
|
run_program(cmd+[pane_id], check=False)
|
||||||
|
|
||||||
|
|
||||||
def layout_needs_fixed(panes, layout):
|
def layout_needs_fixed(layout: dict[str, dict[str, Any]]) -> bool:
|
||||||
"""Check if layout needs fixed, returns bool."""
|
"""Check if layout needs fixed, returns bool."""
|
||||||
needs_fixed = False
|
needs_fixed = False
|
||||||
|
|
||||||
# Check panes
|
# Check panes
|
||||||
for name, data in layout.items():
|
for data in layout.values():
|
||||||
# Skip unpredictably sized panes
|
if 'height' in data:
|
||||||
if not data.get('Check', False):
|
needs_fixed = needs_fixed or any(
|
||||||
continue
|
get_pane_size(pane)[1] != data['height'] for pane in data['Panes']
|
||||||
|
)
|
||||||
# Skip missing panes
|
if 'width' in data:
|
||||||
if name not in panes:
|
needs_fixed = needs_fixed or any(
|
||||||
continue
|
get_pane_size(pane)[0] != data['width'] for pane in data['Panes']
|
||||||
|
)
|
||||||
# Check pane size(s)
|
|
||||||
pane_list = panes[name]
|
|
||||||
if isinstance(pane_list, str):
|
|
||||||
pane_list = [pane_list]
|
|
||||||
for pane_id in pane_list:
|
|
||||||
try:
|
|
||||||
width, height = get_pane_size(pane_id)
|
|
||||||
except ValueError:
|
|
||||||
# Pane may have disappeared during this loop
|
|
||||||
continue
|
|
||||||
if data.get('width', False) and data['width'] != width:
|
|
||||||
needs_fixed = True
|
|
||||||
if data.get('height', False) and data['height'] != height:
|
|
||||||
needs_fixed = True
|
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
return needs_fixed
|
return needs_fixed
|
||||||
|
|
||||||
|
|
||||||
def poll_pane(pane_id):
|
def poll_pane(pane_id: str) -> bool:
|
||||||
"""Check if pane exists, returns bool."""
|
"""Check if pane exists, returns bool."""
|
||||||
cmd = ['tmux', 'list-panes', '-F', '#D']
|
cmd = ['tmux', 'list-panes', '-F', '#{pane_id}']
|
||||||
|
|
||||||
# Get list of panes
|
# Get list of panes
|
||||||
proc = run_program(cmd, check=False)
|
proc = run_program(cmd, check=False)
|
||||||
|
|
@ -142,7 +203,12 @@ def poll_pane(pane_id):
|
||||||
|
|
||||||
|
|
||||||
def prep_action(
|
def prep_action(
|
||||||
cmd=None, working_dir=None, text=None, watch_file=None, watch_cmd='cat'):
|
cmd: str | None = None,
|
||||||
|
working_dir: pathlib.Path | str | None = None,
|
||||||
|
text: str | None = None,
|
||||||
|
watch_file: pathlib.Path | str | None = None,
|
||||||
|
watch_cmd: str = 'cat',
|
||||||
|
) -> list[str]:
|
||||||
"""Prep action to perform during a tmux call, returns list.
|
"""Prep action to perform during a tmux call, returns list.
|
||||||
|
|
||||||
This will prep for running a basic command, displaying text on screen,
|
This will prep for running a basic command, displaying text on screen,
|
||||||
|
|
@ -192,7 +258,7 @@ def prep_action(
|
||||||
return action_cmd
|
return action_cmd
|
||||||
|
|
||||||
|
|
||||||
def prep_file(path):
|
def prep_file(path: pathlib.Path | str) -> None:
|
||||||
"""Check if file exists and create empty file if not."""
|
"""Check if file exists and create empty file if not."""
|
||||||
path = pathlib.Path(path).resolve()
|
path = pathlib.Path(path).resolve()
|
||||||
try:
|
try:
|
||||||
|
|
@ -202,7 +268,11 @@ def prep_file(path):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def resize_pane(pane_id=None, width=None, height=None, **kwargs):
|
def resize_pane(
|
||||||
|
pane_id: str | None = None,
|
||||||
|
width: int | None = None,
|
||||||
|
height: int | None = None,
|
||||||
|
) -> None:
|
||||||
"""Resize current or target pane.
|
"""Resize current or target pane.
|
||||||
|
|
||||||
NOTE: kwargs is only here to make calling this function easier
|
NOTE: kwargs is only here to make calling this function easier
|
||||||
|
|
@ -227,12 +297,24 @@ def resize_pane(pane_id=None, width=None, height=None, **kwargs):
|
||||||
run_program(cmd, check=False)
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def respawn_pane(pane_id: str, **action) -> None:
|
||||||
|
"""Respawn pane with action."""
|
||||||
|
cmd = ['tmux', 'respawn-pane', '-k', '-t', pane_id]
|
||||||
|
cmd.extend(prep_action(**action))
|
||||||
|
|
||||||
|
# Respawn
|
||||||
|
run_program(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
def split_window(
|
def split_window(
|
||||||
lines=None, percent=None,
|
lines: int | None = None,
|
||||||
behind=False, vertical=False,
|
percent: int | None = None,
|
||||||
target_id=None, **action):
|
behind: bool = False,
|
||||||
|
vertical: bool = False,
|
||||||
|
target_id: str | None = None,
|
||||||
|
**action) -> str:
|
||||||
"""Split tmux window, run action, and return pane_id as str."""
|
"""Split tmux window, run action, and return pane_id as str."""
|
||||||
cmd = ['tmux', 'split-window', '-d', '-PF', '#D']
|
cmd = ['tmux', 'split-window', '-d', '-PF', '#{pane_id}']
|
||||||
|
|
||||||
# Safety checks
|
# Safety checks
|
||||||
if not (lines or percent):
|
if not (lines or percent):
|
||||||
|
|
@ -253,7 +335,7 @@ def split_window(
|
||||||
if lines:
|
if lines:
|
||||||
cmd.extend(['-l', str(lines)])
|
cmd.extend(['-l', str(lines)])
|
||||||
elif percent:
|
elif percent:
|
||||||
cmd.extend(['-p', str(percent)])
|
cmd.extend(['-l', f'{percent}%'])
|
||||||
|
|
||||||
# New pane action
|
# New pane action
|
||||||
cmd.extend(prep_action(**action))
|
cmd.extend(prep_action(**action))
|
||||||
|
|
@ -263,16 +345,7 @@ def split_window(
|
||||||
return proc.stdout.strip()
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def respawn_pane(pane_id, **action):
|
def zoom_pane(pane_id: str | None = None) -> None:
|
||||||
"""Respawn pane with action."""
|
|
||||||
cmd = ['tmux', 'respawn-pane', '-k', '-t', pane_id]
|
|
||||||
cmd.extend(prep_action(**action))
|
|
||||||
|
|
||||||
# Respawn
|
|
||||||
run_program(cmd, check=False)
|
|
||||||
|
|
||||||
|
|
||||||
def zoom_pane(pane_id=None):
|
|
||||||
"""Toggle zoom status for current or target pane."""
|
"""Toggle zoom status for current or target pane."""
|
||||||
cmd = ['tmux', 'resize-pane', '-Z']
|
cmd = ['tmux', 'resize-pane', '-Z']
|
||||||
if pane_id:
|
if pane_id:
|
||||||
382
scripts/wk/ui/tui.py
Normal file
382
scripts/wk/ui/tui.py
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
"""WizardKit: TUI functions"""
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
from os import environ
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from wk.exe import start_thread
|
||||||
|
from wk.std import sleep
|
||||||
|
from wk.ui import ansi, tmux
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
TMUX_SIDE_WIDTH = 21
|
||||||
|
TMUX_TITLE_HEIGHT = 2
|
||||||
|
TMUX_LAYOUT = { # NOTE: This needs to be in order from top to bottom
|
||||||
|
'Title': {'Panes': [], 'height': TMUX_TITLE_HEIGHT},
|
||||||
|
'Info': {'Panes': []},
|
||||||
|
'Current': {'Panes': [environ.get('TMUX_PANE', None)]},
|
||||||
|
'Workers': {'Panes': []},
|
||||||
|
'Started': {'Panes': [], 'height': TMUX_TITLE_HEIGHT},
|
||||||
|
'Progress': {'Panes': [], 'width': TMUX_SIDE_WIDTH},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
class TUI():
|
||||||
|
"""Object for tracking TUI elements."""
|
||||||
|
def __init__(self, title_text: str | None = None):
|
||||||
|
self.clear_on_resize = False
|
||||||
|
self.layout: dict[str, dict[str, Any]] = deepcopy(TMUX_LAYOUT)
|
||||||
|
self.side_width: int = TMUX_SIDE_WIDTH
|
||||||
|
self.title_text: str = title_text if title_text else 'Title Text'
|
||||||
|
self.title_text_line2: str = ''
|
||||||
|
self.title_colors: list[str] = ['BLUE', '']
|
||||||
|
|
||||||
|
# Init tmux and start a background process to maintain layout
|
||||||
|
self.init_tmux()
|
||||||
|
start_thread(self.fix_layout_loop)
|
||||||
|
|
||||||
|
# Close all panes at exit
|
||||||
|
atexit.register(tmux.kill_all_panes)
|
||||||
|
|
||||||
|
def add_info_pane(
|
||||||
|
self,
|
||||||
|
lines: int | None = None,
|
||||||
|
percent: int = 0,
|
||||||
|
update_layout: bool = True,
|
||||||
|
**tmux_args,
|
||||||
|
) -> None:
|
||||||
|
"""Add info pane."""
|
||||||
|
if not (lines or percent):
|
||||||
|
# Bail early
|
||||||
|
raise RuntimeError('Neither lines nor percent specified.')
|
||||||
|
|
||||||
|
# Calculate lines if needed
|
||||||
|
if not lines:
|
||||||
|
lines = int(tmux.get_pane_size()[1] * (percent/100))
|
||||||
|
|
||||||
|
# Set tmux split args
|
||||||
|
tmux_args.update({
|
||||||
|
'behind': True,
|
||||||
|
'lines': lines,
|
||||||
|
'target_id': None,
|
||||||
|
'vertical': True,
|
||||||
|
})
|
||||||
|
if self.layout['Info']['Panes']:
|
||||||
|
tmux_args.update({
|
||||||
|
'behind': False,
|
||||||
|
'percent': 50,
|
||||||
|
'target_id': self.layout['Info']['Panes'][-1],
|
||||||
|
'vertical': False,
|
||||||
|
})
|
||||||
|
tmux_args.pop('lines')
|
||||||
|
|
||||||
|
# Update layout
|
||||||
|
if update_layout:
|
||||||
|
self.layout['Info']['height'] = lines
|
||||||
|
|
||||||
|
# Add pane
|
||||||
|
self.layout['Info']['Panes'].append(tmux.split_window(**tmux_args))
|
||||||
|
|
||||||
|
def add_title_pane(
|
||||||
|
self,
|
||||||
|
line1: str,
|
||||||
|
line2: str | None = None,
|
||||||
|
colors: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Add pane to title row."""
|
||||||
|
lines = [line1, line2]
|
||||||
|
colors = colors if colors else self.title_colors.copy()
|
||||||
|
if not line2:
|
||||||
|
lines.pop()
|
||||||
|
colors.pop()
|
||||||
|
tmux_args = {
|
||||||
|
'behind': True,
|
||||||
|
'lines': TMUX_TITLE_HEIGHT,
|
||||||
|
'target_id': None,
|
||||||
|
'text': ansi.color_string(lines, colors, sep='\n'),
|
||||||
|
'vertical': True,
|
||||||
|
}
|
||||||
|
if self.layout['Title']['Panes']:
|
||||||
|
tmux_args.update({
|
||||||
|
'behind': False,
|
||||||
|
'percent': 50,
|
||||||
|
'target_id': self.layout['Title']['Panes'][-1],
|
||||||
|
'vertical': False,
|
||||||
|
})
|
||||||
|
tmux_args.pop('lines')
|
||||||
|
|
||||||
|
# Add pane
|
||||||
|
self.layout['Title']['Panes'].append(tmux.split_window(**tmux_args))
|
||||||
|
|
||||||
|
def add_worker_pane(
|
||||||
|
self,
|
||||||
|
lines: int | None = None,
|
||||||
|
percent: int = 0,
|
||||||
|
update_layout: bool = True,
|
||||||
|
**tmux_args,
|
||||||
|
) -> None:
|
||||||
|
"""Add worker pane."""
|
||||||
|
height = lines
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not (lines or percent):
|
||||||
|
raise RuntimeError('Neither lines nor percent specified.')
|
||||||
|
|
||||||
|
# Calculate height if needed
|
||||||
|
if not height:
|
||||||
|
height = int(tmux.get_pane_size()[1] * (percent/100))
|
||||||
|
|
||||||
|
# Set tmux split args
|
||||||
|
tmux_args.update({
|
||||||
|
'behind': False,
|
||||||
|
'lines': lines,
|
||||||
|
'percent': percent if percent else None,
|
||||||
|
'target_id': None,
|
||||||
|
'vertical': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Update layout
|
||||||
|
if update_layout:
|
||||||
|
self.layout['Workers']['height'] = height
|
||||||
|
|
||||||
|
# Add pane (ensure panes are sorted top to bottom)
|
||||||
|
self.layout['Workers']['Panes'].insert(0, tmux.split_window(**tmux_args))
|
||||||
|
|
||||||
|
def clear_current_pane(self) -> None:
|
||||||
|
"""Clear screen and history for current pane."""
|
||||||
|
tmux.clear_pane()
|
||||||
|
|
||||||
|
def clear_current_pane_height(self) -> None:
|
||||||
|
"""Clear current pane height and update layout."""
|
||||||
|
self.layout['Current'].pop('height', None)
|
||||||
|
|
||||||
|
def fix_layout(self, forced: bool = True) -> None:
|
||||||
|
"""Fix tmux layout based on self.layout."""
|
||||||
|
try:
|
||||||
|
tmux.fix_layout(self.layout, clear_on_resize=self.clear_on_resize, forced=forced)
|
||||||
|
except RuntimeError:
|
||||||
|
# Assuming self.panes changed while running
|
||||||
|
pass
|
||||||
|
|
||||||
|
def fix_layout_loop(self) -> None:
|
||||||
|
"""Fix layout on a loop.
|
||||||
|
|
||||||
|
NOTE: This should be called as a thread.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
self.fix_layout(forced=False)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
def init_tmux(self) -> None:
|
||||||
|
"""Initialize tmux layout."""
|
||||||
|
tmux.kill_all_panes()
|
||||||
|
self.layout.clear()
|
||||||
|
self.layout.update(deepcopy(TMUX_LAYOUT))
|
||||||
|
|
||||||
|
# Progress
|
||||||
|
self.layout['Progress']['Panes'].append(tmux.split_window(
|
||||||
|
lines=TMUX_SIDE_WIDTH,
|
||||||
|
text=' ',
|
||||||
|
))
|
||||||
|
|
||||||
|
# Started
|
||||||
|
self.layout['Started']['Panes'].append(tmux.split_window(
|
||||||
|
behind=True,
|
||||||
|
lines=2,
|
||||||
|
target_id=self.layout['Progress']['Panes'][0],
|
||||||
|
text=ansi.color_string(
|
||||||
|
['Started', time.strftime("%Y-%m-%d %H:%M %Z")],
|
||||||
|
['BLUE', None],
|
||||||
|
sep='\n',
|
||||||
|
),
|
||||||
|
vertical=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
# Title
|
||||||
|
self.layout['Title']['Panes'].append(tmux.split_window(
|
||||||
|
behind=True,
|
||||||
|
lines=2,
|
||||||
|
vertical=True,
|
||||||
|
text=ansi.color_string(
|
||||||
|
[self.title_text, self.title_text_line2],
|
||||||
|
self.title_colors,
|
||||||
|
sep = '\n',
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
# Done
|
||||||
|
sleep(0.2)
|
||||||
|
|
||||||
|
def remove_all_info_panes(self) -> None:
|
||||||
|
"""Remove all info panes and update layout."""
|
||||||
|
self.layout['Info'].pop('height', None)
|
||||||
|
panes = self.layout['Info']['Panes'].copy()
|
||||||
|
self.layout['Info']['Panes'].clear()
|
||||||
|
tmux.kill_pane(*panes)
|
||||||
|
|
||||||
|
def remove_all_worker_panes(self) -> None:
|
||||||
|
"""Remove all worker panes and update layout."""
|
||||||
|
self.layout['Workers'].pop('height', None)
|
||||||
|
panes = self.layout['Workers']['Panes'].copy()
|
||||||
|
self.layout['Workers']['Panes'].clear()
|
||||||
|
tmux.kill_pane(*panes)
|
||||||
|
|
||||||
|
def reset_title_pane(
|
||||||
|
self,
|
||||||
|
line1: str = 'Title Text',
|
||||||
|
line2: str = '',
|
||||||
|
colors: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Remove all extra title panes, reset main title pane, and update layout."""
|
||||||
|
colors = self.title_colors if colors is None else colors
|
||||||
|
panes = self.layout['Title']['Panes'].copy()
|
||||||
|
if len(panes) > 1:
|
||||||
|
tmux.kill_pane(*panes[1:])
|
||||||
|
self.layout['Title']['Panes'] = panes[:1]
|
||||||
|
self.set_title(line1, line2, colors)
|
||||||
|
|
||||||
|
def set_current_pane_height(self, height: int) -> None:
|
||||||
|
"""Set current pane height and update layout."""
|
||||||
|
self.layout['Current']['height'] = height
|
||||||
|
tmux.resize_pane(height=height)
|
||||||
|
|
||||||
|
def set_progress_file(self, progress_file: str) -> None:
|
||||||
|
"""Set the file to use for the progresse pane."""
|
||||||
|
tmux.respawn_pane(
|
||||||
|
pane_id=self.layout['Progress']['Panes'][0],
|
||||||
|
watch_file=progress_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_title(
|
||||||
|
self,
|
||||||
|
line1: str,
|
||||||
|
line2: str | None = None,
|
||||||
|
colors: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Set title text."""
|
||||||
|
self.title_text = line1
|
||||||
|
self.title_text_line2 = line2 if line2 else ''
|
||||||
|
if colors:
|
||||||
|
self.title_colors = colors
|
||||||
|
|
||||||
|
# Update pane (if present)
|
||||||
|
if self.layout['Title']['Panes']:
|
||||||
|
tmux.respawn_pane(
|
||||||
|
pane_id=self.layout['Title']['Panes'][0],
|
||||||
|
text=ansi.color_string(
|
||||||
|
[self.title_text, self.title_text_line2],
|
||||||
|
self.title_colors,
|
||||||
|
sep = '\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_clock(self) -> None:
|
||||||
|
"""Update 'Started' pane following clock sync."""
|
||||||
|
tmux.respawn_pane(
|
||||||
|
pane_id=self.layout['Started']['Panes'][0],
|
||||||
|
text=ansi.color_string(
|
||||||
|
['Started', time.strftime("%Y-%m-%d %H:%M %Z")],
|
||||||
|
['BLUE', None],
|
||||||
|
sep='\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def fix_layout(layout, forced: bool = False) -> None:
|
||||||
|
"""Fix pane sizes based on layout."""
|
||||||
|
resize_kwargs = []
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not (forced or layout_needs_fixed(layout)):
|
||||||
|
# Layout should be fine
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove closed panes
|
||||||
|
for data in layout.values():
|
||||||
|
data['Panes'] = [pane for pane in data['Panes'] if tmux.poll_pane(pane)]
|
||||||
|
|
||||||
|
# Update main panes
|
||||||
|
for section, data in layout.items():
|
||||||
|
if section == 'Workers':
|
||||||
|
# Skip for now
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'height' in data:
|
||||||
|
for pane_id in data['Panes']:
|
||||||
|
resize_kwargs.append({'pane_id': pane_id, 'height': data['height']})
|
||||||
|
if 'width' in data:
|
||||||
|
for pane_id in data['Panes']:
|
||||||
|
resize_kwargs.append({'pane_id': pane_id, 'width': data['width']})
|
||||||
|
for kwargs in resize_kwargs:
|
||||||
|
try:
|
||||||
|
tmux.resize_pane(**kwargs)
|
||||||
|
except RuntimeError:
|
||||||
|
# Assuming pane was closed just before resizing
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Update "group" panes widths
|
||||||
|
for group in ('Title', 'Info'):
|
||||||
|
num_panes = len(layout[group]['Panes'])
|
||||||
|
if num_panes <= 1:
|
||||||
|
continue
|
||||||
|
width = int( (tmux.get_pane_size()[0] - (1 - num_panes)) / num_panes )
|
||||||
|
for pane_id in layout[group]['Panes']:
|
||||||
|
tmux.resize_pane(pane_id, width=width)
|
||||||
|
if group == 'Title':
|
||||||
|
# (re)fix Started pane
|
||||||
|
tmux.resize_pane(layout['Started']['Panes'][0], width=TMUX_SIDE_WIDTH)
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not (layout['Workers']['Panes'] and layout['Workers']['height']):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update worker heights
|
||||||
|
worker_height = layout['Workers']['height']
|
||||||
|
workers = layout['Workers']['Panes'].copy()
|
||||||
|
num_workers = len(workers)
|
||||||
|
avail_height = sum(tmux.get_pane_size(pane)[1] for pane in workers)
|
||||||
|
avail_height += tmux.get_pane_size()[1] # Current pane
|
||||||
|
# Check if window is too small
|
||||||
|
if avail_height < (worker_height*num_workers) + 3:
|
||||||
|
# Just leave things as-is
|
||||||
|
return
|
||||||
|
# Resize current pane
|
||||||
|
tmux.resize_pane(height=avail_height-(worker_height*num_workers))
|
||||||
|
# Resize bottom pane
|
||||||
|
tmux.resize_pane(workers.pop(0), height=worker_height)
|
||||||
|
# Resize the rest of the panes by adjusting the ones above them
|
||||||
|
while len(workers) > 1:
|
||||||
|
next_height = sum(tmux.get_pane_size(pane)[1] for pane in workers[:2])
|
||||||
|
next_height -= worker_height
|
||||||
|
tmux.resize_pane(workers[1], height=next_height)
|
||||||
|
workers.pop(0)
|
||||||
|
|
||||||
|
def layout_needs_fixed(layout) -> bool:
|
||||||
|
"""Check if layout needs fixed, returns bool."""
|
||||||
|
needs_fixed = False
|
||||||
|
|
||||||
|
# Check panes
|
||||||
|
for data in layout.values():
|
||||||
|
if 'height' in data:
|
||||||
|
needs_fixed = needs_fixed or any(
|
||||||
|
tmux.get_pane_size(pane)[1] != data['height'] for pane in data['Panes']
|
||||||
|
)
|
||||||
|
if 'width' in data:
|
||||||
|
needs_fixed = needs_fixed or any(
|
||||||
|
tmux.get_pane_size(pane)[0] != data['width'] for pane in data['Panes']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return needs_fixed
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
|
|
@ -17,7 +17,7 @@ OPTIONS = {
|
||||||
|
|
||||||
def get_debug_prefix() -> str:
|
def get_debug_prefix() -> str:
|
||||||
"""Ask what we're debugging, returns log dir prefix."""
|
"""Ask what we're debugging, returns log dir prefix."""
|
||||||
menu = wk.std.Menu(title=f'{wk.cfg.main.KIT_NAME_FULL}: Debugging Menu\n')
|
menu = wk.ui.cli.Menu(title=f'{wk.cfg.main.KIT_NAME_FULL}: Debugging Menu\n')
|
||||||
for name, prefix in OPTIONS.items():
|
for name, prefix in OPTIONS.items():
|
||||||
menu.add_option(name, {'Prefix': prefix})
|
menu.add_option(name, {'Prefix': prefix})
|
||||||
selection = menu.simple_select()
|
selection = menu.simple_select()
|
||||||
|
|
@ -38,14 +38,14 @@ def get_debug_path() -> pathlib.Path:
|
||||||
|
|
||||||
# Safety check
|
# Safety check
|
||||||
if not debug_paths:
|
if not debug_paths:
|
||||||
wk.std.abort('No logs found, aborting.')
|
wk.ui.cli.abort('No logs found, aborting.')
|
||||||
|
|
||||||
# Use latest option
|
# Use latest option
|
||||||
if wk.std.ask('Use latest session?'):
|
if wk.ui.cli.ask('Use latest session?'):
|
||||||
return debug_paths[-1]
|
return debug_paths[-1]
|
||||||
|
|
||||||
# Select from list
|
# Select from list
|
||||||
menu = wk.std.Menu(title=f'{wk.cfg.main.KIT_NAME_FULL}: Debugging Menu\n')
|
menu = wk.ui.cli.Menu(title=f'{wk.cfg.main.KIT_NAME_FULL}: Debugging Menu\n')
|
||||||
for item in debug_paths:
|
for item in debug_paths:
|
||||||
menu.add_option(item.parent.name, {'Path': item})
|
menu.add_option(item.parent.name, {'Path': item})
|
||||||
selection = menu.simple_select()
|
selection = menu.simple_select()
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,21 @@ function copy_live_env() {
|
||||||
mkdir -p "$PROFILE_DIR/airootfs/usr/local/bin"
|
mkdir -p "$PROFILE_DIR/airootfs/usr/local/bin"
|
||||||
rsync -aI "$ROOT_DIR/scripts/" "$PROFILE_DIR/airootfs/usr/local/bin/"
|
rsync -aI "$ROOT_DIR/scripts/" "$PROFILE_DIR/airootfs/usr/local/bin/"
|
||||||
|
|
||||||
|
echo "Copying WizardKit UFD files..."
|
||||||
|
rsync -aI --exclude="macOS-boot-icon.tar" "$ROOT_DIR/setup/ufd/" "$PROFILE_DIR/airootfs/usr/share/WizardKit/"
|
||||||
|
tar xaf "$ROOT_DIR/setup/ufd/macOS-boot-icon.tar" -C "$PROFILE_DIR/airootfs/usr/share/WizardKit"
|
||||||
|
cp "$ROOT_DIR/images/rEFInd.png" "$PROFILE_DIR/airootfs/usr/share/WizardKit/EFI/Boot/rEFInd.png"
|
||||||
|
cp "$ROOT_DIR/images/Syslinux.png" "$PROFILE_DIR/airootfs/usr/share/WizardKit/syslinux/syslinux.png"
|
||||||
|
|
||||||
|
echo "Copying Memtest86+ files..."
|
||||||
|
rsync -aI "/boot/memtest86+/memtest.bin" "$PROFILE_DIR/airootfs/usr/share/WizardKit/syslinux/"
|
||||||
|
rsync -aI "/boot/memtest86+/memtest.efi" "$PROFILE_DIR/airootfs/usr/share/WizardKit/EFI/Memtest86+/"
|
||||||
|
mv "$PROFILE_DIR/airootfs/usr/share/WizardKit/EFI/Memtest86+"/{memtest.efi,bootx64.efi}
|
||||||
|
|
||||||
|
# Pre-compile Python scripts
|
||||||
|
unset PYTHONPYCACHEPREFIX
|
||||||
|
python -m compileall "$PROFILE_DIR/airootfs/usr/local/bin/"
|
||||||
|
|
||||||
# Update profiledef.sh to set proper permissions for executable files
|
# Update profiledef.sh to set proper permissions for executable files
|
||||||
for _file in $(find "$PROFILE_DIR/airootfs" -executable -type f | sed "s%$PROFILE_DIR/airootfs%%" | sort); do
|
for _file in $(find "$PROFILE_DIR/airootfs" -executable -type f | sed "s%$PROFILE_DIR/airootfs%%" | sort); do
|
||||||
sed -i "\$i\ [\"$_file\"]=\"0:0:755\"" "$PROFILE_DIR/profiledef.sh"
|
sed -i "\$i\ [\"$_file\"]=\"0:0:755\"" "$PROFILE_DIR/profiledef.sh"
|
||||||
|
|
@ -112,41 +127,15 @@ function update_live_env() {
|
||||||
username="tech"
|
username="tech"
|
||||||
label="${KIT_NAME_SHORT}_LINUX"
|
label="${KIT_NAME_SHORT}_LINUX"
|
||||||
|
|
||||||
|
# Boot config
|
||||||
|
cp "$ROOT_DIR/images/Syslinux.png" "$PROFILE_DIR/syslinux/splash.png"
|
||||||
|
sed -i -r "s/___+/${KIT_NAME_FULL}/" "$PROFILE_DIR/airootfs/usr/share/WizardKit/syslinux/syslinux.cfg"
|
||||||
|
|
||||||
# MOTD
|
# MOTD
|
||||||
sed -i -r "s/KIT_NAME_SHORT/$KIT_NAME_SHORT/" "$PROFILE_DIR/profiledef.sh"
|
sed -i -r "s/KIT_NAME_SHORT/$KIT_NAME_SHORT/" "$PROFILE_DIR/profiledef.sh"
|
||||||
sed -i -r "s/KIT_NAME_FULL/$KIT_NAME_SHORT/" "$PROFILE_DIR/profiledef.sh"
|
sed -i -r "s/KIT_NAME_FULL/$KIT_NAME_SHORT/" "$PROFILE_DIR/profiledef.sh"
|
||||||
sed -i -r "s/SUPPORT_URL/$KIT_NAME_SHORT/" "$PROFILE_DIR/profiledef.sh"
|
sed -i -r "s/SUPPORT_URL/$KIT_NAME_SHORT/" "$PROFILE_DIR/profiledef.sh"
|
||||||
|
|
||||||
# Boot config (legacy)
|
|
||||||
mkdir -p "$TEMP_DIR" 2>/dev/null
|
|
||||||
git clone --depth=1 https://github.com/ipxe/wimboot "$TEMP_DIR/wimboot"
|
|
||||||
rsync -aI "$TEMP_DIR/wimboot"/{LICENSE.txt,README.md,wimboot} "$PROFILE_DIR/syslinux/wimboot/"
|
|
||||||
cp "$ROOT_DIR/images/Pxelinux.png" "$PROFILE_DIR/syslinux/pxelinux.png"
|
|
||||||
cp "$ROOT_DIR/images/Syslinux.png" "$PROFILE_DIR/syslinux/syslinux.png"
|
|
||||||
sed -i -r "s/__+/$KIT_NAME_FULL/" "$PROFILE_DIR/syslinux/syslinux.cfg"
|
|
||||||
|
|
||||||
# Boot config (UEFI)
|
|
||||||
curl -Lo "$TEMP_DIR/refind.zip" "https://sourceforge.net/projects/refind/files/latest/download"
|
|
||||||
7z x -aoa "$TEMP_DIR/refind.zip" -o"$TEMP_DIR/refind"
|
|
||||||
cp "$ROOT_DIR/images/rEFInd.png" "$PROFILE_DIR/EFI/boot/rEFInd.png"
|
|
||||||
cp "$TEMP_DIR/refind"/refind*/"refind/refind_x64.efi" "$PROFILE_DIR/EFI/boot/bootx64.efi"
|
|
||||||
rsync -aI "$TEMP_DIR/refind"/refind*/refind/drivers_x64/ "$PROFILE_DIR/EFI/boot/drivers_x64/"
|
|
||||||
rsync -aI "$TEMP_DIR/refind"/refind*/refind/icons/ "$PROFILE_DIR/EFI/boot/icons/"
|
|
||||||
sed -i "s/%ARCHISO_LABEL%/${label}/" "$PROFILE_DIR/EFI/boot/refind.conf"
|
|
||||||
|
|
||||||
# Memtest86
|
|
||||||
mkdir -p "$PROFILE_DIR/EFI/memtest86/Benchmark"
|
|
||||||
mkdir -p "$TEMP_DIR/memtest86"
|
|
||||||
curl -Lo "$TEMP_DIR/memtest86/memtest86-usb.zip" "https://www.memtest86.com/downloads/memtest86-usb.zip"
|
|
||||||
7z e -aoa "$TEMP_DIR/memtest86/memtest86-usb.zip" -o"$TEMP_DIR/memtest86" "memtest86-usb.img"
|
|
||||||
7z e -aoa "$TEMP_DIR/memtest86/memtest86-usb.img" -o"$TEMP_DIR/memtest86" "MemTest86.img"
|
|
||||||
7z x -aoa "$TEMP_DIR/memtest86/MemTest86.img" -o"$TEMP_DIR/memtest86"
|
|
||||||
rm "$TEMP_DIR/memtest86/EFI/BOOT/BOOTIA32.efi"
|
|
||||||
mv "$TEMP_DIR/memtest86/EFI/BOOT/BOOTX64.efi" "$PROFILE_DIR/EFI/memtest86/memtestx64.efi"
|
|
||||||
mv "$TEMP_DIR/memtest86/EFI/BOOT"/* "$PROFILE_DIR/EFI/memtest86"/
|
|
||||||
mv "$TEMP_DIR/memtest86/help"/* "$PROFILE_DIR/EFI/memtest86"/
|
|
||||||
mv "$TEMP_DIR/memtest86/license.rtf" "$PROFILE_DIR/EFI/memtest86"/
|
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
echo "$hostname" > "$PROFILE_DIR/airootfs/etc/hostname"
|
echo "$hostname" > "$PROFILE_DIR/airootfs/etc/hostname"
|
||||||
echo "127.0.1.1 $hostname.localdomain $hostname" >> "$PROFILE_DIR/airootfs/etc/hosts"
|
echo "127.0.1.1 $hostname.localdomain $hostname" >> "$PROFILE_DIR/airootfs/etc/hosts"
|
||||||
|
|
@ -165,9 +154,6 @@ function update_live_env() {
|
||||||
# MOTD
|
# MOTD
|
||||||
sed -i -r "s/_+/$KIT_NAME_FULL Linux Environment/" "$PROFILE_DIR/airootfs/etc/motd"
|
sed -i -r "s/_+/$KIT_NAME_FULL Linux Environment/" "$PROFILE_DIR/airootfs/etc/motd"
|
||||||
|
|
||||||
# Network
|
|
||||||
ln -s "/run/systemd/resolve/stub-resolv.conf" "$PROFILE_DIR/airootfs/etc/resolv.conf"
|
|
||||||
|
|
||||||
# Oh My ZSH
|
# Oh My ZSH
|
||||||
git clone --depth=1 https://github.com/robbyrussell/oh-my-zsh.git "$SKEL_DIR/.oh-my-zsh"
|
git clone --depth=1 https://github.com/robbyrussell/oh-my-zsh.git "$SKEL_DIR/.oh-my-zsh"
|
||||||
rm -Rf "$SKEL_DIR/.oh-my-zsh/.git"
|
rm -Rf "$SKEL_DIR/.oh-my-zsh/.git"
|
||||||
|
|
@ -340,16 +326,6 @@ function build_iso() {
|
||||||
-v "$PROFILE_DIR" \
|
-v "$PROFILE_DIR" \
|
||||||
| tee -a "$LOG_DIR/$DATETIME.log"
|
| tee -a "$LOG_DIR/$DATETIME.log"
|
||||||
|
|
||||||
# Build better ISO
|
|
||||||
rsync -aI "$PROFILE_DIR/EFI/" "${ISO_DIR:-safety}/EFI/"
|
|
||||||
rsync -aI --ignore-existing "$PROFILE_DIR/syslinux/" "${ISO_DIR:-safety}/syslinux/"
|
|
||||||
## Sketchy bit ##
|
|
||||||
. /usr/bin/mkarchiso -o "${OUT_DIR}" -w "${WORK_DIR}" "${PROFILE_DIR}"
|
|
||||||
isofs_dir="${ISO_DIR}"
|
|
||||||
image_name="${KIT_NAME_SHORT}-Linux-${DATE}-x86_64.iso"
|
|
||||||
rm "${OUT_DIR}/${image_name}"
|
|
||||||
_build_iso_image
|
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
echo "Removing temp files..."
|
echo "Removing temp files..."
|
||||||
rm "$TEMP_DIR/Linux" -Rf | tee -a "$LOG_DIR/$DATETIME.log"
|
rm "$TEMP_DIR/Linux" -Rf | tee -a "$LOG_DIR/$DATETIME.log"
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,6 @@
|
||||||
setlocal EnableDelayedExpansion
|
setlocal EnableDelayedExpansion
|
||||||
title WizardKit: Build Tool
|
title WizardKit: Build Tool
|
||||||
call :CheckFlags %*
|
call :CheckFlags %*
|
||||||
rem TODO: Remove warning
|
|
||||||
echo "Windows PE build is currently under development"
|
|
||||||
echo " Proceeding will likely result in errors so be warned"
|
|
||||||
pause
|
|
||||||
call :CheckElevation || goto Exit
|
call :CheckElevation || goto Exit
|
||||||
call :FindKitsRoot || goto ErrorKitNotFound
|
call :FindKitsRoot || goto ErrorKitNotFound
|
||||||
|
|
||||||
|
|
@ -19,14 +15,8 @@ set "dandi_set_env=%adk_root%\Deployment Tools\DandISetEnv.bat"
|
||||||
if not exist "%dandi_set_env%" (goto ErrorKitNotFound)
|
if not exist "%dandi_set_env%" (goto ErrorKitNotFound)
|
||||||
call "%dandi_set_env%" || goto ErrorUnknown
|
call "%dandi_set_env%" || goto ErrorUnknown
|
||||||
|
|
||||||
:EnsureCRLF
|
|
||||||
rem Rewrite main.py using PowerShell to have CRLF/`r`n lineendings
|
|
||||||
set "script=%~dp0\.bin\Scripts\borrowed\set-eol.ps1"
|
|
||||||
set "main=%~dp0\.bin\Scripts\settings\main.py"
|
|
||||||
powershell -executionpolicy bypass -noprofile -file %script% -lineEnding win -file %main% || goto ErrorUnknown
|
|
||||||
|
|
||||||
:Launch
|
:Launch
|
||||||
set "script=%~dp0\.bin\Scripts\build_pe.ps1"
|
set "script=%~dp0\pe\build_pe.ps1"
|
||||||
powershell -executionpolicy bypass -noprofile -file %script% || goto ErrorUnknown
|
powershell -executionpolicy bypass -noprofile -file %script% || goto ErrorUnknown
|
||||||
goto Exit
|
goto Exit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@ smartmontools-svn
|
||||||
ttf-font-awesome-4
|
ttf-font-awesome-4
|
||||||
udevil
|
udevil
|
||||||
wd719x-firmware
|
wd719x-firmware
|
||||||
|
wimboot-bin
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ bc
|
||||||
bind
|
bind
|
||||||
bluez
|
bluez
|
||||||
bluez-utils
|
bluez-utils
|
||||||
|
bolt
|
||||||
btrfs-progs
|
btrfs-progs
|
||||||
cbatticon
|
cbatticon
|
||||||
chntpw
|
chntpw
|
||||||
|
|
@ -31,15 +32,21 @@ dosfstools
|
||||||
dunst
|
dunst
|
||||||
e2fsprogs
|
e2fsprogs
|
||||||
edk2-shell
|
edk2-shell
|
||||||
|
efibootmgr
|
||||||
evince
|
evince
|
||||||
exfatprogs
|
exfatprogs
|
||||||
|
f2fs-tools
|
||||||
|
fatresize
|
||||||
feh
|
feh
|
||||||
ffmpeg
|
ffmpeg
|
||||||
firefox
|
firefox
|
||||||
|
foot-terminfo
|
||||||
gnome-keyring
|
gnome-keyring
|
||||||
|
gnu-netcat
|
||||||
gparted
|
gparted
|
||||||
gpicview
|
gpicview
|
||||||
gptfdisk
|
gptfdisk
|
||||||
|
grub
|
||||||
gsmartcontrol
|
gsmartcontrol
|
||||||
hardinfo-gtk3
|
hardinfo-gtk3
|
||||||
hexedit
|
hexedit
|
||||||
|
|
@ -50,6 +57,7 @@ intel-ucode
|
||||||
iwd
|
iwd
|
||||||
iwgtk
|
iwgtk
|
||||||
jfsutils
|
jfsutils
|
||||||
|
kitty-terminfo
|
||||||
ldns
|
ldns
|
||||||
leafpad
|
leafpad
|
||||||
less
|
less
|
||||||
|
|
@ -57,65 +65,77 @@ lha
|
||||||
libewf
|
libewf
|
||||||
libinput
|
libinput
|
||||||
libldm
|
libldm
|
||||||
|
libusb-compat
|
||||||
libxft
|
libxft
|
||||||
linux
|
linux
|
||||||
linux-firmware
|
linux-firmware
|
||||||
|
linux-firmware-marvell
|
||||||
lm_sensors
|
lm_sensors
|
||||||
|
lsscsi
|
||||||
lvm2
|
lvm2
|
||||||
lzip
|
lzip
|
||||||
man-db
|
man-db
|
||||||
man-pages
|
man-pages
|
||||||
mdadm
|
mdadm
|
||||||
mediainfo
|
mediainfo
|
||||||
memtest86+
|
|
||||||
memtest86-efi
|
memtest86-efi
|
||||||
mesa-demos
|
mesa-demos
|
||||||
mesa-utils
|
mesa-utils
|
||||||
mkinitcpio
|
mkinitcpio
|
||||||
mkinitcpio-archiso
|
mkinitcpio-archiso
|
||||||
|
mkinitcpio-nfs-utils
|
||||||
mkvtoolnix-cli
|
mkvtoolnix-cli
|
||||||
mprime-bin
|
mprime-bin
|
||||||
mpv
|
mpv
|
||||||
mtools
|
mtools
|
||||||
nano
|
nano
|
||||||
|
nbd
|
||||||
ncdu
|
ncdu
|
||||||
|
ndisc6
|
||||||
|
nfs-utils
|
||||||
|
nmap
|
||||||
noto-fonts
|
noto-fonts
|
||||||
noto-fonts-cjk
|
noto-fonts-cjk
|
||||||
|
ntfs-3g
|
||||||
numlockx
|
numlockx
|
||||||
nvme-cli
|
nvme-cli
|
||||||
|
open-iscsi
|
||||||
openbox
|
openbox
|
||||||
openssh
|
openssh
|
||||||
opensuperclone-git
|
opensuperclone-git
|
||||||
otf-font-awesome-4
|
otf-font-awesome-4
|
||||||
p7zip
|
p7zip
|
||||||
papirus-icon-theme
|
papirus-icon-theme
|
||||||
|
parted
|
||||||
perl
|
perl
|
||||||
picom
|
picom
|
||||||
pipes.sh
|
pipes.sh
|
||||||
pv
|
pv
|
||||||
python
|
python
|
||||||
python-docopt
|
python-prompt_toolkit
|
||||||
python-psutil
|
python-psutil
|
||||||
python-pytz
|
python-pytz
|
||||||
python-requests
|
python-requests
|
||||||
qemu-guest-agent
|
qemu-guest-agent
|
||||||
qemu-guest-agent
|
refind
|
||||||
reiserfsprogs
|
|
||||||
reiserfsprogs
|
reiserfsprogs
|
||||||
rfkill
|
rfkill
|
||||||
rng-tools
|
|
||||||
rofi
|
rofi
|
||||||
rsync
|
rsync
|
||||||
rxvt-unicode
|
rxvt-unicode
|
||||||
rxvt-unicode-terminfo
|
rxvt-unicode-terminfo
|
||||||
|
sdparm
|
||||||
smartmontools-svn
|
smartmontools-svn
|
||||||
|
sof-firmware
|
||||||
speedtest-cli
|
speedtest-cli
|
||||||
spice-vdagent
|
spice-vdagent
|
||||||
|
squashfs-tools
|
||||||
st
|
st
|
||||||
sudo
|
sudo
|
||||||
sysbench
|
sysbench
|
||||||
sysfsutils
|
sysfsutils
|
||||||
syslinux
|
syslinux
|
||||||
|
systemd-resolvconf
|
||||||
systemd-sysvcompat
|
systemd-sysvcompat
|
||||||
terminus-font
|
terminus-font
|
||||||
testdisk
|
testdisk
|
||||||
|
|
@ -125,6 +145,8 @@ tigervnc
|
||||||
tint2
|
tint2
|
||||||
tk
|
tk
|
||||||
tmux
|
tmux
|
||||||
|
tpm2-tools
|
||||||
|
tpm2-tss
|
||||||
tree
|
tree
|
||||||
ttf-font-awesome-4
|
ttf-font-awesome-4
|
||||||
ttf-hack
|
ttf-hack
|
||||||
|
|
@ -135,6 +157,8 @@ ufw
|
||||||
unarj
|
unarj
|
||||||
unrar
|
unrar
|
||||||
unzip
|
unzip
|
||||||
|
usb_modeswitch
|
||||||
|
usbmuxd
|
||||||
usbutils
|
usbutils
|
||||||
util-linux
|
util-linux
|
||||||
veracrypt
|
veracrypt
|
||||||
|
|
@ -142,7 +166,9 @@ vim
|
||||||
virtualbox-guest-utils
|
virtualbox-guest-utils
|
||||||
volumeicon
|
volumeicon
|
||||||
wd719x-firmware
|
wd719x-firmware
|
||||||
|
wezterm-terminfo
|
||||||
which
|
which
|
||||||
|
wimboot-bin
|
||||||
wimlib
|
wimlib
|
||||||
wmctrl
|
wmctrl
|
||||||
xarchiver
|
xarchiver
|
||||||
|
|
@ -150,6 +176,7 @@ xf86-input-libinput
|
||||||
xf86-video-amdgpu
|
xf86-video-amdgpu
|
||||||
xf86-video-fbdev
|
xf86-video-fbdev
|
||||||
xf86-video-nouveau
|
xf86-video-nouveau
|
||||||
|
xf86-video-qxl
|
||||||
xf86-video-vesa
|
xf86-video-vesa
|
||||||
xfsprogs
|
xfsprogs
|
||||||
xorg-server
|
xorg-server
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ curl
|
||||||
dos2unix
|
dos2unix
|
||||||
git
|
git
|
||||||
gtk3
|
gtk3
|
||||||
|
memtest86+
|
||||||
|
memtest86+-efi
|
||||||
p7zip
|
p7zip
|
||||||
perl-rename
|
perl-rename
|
||||||
pv
|
pv
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
LANG=en_US.UTF-8
|
LANG=C.UTF-8
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
# vim:set ft=sh
|
|
||||||
# MODULES
|
|
||||||
# The following modules are loaded before any boot hooks are
|
|
||||||
# run. Advanced users may wish to specify all system modules
|
|
||||||
# in this array. For instance:
|
|
||||||
# MODULES=(piix ide_disk reiserfs)
|
|
||||||
MODULES=()
|
|
||||||
|
|
||||||
# BINARIES
|
|
||||||
# This setting includes any additional binaries a given user may
|
|
||||||
# wish into the CPIO image. This is run last, so it may be used to
|
|
||||||
# override the actual binaries included by a given hook
|
|
||||||
# BINARIES are dependency parsed, so you may safely ignore libraries
|
|
||||||
BINARIES=()
|
|
||||||
|
|
||||||
# FILES
|
|
||||||
# This setting is similar to BINARIES above, however, files are added
|
|
||||||
# as-is and are not parsed in any way. This is useful for config files.
|
|
||||||
FILES=()
|
|
||||||
|
|
||||||
# HOOKS
|
|
||||||
# This is the most important setting in this file. The HOOKS control the
|
|
||||||
# modules and scripts added to the image, and what happens at boot time.
|
|
||||||
# Order is important, and it is recommended that you do not change the
|
|
||||||
# order in which HOOKS are added. Run 'mkinitcpio -H <hook name>' for
|
|
||||||
# help on a given hook.
|
|
||||||
# 'base' is _required_ unless you know precisely what you are doing.
|
|
||||||
# 'udev' is _required_ in order to automatically load modules
|
|
||||||
# 'filesystems' is _required_ unless you specify your fs modules in MODULES
|
|
||||||
# Examples:
|
|
||||||
## This setup specifies all modules in the MODULES setting above.
|
|
||||||
## No raid, lvm2, or encrypted root is needed.
|
|
||||||
# HOOKS=(base)
|
|
||||||
#
|
|
||||||
## This setup will autodetect all modules for your system and should
|
|
||||||
## work as a sane default
|
|
||||||
# HOOKS=(base udev autodetect block filesystems)
|
|
||||||
#
|
|
||||||
## This setup will generate a 'full' image which supports most systems.
|
|
||||||
## No autodetection is done.
|
|
||||||
# HOOKS=(base udev block filesystems)
|
|
||||||
#
|
|
||||||
## This setup assembles a pata mdadm array with an encrypted root FS.
|
|
||||||
## Note: See 'mkinitcpio -H mdadm' for more information on raid devices.
|
|
||||||
# HOOKS=(base udev block mdadm encrypt filesystems)
|
|
||||||
#
|
|
||||||
## This setup loads an lvm2 volume group on a usb device.
|
|
||||||
# HOOKS=(base udev block lvm2 filesystems)
|
|
||||||
#
|
|
||||||
## NOTE: If you have /usr on a separate partition, you MUST include the
|
|
||||||
# usr, fsck and shutdown hooks.
|
|
||||||
HOOKS=(base udev modconf memdisk archiso_shutdown archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs archiso_kms block filesystems keyboard)
|
|
||||||
|
|
||||||
# COMPRESSION
|
|
||||||
# Use this to compress the initramfs image. By default, gzip compression
|
|
||||||
# is used. Use 'cat' to create an uncompressed image.
|
|
||||||
#COMPRESSION="gzip"
|
|
||||||
#COMPRESSION="bzip2"
|
|
||||||
#COMPRESSION="lzma"
|
|
||||||
COMPRESSION="xz"
|
|
||||||
#COMPRESSION="lzop"
|
|
||||||
#COMPRESSION="lz4"
|
|
||||||
#COMPRESSION="zstd"
|
|
||||||
|
|
||||||
# COMPRESSION_OPTIONS
|
|
||||||
# Additional options for the compressor
|
|
||||||
#COMPRESSION_OPTIONS=()
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
HOOKS=(base udev modconf kms memdisk archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs block filesystems keyboard)
|
||||||
|
COMPRESSION="xz"
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# mkinitcpio preset file for the 'linux' package on archiso
|
|
||||||
|
|
||||||
PRESETS=('archiso')
|
|
||||||
|
|
||||||
ALL_kver='/boot/vmlinuz-linux'
|
|
||||||
ALL_config='/etc/mkinitcpio.conf'
|
|
||||||
|
|
||||||
archiso_image="/boot/initramfs-linux.img"
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
# remove from airootfs!
|
|
||||||
[Trigger]
|
|
||||||
Operation = Install
|
|
||||||
Type = Package
|
|
||||||
Target = glibc
|
|
||||||
|
|
||||||
[Action]
|
|
||||||
Description = Uncommenting en_US.UTF-8 locale and running locale-gen...
|
|
||||||
When = PostTransaction
|
|
||||||
Depends = glibc
|
|
||||||
Depends = sed
|
|
||||||
Depends = sh
|
|
||||||
Exec = /bin/sh -c "sed -i 's/#\(en_US\.UTF-8\)/\1/' /etc/locale.gen && locale-gen"
|
|
||||||
23
setup/linux/profile/airootfs/etc/resolv.conf
Normal file
23
setup/linux/profile/airootfs/etc/resolv.conf
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).
|
||||||
|
# Do not edit.
|
||||||
|
#
|
||||||
|
# This file might be symlinked as /etc/resolv.conf. If you're looking at
|
||||||
|
# /etc/resolv.conf and seeing this text, you have followed the symlink.
|
||||||
|
#
|
||||||
|
# This is a dynamic resolv.conf file for connecting local clients to the
|
||||||
|
# internal DNS stub resolver of systemd-resolved. This file lists all
|
||||||
|
# configured search domains.
|
||||||
|
#
|
||||||
|
# Run "resolvectl status" to see details about the uplink DNS servers
|
||||||
|
# currently in use.
|
||||||
|
#
|
||||||
|
# Third party programs should typically not access this file directly, but only
|
||||||
|
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
|
||||||
|
# different way, replace this symlink by a static file or a different symlink.
|
||||||
|
#
|
||||||
|
# See man:systemd-resolved.service(8) for details about the supported modes of
|
||||||
|
# operation for /etc/resolv.conf.
|
||||||
|
|
||||||
|
nameserver 127.0.0.53
|
||||||
|
options edns0 trust-ad
|
||||||
|
search .
|
||||||
|
|
@ -12,7 +12,6 @@ alias fix-perms='find -type d -exec chmod 755 "{}" \; && find -type f -exec chmo
|
||||||
alias hexedit='hexedit --color'
|
alias hexedit='hexedit --color'
|
||||||
alias hw-info='sudo hw-info | less -S'
|
alias hw-info='sudo hw-info | less -S'
|
||||||
alias ip='ip -br -c'
|
alias ip='ip -br -c'
|
||||||
alias journalctl-datarec="echo -e 'Monitoring journal output...\n' && journalctl -kf | grep -Ei 'ata|nvme|scsi|sd[a..z]+|usb|comreset|critical|error'"
|
|
||||||
alias less='less -S'
|
alias less='less -S'
|
||||||
alias ls='ls --color=auto'
|
alias ls='ls --color=auto'
|
||||||
alias mkdir='mkdir -p'
|
alias mkdir='mkdir -p'
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,17 @@
|
||||||
#
|
#
|
||||||
## Calculate DPI, update settings if necessary, then start desktop apps
|
## Calculate DPI, update settings if necessary, then start desktop apps
|
||||||
|
|
||||||
|
MONITOR=$(xrandr --listmonitors | grep -E '^\s+[0-9]' | head -1 | sed -r 's/^.*\s+(.*)$/\1/')
|
||||||
REGEX_XRANDR='^.* ([0-9]+)x([0-9]+)\+[0-9]+\+[0-9]+.* ([0-9]+)mm x ([0-9]+)mm.*$'
|
REGEX_XRANDR='^.* ([0-9]+)x([0-9]+)\+[0-9]+\+[0-9]+.* ([0-9]+)mm x ([0-9]+)mm.*$'
|
||||||
|
|
||||||
|
# Resize screen in VMs
|
||||||
|
if lsmod | grep -Eq 'qxl|virtio_gpu'; then
|
||||||
|
echo -n "Starting VM guest services..."
|
||||||
|
spice-vdagent
|
||||||
|
sleep 0.5
|
||||||
|
xrandr --output "${MONITOR}" --auto
|
||||||
|
fi
|
||||||
|
|
||||||
echo -n "Getting display details... "
|
echo -n "Getting display details... "
|
||||||
|
|
||||||
# Get screen data
|
# Get screen data
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
setterm -blank 0 -powerdown 0 2>/dev/null
|
setterm -blank 0 -powerdown 0 2>/dev/null
|
||||||
if [ "$(fgconsole 2>/dev/null)" -eq "1" ]; then
|
if [ "$(fgconsole 2>/dev/null)" -eq "1" ]; then
|
||||||
|
# VM guest init
|
||||||
|
if lsmod | grep -Eq 'qxl|virtio_gpu'; then
|
||||||
|
systemctl start spice-vdagentd.service
|
||||||
|
fi
|
||||||
|
|
||||||
# Set up teststation details
|
# Set up teststation details
|
||||||
$HOME/.setup_teststation
|
$HOME/.setup_teststation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[Network]
|
||||||
|
IPv6PrivacyExtensions=yes
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
[Match]
|
[Match]
|
||||||
|
# Matching with "Type=ether" causes issues with containers because it also matches virtual Ethernet interfaces (veth*).
|
||||||
|
# See https://bugs.archlinux.org/task/70892
|
||||||
|
# Instead match by globbing the network interface name.
|
||||||
Name=en*
|
Name=en*
|
||||||
Name=eth*
|
Name=eth*
|
||||||
|
|
||||||
[Network]
|
[Network]
|
||||||
DHCP=yes
|
DHCP=yes
|
||||||
IPv6PrivacyExtensions=yes
|
MulticastDNS=yes
|
||||||
|
|
||||||
[DHCP]
|
# systemd-networkd does not set per-interface-type default route metrics
|
||||||
RouteMetric=512
|
# https://github.com/systemd/systemd/issues/17698
|
||||||
|
# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband.
|
||||||
|
# Use values from NetworkManager. From nm_device_get_route_metric_default in
|
||||||
|
# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c
|
||||||
|
[DHCPv4]
|
||||||
|
RouteMetric=100
|
||||||
|
|
||||||
|
[IPv6AcceptRA]
|
||||||
|
RouteMetric=100
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
[Match]
|
|
||||||
Name=wlp*
|
|
||||||
Name=wlan*
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
DHCP=yes
|
|
||||||
IPv6PrivacyExtensions=yes
|
|
||||||
|
|
||||||
[DHCP]
|
|
||||||
RouteMetric=1024
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
[Match]
|
||||||
|
Name=wl*
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
DHCP=yes
|
||||||
|
MulticastDNS=yes
|
||||||
|
|
||||||
|
# systemd-networkd does not set per-interface-type default route metrics
|
||||||
|
# https://github.com/systemd/systemd/issues/17698
|
||||||
|
# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband.
|
||||||
|
# Use values from NetworkManager. From nm_device_get_route_metric_default in
|
||||||
|
# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c
|
||||||
|
[DHCPv4]
|
||||||
|
RouteMetric=600
|
||||||
|
|
||||||
|
[IPv6AcceptRA]
|
||||||
|
RouteMetric=600
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
[Match]
|
||||||
|
Name=ww*
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
DHCP=yes
|
||||||
|
|
||||||
|
# systemd-networkd does not set per-interface-type default route metrics
|
||||||
|
# https://github.com/systemd/systemd/issues/17698
|
||||||
|
# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband.
|
||||||
|
# Use values from NetworkManager. From nm_device_get_route_metric_default in
|
||||||
|
# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c
|
||||||
|
[DHCPv4]
|
||||||
|
RouteMetric=700
|
||||||
|
|
||||||
|
[IPv6AcceptRA]
|
||||||
|
RouteMetric=700
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Default systemd-resolved configuration for archiso
|
||||||
|
|
||||||
|
[Resolve]
|
||||||
|
MulticastDNS=yes
|
||||||
|
|
@ -5,4 +5,4 @@ Description=Temporary /etc/pacman.d/gnupg directory
|
||||||
What=tmpfs
|
What=tmpfs
|
||||||
Where=/etc/pacman.d/gnupg
|
Where=/etc/pacman.d/gnupg
|
||||||
Type=tmpfs
|
Type=tmpfs
|
||||||
Options=mode=0755
|
Options=mode=0755,noswap
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/usr/lib/systemd/system/rngd.service
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Initializes Pacman keyring
|
Description=Initializes Pacman keyring
|
||||||
Wants=haveged.service
|
|
||||||
After=haveged.service
|
|
||||||
Requires=etc-pacman.d-gnupg.mount
|
Requires=etc-pacman.d-gnupg.mount
|
||||||
After=etc-pacman.d-gnupg.mount
|
After=etc-pacman.d-gnupg.mount time-sync.target
|
||||||
|
BindsTo=etc-pacman.d-gnupg.mount
|
||||||
|
Before=archlinux-keyring-wkd-sync.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
ExecStart=/usr/bin/pacman-key --init
|
ExecStart=/usr/bin/pacman-key --init
|
||||||
ExecStart=/usr/bin/pacman-key --populate archlinux
|
ExecStart=/usr/bin/pacman-key --populate
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
||||||
4
setup/linux/profile/airootfs/root/.gnupg/scdaemon.conf
Normal file
4
setup/linux/profile/airootfs/root/.gnupg/scdaemon.conf
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
disable-ccid
|
||||||
|
disable-pinpad
|
||||||
|
pcsc-driver /usr/lib/libpcsclite.so
|
||||||
|
pcsc-shared
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue